1.AOP简介
1.1 概念
AOP(Aspect Orient Programming),一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。
散布于应用中多处的功能被称为横切关注点(cross-cutting concern),这些关注点从概念上是与应用业务逻辑分离的。而把这些横切关注点与业务逻辑相分离正式面向切面编程(AOP)要解决的问题。
AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。
1.2 基本术语
连接点(Joinpoint):指那些被拦截到的点。在spring中指的是方法,因为spring只支持方法类型的连接点。
切入点(Pointcut):指要对哪些Joinpoint进行拦截的定义。
通知(Advice):指拦截到Joinpoint之后所要做的事情,分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)。
切面(Aspect):是切入点和通知的结合,把增强应用到具体方法上面,此过程称为切面 。
引入(Introduction):一种特殊的通知。在不修改类代码的前提下,引介可以在运行期为类动态的添加一些方法或者Field。
目标对象(Target):所代理的目标对象(要增强的类)。
织入(Weaving):将advice应用到target的过程。
代理(Proxy):一个类被AOP织入增强后,就产生一个结果代理类。
2.实现原理
Spring在运行时通知对象。通过在代理勒种包裹切面,Spring在运行期把切面织入到Spring管理的bean中。如图2.1所示,代理类封装了目标类,并且拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。
通过JDKProxy或CGLibProxy动态生成代理对象,当从外部调用目标对象的方法时,外部调用的对象为动态代理生成的Proxy对象。因此外部调用的方法实际为Proxy对象中的实现的方法,这个方法中包含了AOP中设置的Advice。然后通过反射机制调用目标对象的处理逻辑方法,从而达到对目标对象方法的增强。
如何判断是否执行增强的通知,在执行代理对象的方法时CglibAopProxy调用intercept(Object, Method, Object[], MethodProxy)方法,JDKProxy调用JdkDynamicAopProxy.invoke(Object, Method, Object[])方法,在这两个方法中我们可以看到,在调用目标对象的方法前会先去获取该目标对象方法的拦截器链:
// Get the interception chain for this method.
List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
获取拦截器链为空则直接调用目标对象的方法,拦截器链不为空则执行拦截器链中需要增强的通知方法,这样就达到了AOP动态增强的目标。
拦截器拦截原理
Dispatchservlet在执行dodispatch方法时会根据request查询匹配的HandlerMapping,匹配请求URL的HandlerMapping会去查找所有拦截匹配该请求的拦截器,并将handler和所有的拦截器HandlerInterceptor组成处理执行器链HandlerExecutionChain返回给dispatchservlet。dispatchservlet在执行处理器的方法前先调用所有HandlerInterceptor的preHandle、postHandle、afterCompletion方法。
2.1 静态代理
静态和动态是由代理产生的时间段来决定的。静态代理产生于代码编译阶段,即一旦代码运行就不可变了
2.2 动态代理
那能否通过实现一次代码即可织入到函数中呢,答案当然是可以的,此时就要用到java中的反射机制。它的好处理时可以为我们生成任何一个接口的代理类,并将需要增强的方法织入到任意目标函数。但它仍然具有一个局限性,就是只有实现了接口的类,才能为其实现代理。
2.3 CGLIB
CGLIB解决了动态代理的难题,它通过生成目标类子类的方式来实现来实现代理,而不是接口,规避了接口的局限性。CGLIB是一个强大的高性能代码生成包,其在运行时期(非编译时期)生成被 代理对象的子类,并重写了被代理对象的所有方法,从而作为代理对象。当然CGLIB也具有局限性,对于无法生成子类的类(final类),肯定是没有办法生成代理子类的。
3.在XML中声明切面
步骤
(1)xml文件中配置bean对象(包含增强类和被增强类)
(2)配置aop操作
配置切入点
配置切面
案例:为类Book在实现增加功能add()方法之前,打印一条日志。
<!-- 1.配置对象 -->
<bean id="book" class="com.myaop.Book"></bean>
<bean id="myBook" class="com.myaop.MyBook"></bean>
<!-- 2.配置aop操作 -->
<aop:config>
<!-- 2.1 配置切入点 -->
<aop:pointcut expression="execution(* com.myaop.Book.*(..))" id="pointcut1">
<!-- 2.2 配置切面。把增强用到方法上面 -->
<aop:aspect ref="myBook">
<!-- 配置增强类型。method表示增强类中使用哪个方法作为前置 -->
<aop:before method="before1" pointcut-ref="pointcut1">
</aop:aspect>
</aop:config>
测试类代码:
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Book book = (Book)context.getBean("book");
book.add();
环绕通知实现原理
入参:ProceedingJoinPoint,表示切入点
通过调用方法proceed()实现
//方法之前要做的逻辑
doBefore();
//执行被增强的方法
proceedingJoinPoint.proceed();
//方法之后要做的逻辑
doAfter();
4.使用注解创建切面
基于aspectj的注解实现aop
(1)创建对象
(2)在spring核心配置文件中,开启aop代理
(3)在增强类上使用注解完成aop操作
案例:为类Book在实现增加功能add()方法之前,打印一条日志。
1.创建对象
<!-- 1.配置对象 -->
<bean id="book" class="com.myaop.Book"></bean>
<bean id="myBook" class="com.myaop.MyBook"></bean>
2.开启aop代理
<!-- 2.开启aop操作 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3.在增强类中使用注解完成aop操作
在通知中通过value属性定义切点
@Aspect
public class MyBook{
//在方法上使用注解完成增强配置
@Before(value="execution(* com.myaop.Book.*(..))")
public void before1(){
System.out.println(before........);
}
}
5.Demo实现
参考文章
https://www.jianshu.com/p/503836e398ee
https://www.jianshu.com/p/a1caa1a64393