发表于: 2020-05-16 23:55:14
1 1362
今天完成的事情:学习了Spring AOP底层原理,花了些时间,然后学习了动态代理,Aspectj
1.aop概述
aop就是面向切面编程,采用了横向抽取的机制,替代了传统的纵向继承重复性代码性能监视,事务管理,安全检查,缓存
Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代理
2.aop相关术语
Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在Spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
Pointcut(切入点): 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义;
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
Introduction(引介): 引介是-种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field(属性).
Target(目标对象:代理的目标对象
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面):是切入点和通知(引介)的结合
3.AOP的底层实现
手动实现jdk的动态代理,(有接口及其实现类采用)
代理类的实现
public class MyJdkProxy implements InvocationHandler {
private UserDao userDao;
public MyJdkProxy(UserDao userDao){
this.userDao = userDao;
}
public Object createProxy(){
Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),this);
return proxy;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验。。。。。。。。。。");
return method.invoke(userDao,args);
}
return method.invoke(userDao,args);
}
}
1.)第一个proxy是Proxy.newProxyInstance得到的实例。而第二个proxy是InvocationHandler中的invoke方法的参数名。
2.)这里的method属性传入的是Method类的对象,并不是一个名字。这个method对象是用真正的方法名,由方法名和形参抽象出Method对象。
3.)args[]中传入的是原方法执行所需要的参数,由于参数可能有多个,所以使用数组。
4.)这里不能返回this,invoke方法的返回值是代理类实例。this是当前对象的实例也就是InvocationHandler的实例。
5)method.invoke(o, args)方法,用来执行对象o的目标方法。在课程中的例子实际上就是那个UserDaoImpl对象的save方法。method.invoke方法的返回值是原方法的返回值。method.invoke方法的作用实际和直接“对象.方法名”调用作用是相同的
6.)不能直接返回proxy对象。传入的参数proxy在我们重写的逻辑中没有直接用到,这只是父类中规定好的参数,我们重写时需要遵循。如果不return method.invoke就不会调用到想要调用的方法了。 因为我们增强时返回的是Proxy.newProxyInstance的实例,即外面的proxy代理对象。将来我们要使用它来进行调用,代理在调用的时候会自动调用invoke从而达到增强效果。如果不使用method.invoke就不会调用到真正的类的方法了
测试:
public class UserTest {
@Test
public void Test(){
UserDao userDao = new UserDaoImpl();
UserDao proxy = (UserDao) new MyJdkProxy(userDao).createProxy();
proxy.delete();
proxy.find();
proxy.save();
proxy.update();
}
}
手动实现CGLIB动态代理
对于不适用接口的业务类,无法使用JDK动态代理
CGlib采用非常底层字节码技术,可以为一个类创建子类,解决无接口代理问题
public class MyCglibProxy implements MethodInterceptor {
private UserDao userDao;
public MyCglibProxy(UserDao userDao){
this.userDao = userDao;
}
public Object createProxy(){
//创建核心类
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(userDao.getClass());
//设置回调
enhancer.setCallback(this);//这个this就是传入的上面MethodInterceptor接口
//生成代理
Object proxy = enhancer.create();
return proxy;
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if("save".equals(method.getName())){
System.out.println("权限校验。。。。。。。");
return methodProxy.invokeSuper(proxy,args);
}
return methodProxy.invokeSuper(proxy,args);
}
}
小结:
●Spring在运行期,生成动态代理对象,不需要特殊的编译器
●Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术为目标Bean执行横向织入
1.若目标对象实现了若干接口, spring使用JDK的java.lang.reflect.Proxy类代理。
2.若目标对象没有实现任何接口, spring使用CGLIB库生成目标对象的子类。
●程序中应优先对接口创建代理,便于程序解耦维护
●标记为final的方法,不能被代理,因为无法进行覆盖
一JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰
一CGLib是针对目标类生产子类,因此类或方法不能使final的
spring只支持方法的连接点,不支持属性的连接点;
4. AOP增强的类型即 通知的类型
Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
一前置通知org.springframework.aop.MethodBeforeAdvice
●在目标方法执行前实施增强
一后置通知org.springframework.aop.AfterReturningAdvice
●在目标方法执行后实施增强
一环绕通知org.aopalliance.intercept.MethodInterceptor
●在目标方法执行前后实施增强
一异常抛出通知org.springframework.aop.ThrowsAdvice
●在方法抛出异常后实施增强
一引介通知org.springframework.aop.IntroductionInterceptor
●在目标类中添加一-些新的方法和属性
AOP切面类型
●Advisor :代表一般切面, Advice本身就是一个切面,对目标类所有方法进
行拦截
●PointcutAdvisor :代表具有切点的切面,可以指定拦截目标类哪些方法
●IntroductionAdvisor :代表引介切面,针对引介通知而使用切面
4.1 运用Advisor实现一个前置通知
运行结果
import java.lang.reflect.Method;
public class MyBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object target) throws Throwable {
System.out.println("前置通知。。。。。。");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置的目标类-->
<bean id="userDao" class="com.hyx.dao.impl.UserDaoImpl"/>
<!--配置的前置通知类型-->
<bean id="myBeforeAdvice" class="com.hyx.dao.impl.MyBeforeAdvice"/>
<!--Spring AOP 产生的代理类-->
<bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--配置目标类-->
<property name="target" ref="userDao"/>
<!--实现的接口-->
<property name="proxyInterfaces" value="com.hyx.dao.UserDao"/>
<!--采用拦截的名称--><property name="interceptorNames" value="myBeforeAdvice"/>
<!--其他的一些属性:
</bean>
-proxyTargetClass:是否对类代理而不是接口,设置为true时,使用CGLib代理
-interceptorNames:需要织入目标的Advice
-singleton:返回代理是否为单实例,默认为单单例
-optimize:当设置为true时,强制使用CGLib
-->
<property name="singleton" value="true"/>
</beans>
//@RunWith是声明一个运行器, @RunWith(SpringJUnit4ClassRunner.class)就是指使用Spring的JUnit4来运行
//@ContextConfiguration这个注解通常与@RunWith(SpringJUnit4ClassRunner.class)联合使用用来测试,用来注入测试需要的spring配置文件
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserTest {
@Resource(name="studentDaoProxy")
private UserDao userDao;
@Test
public void test(){
userDao.update();
userDao.save();
userDao.find();
userDao.delete();
}
}
4.2 PointcutAdvisor 切点切面方式实现环绕通知
-JdkRegexpMethodPointcut 构造正则表达式切点 作为它的实现类
public class MyAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕通知前。。。。。");
Object obj = invocation.proceed();//用来执行目标方法
System.out.println("环绕通知后。。。。。");
return obj;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置的目标类-->
<bean id="studentDao" class="com.hyx.dao.demo2.StudentDao"/>
<!--配置通知-->
<bean id="myAroundAdvice" class="com.hyx.dao.demo2.MyAroundAdvice"/>
<!--一般的切面都是使用通知作为切面,现在对目标类的某个方法进行增强需要配置一个带有一个切入点的切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--pattern中配置正则表达式:.任意字符 *任意次数-->
<property name="pattern" value=".*"/>
<property name="advice" value="myAroundAdvice"/>
</bean>
<!--配置产生代理-->
<bean id="studentProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="studentDao"/>
<property name="proxyTargetClass" value="true"/><!--没有实现接口配置-->
<property name="interceptorNames" value="myAdvisor"/>
</bean>
</beans>
上面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大
●解决方案:自动创建代理
一BeanNameAutoProxyCreator 根据Bean名称创建代理,适合对所有类的方法进行增强;
一DefaultAdvisorAutoProxyCreator 根据Advisor本身包含信息创建代理,基于切面,单独配置切面
一AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ注解进行自动代理
5. AspectJ实现AOP方式(重要)
●AspectJ是一个基于Java语言的单独的AOP框架
●Spring2.0以后新增了对AspectJ切点表达式支持
●@AspectJ是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面,新版本Spring框架,建议使用AspectJ方式来开发AOP
●使用AspectJ需要导入Spring AOP和AspectJ相关jar包
一spring-aop-4.2.4.RELEASE.jar
一com.springsource.org.aopalliance-1.0.0.jar
一spring-aspects-4.2.4.RELEASE.jar
一com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
基于注解开发
<!--开启AspectJ自动代理-->
<aop:aspectj-autoproxy/>
a. @AspectJ提供不同的通知类型
@Before前置通知,相当于BeforeAdvice
@AfterReturning后置通知,相当于AfterReturningAdvice
@Around环绕通知,相当于MethodInterceptor
@AfterThrowing异常抛出通知,相当于ThrowAdvice
@After最终final通知,不管是否异常,该通知都会执行
@DeclarwParents引介通知,相当于IntroductionInterceptor(不要求掌握)
b.在通知中通过value属性定义切点
●通过execution函数,可以定义切点的方法切入
●语法: execution( <访问修饰符> ?<返回类型> <方法名>(<参数>)<异常>)
●例如
一匹配所有类public方法execution(public * *(..))
一匹配指定包下所有类方法execution(* com.imooc.dao.*(..))不包含子包
一execution(* com.imooc.dao..*(..)) ..*表示包、 子孙包下所有类
一匹配指定类所有方法execution(* com.imooc.service.UserService.*(..))
一匹配实现特定接口所有类方法execution(* com.imooc.dao.GenericDA0+.*(..))
一匹配所有save开头的方法execution(*save*(..))
基于xml方式开发
明天计划的事情:明天把aop整理下,熟悉下AspectJ,以及整合下Spring相关内容
遇到的问题: 还不错,问题比较少
收获:学到了aop
评论