Spring Aop
1、OOP 和 AOP 的对比
- OOP(Object Oriented Programming,面向对象编程:
OOP适合开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能,日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系。这种散布在各处的无关的代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用,于是引出AOP技术来解决这个问题。
- AOP(Aspect Oriented Programming),即面向切面编程:
AOP技术剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
AOP把软件系统分为两个部分:
核心关注点 和 横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似
2、AOP核心概念
- 1、横切关注点 :对哪些方法进行切入,对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
- 2、切面(aspect) 把原来糅杂在业务逻辑代码中的非业务代码抽取出来,(把功能相同的放在一个类中形成一个切面)类是对物体特征的抽象,切面就是对横切关注点的抽象
- 3、连接点(joinpoint)(需要切入的点)被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
- 4、切入点(pointcut) 对连接点进行拦截的定义
- 5、通知(advice) 所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置(finally)、异常、返回、环绕通知五类
- 6、目标对象:代理的目标对象
- 7、织入(weave) 将切面应用到目标对象并导致代理对象创建的过程8、引入(introduction)在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
3、 代码例子:
package com.tuling;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
//后置通知总是会执行,它是在finally块执行
//catch 执行异常通知
@Aspect
@Component
public class TulingLogAspect {
//拦截到 com.tuling.TulingCalculate 这个类下的函数进行织入
@Pointcut("execution(* com.tuling.TulingCalculate.*(..))")
public void pointCut(){};
//@After(value = "pointCut()")
//@AfterReturning(value = "pointCut()",returning = "result")
//@AfterThrowing(value = "pointCut()")
@Before(value = "pointCut()")
public void methodBefore(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
System.out.println("执行目标方法【"+methodName+"】之前执行<前置通知>,入参"+ Arrays.asList(joinPoint.getArgs()));
}
}
//需要在这个类的方法执行前 执行上面Aspect的方法。
package com.tuling;
import org.springframework.aop.framework.AopContext;
public class TulingCalculate {
//add方法
public int add(int numA, int numB) {
System.out.println("执行目标方法:add");
//System.out.println(1/0);
return numA+numB;
}
//mod方法内调用了add方法
public int mod(int numA,int numB){
System.out.println("执行目标方法:mod");
//这种写法就可以触发mod方法的切面 和add方法的切面
int retVal = ((Calculate) AopContext.currentProxy()).add(numA,numB);
//只会触发mod方法的切面,add方法的切面没有触发
//int retVal = this.add(numA,numB);
return retVal%numA;
}
}
//测试类
package com.tuling;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.HashMap;
public class TulingMainClass {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);
Calculate calculate = (Calculate) ctx.getBean("calculate");
int retVal = calculate.add(3,6);
//int retVal = calculate.mod(2,4);
System.out.println("运算结果是:"+retVal);
}
}
执行结果:
注意:如果按照下图这样写代码,当测试类执行mod方法时,mod里面调用add方法,但是执行add方法之前没有去执行切面的代码。
注意:如果按照下图这样写代码,会执行add方法之前的切面代码。
4、注解 @EnableAspectJAutoProxy 帮我们做了什么
-
1、 @EnableAspectJAutoProxy=====> 导入了AnnotationAwareAspectJAutoProxyCreator组件
-
2、我们分析AnnotationAwareAspectJAutoProxyCreator 的继承关系图,发现了他具有InstantiationAwareBeanPostProcessor 的特性 和 BeanPostProcessor 接口特性
-
3、我们发现InstantiationAwareBeanPostProcessor 在实例化之前(调用构造方法之前)执行before方法
5、InstantiationAwareBeanPostProcessor 的before做了啥
- 在创建第一个bean 根据Bean的生命周期中的createBean的环节 resolveBeforeInstantiation 触发我的InstantiationAwareBeanPostProcessor的before方法.
此时在这个环节就会去把我们的切面信息(@AspectJ)的信息找出来 然后 进行缓存,缓存内容如下图:
6、BeanPostProcessor 的 after 做了啥
创建要切的对象的时候,根据切面类名去找自己的切面(增强器),然后把被切的对象 和增强器 创建成一个代理对象
7、代理对象怎么调用我们方法
代理对象调用:(默认jdk代理 )@proxyTragetClass为true 则为cglib代理
通过职责链模式+递归的来进行调用
- 1、先执行我们的异常通知…(catch里面执行异常通知的方法)
- 2、返回通知:(由于正是在这里返回通知中没有进行任何的try catch处理,那么加入抛出异常 就不会执 - 行返回通知的方法而是直接执行到异常通知了)
- 3、后置通知:正是因为在后置通知中,代码在finally里中 所以他才是总是被执行的…
- 4、前置通知:执行我们的前置通知.
- 5、递归终止条件满足:返回去的时候逆序执行我们目标方法,就是前置,后置,返回,异常