目标:
- 什么是AOP, 什么是AspectJ
- 什么是Spring AOP
- Spring AOP注解版实现原理
- Spring AOP切面原理解析
一. 认识AOP及其使用
详见博文1: 5.1 Spring5源码--Spring AOP源码分析一
二. AOP的特点
2.1 Spring AOP
2.1.1 他是基于动态代理实现的
代码语言:javascript复制Spring 提供了很多的实现AOP的方式:Spring 接口方式,schema配置方式和注解的方式.
如果使用接口方式引入AOP, 就是用JDK提供的动态代理来实现.
如果没有使用接口的方式引入. 那么就是使用CGLIB来实现的.
Spring使用接口方式实现AOP, 下面有详细说明.
研究使用接口方式实现AOP, 目的是为了更好地理解spring使用动态代理实现AOP的两种方式
2.1.2 spring3.2以后, spring-core直接把CGLIB和ASM的源码引入进来了, 所以, 后面我们就不需要再显示的引入这两个依赖了.
2.1.3 Spring AOP依赖于Spring ioc容器来管理
2.1.4 Spring AOP只能作用于bean的方法.
如果某个类, 没有注入到ioc容器中, 那么是不能被增强的
2.1.5 Spring提供了对AspectJ的支持, 但只提供了部分功能的支持: 即AspectJ的切点解析(表达式)和匹配
我们在写切面的时候,经常使用到的@Aspect, @Before, @Pointcut, @After, @AfterReturning, @AfterThrowing等就是AspectJ提供的.
我们知道AspectJ很好用, 效率也很高. 那么为什么Spring不使用AspectJ全套的东西呢? 尤其是AspectJ的静态织入.
先来看看AspectJ有哪些特点
代码语言:javascript复制AspectJ的特点
1. AspectJ属于静态织入. 他是通过修改代码实现的. 它的织入时机有三种
1) Compile-time weaving: 编译期织入. 例如: 类A使用AspectJ增加了一个属性. 类B引用了类A, 这个场景就需要在编译期的时候进行织入, 否则类B就没有办法编译, 会报错.
2) Post-compile weaving: 编译后织入.也就是已经生成了.class文件了, 或者是都已经达成jar包了. 这个时候, 如果我们需要增强, 就要使用到编译后织入
3) Loading-time weaving: 指的是在加载类的时候进行织入.
2. AspectJ实现了对AOP变成完全的解决方案. 他提供了很多Spring AOP所不能实现的功能
3. 由于AspectJ是在实际代码运行前就完成了织入, 因此可以认为他生成的类是没有额外运行开销的.
扩展: 这里为什么没有使用到AspectJ的静态织入呢? 因为如果引入静态织入, 需要使用AspectJ自己的解析器. AspectJ文件是以aj后缀结尾的文件, 这个文件Spring是没有办法, 因此要使用AspectJ自己的解析器进行解析. 这样就增加了Spring的成本.
2.1.6 Spring AOP和AspectJ的比较。由于,Spring AOP基于代理实现. 容器启动时会生成代理对象, 方法调用时会增加栈的深度。使得Spring AOP的性能不如AspectJ好。
三. AOP的配置方式
上面说了Spring AOP和AspectJ. 也说道了AspectJ定义了很多注解, 比如: @Aspect, @Pointcut, @Before, @After等等. 但是, 我们使用Spring AOP是使用纯java代码写的. 也就是说他完全属于Spring, 和AspectJ没有什么关系. Spring只是沿用了AspectJ中的概念. 包括AspectJ提供的jar报的注解. 但是, 并不依赖于AspectJ的功能.
我们使用的@Aspect, @Pointcut, @Before, @After等注解都是来自于AspectJ, 但是其功能的实现是纯Spring AOP自己实现的.
Spring AOP有三种配置方式.
- 第一种: 基于接口方式的配置. 在Spring1.2版本, 提供的是完全基于接口方式实现的
- 第二种: 基于schema-based配置. 在spring2.0以后使用了xml的方式来配置.
- 第三种: 基于注解@Aspect的方式. 这种方式是最简单, 方便的. 这里虽然叫做AspectJ, 但实际上和AspectJ一点关系也没有.
因为我们在平时工作中主要使用的是注解的方式配置AOP, 而注解的方式主要是基于第一种接口的方式实现的. 所以, 我们会重点研究第一种和第三种配置方式.
3.1 基于接口方式的配置. 在Spring1.2版本, 提供的是完全基于接口方式实现的
这种方式是最古老的方式, 但由于spring做了很好的向后兼容, 所以, 现在还是会有很多代码使用这种方式, 比如:声明式事务.
我们要了解这种配置方式还有另一个原因, 就是我们要看源码. 源码里对接口方式的配置进行了兼容处理. 同时, 看源码的入口是从接口方式的配置开始的.
那么, 在没有引入AspectJ的时候, Spring是如何实现AOP的呢? 我们来看一个例子:
1. 定义一个业务逻辑接口类
代码语言:javascript复制package com.lxl.www.aop.interfaceAop;
/**
* 使用接口方式实现AOP, 默认通过JDK的动态代理来实现. 非接口方式, 使用的是cglib实现动态代理
*
* 业务接口类-- 计算器接口类
*
* 定义三个业务逻辑方法
*/
public interface IBaseCalculate {
int add(int numA, int numB);
int sub(int numA, int numB);
int div(int numA, int numB);
int multi(int numA, int numB);
int mod(int numA, int numB);
}
2.定义业务逻辑类
代码语言:javascript复制package com.lxl.www.aop.interfaceAop;//业务类,也是目标对象
import com.lxl.www.aop.Calculate;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
/**
* 业务实现类 -- 基础计算器
*/
public class BaseCalculate implements IBaseCalculate {
@Override
public int add(int numA, int numB) {
System.out.println("执行目标方法: add");
return numA numB;
}
@Override
public int sub(int numA, int numB) {
System.out.println("执行目标方法: sub");
return numA - numB;
}
@Override
public int multi(int numA, int numB) {
System.out.println("执行目标方法: multi");
return numA * numB;
}
@Override
public int div(int numA, int numB) {
System.out.println("执行目标方法: div");
return numA / numB;
}
@Override
public int mod(int numA, int numB) {
System.out.println("执行目标方法: mod");
int retVal = ((Calculate) AopContext.currentProxy()).add(numA, numB);
return retVal % numA;
}
}
3. 定义通知类
前置通知
代码语言:javascript复制package com.lxl.www.aop.interfaceAop;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 定义前置通知
* 实现MethodBeforeAdvice接口
*/
public class BaseBeforeAdvice implements MethodBeforeAdvice {
/**
*
* @param method 切入的方法
* @param args 切入方法的参数
* @param target 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("===========进入beforeAdvice()============");
System.out.println("前置通知--即将进入切入点方法");
System.out.println("===========进入beforeAdvice() 结束============n");
}
}
后置通知
代码语言:javascript复制package com.lxl.www.aop.interfaceAop;
import org.aspectj.lang.annotation.AfterReturning;
import org.springframework.aop.AfterAdvice;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 后置通知
* 实现AfterReturningAdvice接口
*/
public class BaseAfterReturnAdvice implements AfterReturningAdvice {
/**
*
* @param returnValue 切入点执行完方法的返回值,但不能修改
* @param method 切入点方法
* @param args 切入点方法的参数数组
* @param target 目标对象
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("n==========进入afterReturning()===========");
System.out.println("后置通知--切入点方法执行完成");
System.out.println("==========进入afterReturning() 结束=========== ");
}
}
环绕通知
代码语言:javascript复制package com.lxl.www.aop.interfaceAop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 环绕通知
* 实现MethodInterceptor接口
*/
public class BaseAroundAdvice implements MethodInterceptor {
/**
* invocation :连接点
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("===========around环绕通知方法 开始===========");
// 调用目标方法之前执行的动作
System.out.println("环绕通知--调用方法之前: 执行");
// 执行完方法的返回值:调用proceed()方法,就会触发切入点方法执行
Object returnValue = invocation.proceed();
System.out.println("环绕通知--调用方法之后: 执行");
System.out.println("===========around环绕通知方法 结束===========");
return returnValue;
}
}
配置类
代码语言:javascript复制package com.lxl.www.aop.interfaceAop;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;
/**
* 配置类
*/
public class MainConfig {
/**
* 被代理的对象
* @return
*/
@Bean
public IBaseCalculate baseCalculate() {
return new BaseCalculate();
}
/**
* 前置通知
* @return
*/
@Bean
public BaseBeforeAdvice baseBeforeAdvice() {
return new BaseBeforeAdvice();
}
/**
* 后置通知
* @return
*/
@Bean
public BaseAfterReturnAdvice baseAfterReturnAdvice() {
return new BaseAfterReturnAdvice();
}
/**
* 环绕通知
* @return
*/
@Bean
public BaseAroundAdvice baseAroundAdvice() {
return new BaseAroundAdvice();
}
/**
* 使用接口方式, 一次只能给一个类增强, 如果想给多个类增强, 需要定义多个ProxyFactoryBean
* 而且, 曾增强类的粒度是到类级别的. 不能指定对某一个方法增强
* @return
*/
@Bean
public ProxyFactoryBean calculateProxy() {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setInterceptorNames("baseAfterReturnAdvice", "baseBeforeAdvice", "baseAroundAdvice");
proxyFactoryBean.setTarget(baseCalculate());
return proxyFactoryBean;
}
}
之前说过, AOP是依赖ioc的, 必须将其注册为bean才能实现AOP功能
方法入口
代码语言:javascript复制package com.lxl.www.aop.interfaceAop;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class InterfaceMainClass{
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
IBaseCalculate calculate = context.getBean("calculateProxy", IBaseCalculate.class);
System.out.println(calculate.getClass());
calculate.add(1, 3);
}
}
执行结果:
代码语言:javascript复制===========进入beforeAdvice()============
前置通知--即将进入切入点方法
===========进入beforeAdvice() 结束============
===========around环绕通知方法 开始===========
环绕通知--调用方法之前: 执行
执行目标方法: add
环绕通知--调用方法之后: 执行
===========around环绕通知方法 结束===========
==========进入afterReturning()===========
后置通知--切入点方法执行完成
==========进入afterReturning() 结束===========
通过观察, 我们发现, 执行的顺序是: 前置通知-->环绕通知的前置方法 --> 目标逻辑 --> 环绕通知的后置方法 --> 后置通知.
那么到底是先执行前置通知, 还是先执行环绕通知的前置方法呢? 这取决于配置文件的配置顺序
这里,我们将环绕通知放在最后面, 所以, 环绕通知在前置通知之后执行.
代码语言:javascript复制 @Bean
public ProxyFactoryBean calculateProxy() {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setInterceptorNames( "baseAfterReturnAdvice", "baseBeforeAdvice", "baseAroundAdvice");
proxyFactoryBean.setTarget(baseCalculate());
return proxyFactoryBean;
}
那么, 如果我们将环绕通知放在前置通知之前. 就会先执行环绕通知
代码语言:javascript复制 @Bean
public ProxyFactoryBean calculateProxy() {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setInterceptorNames("baseAroundAdvice", "baseAfterReturnAdvice", "baseBeforeAdvice");
proxyFactoryBean.setTarget(baseCalculate());
return proxyFactoryBean;
}
运行结果
代码语言:javascript复制===========around环绕通知方法 开始===========
环绕通知--调用方法之前: 执行
===========进入beforeAdvice()============
前置通知--即将进入切入点方法
===========进入beforeAdvice() 结束============
执行目标方法: add
==========进入afterReturning()===========
后置通知--切入点方法执行完成
==========进入afterReturning() 结束===========
环绕通知--调用方法之后: 执行
===========around环绕通知方法 结束===========
思考: 使用ProxyFactoryBean实现AOP的方式有什么问题?
- 通知加在类级别上, 而不是方法上. 一旦使用这种方式, 那么所有类都会被织入前置通知, 后置通知, 环绕通知. 可有时候我们可能并不想这么做
- 每次只能指定一个类. 也就是类A要实现加日志, 那么创建一个A的ProxyFactoryBean, 类B也要实现同样逻辑的加日志. 但是需要再写一个ProxyFactoryBean.
基于以上两点原因. 我们需要对其进行改善.
下面, 我们来看看, ProxyFactoryBean是如何实现动态代理的?
ProxyFactoryBean是一个工厂bean, 我们知道工厂bean在创建类的时候调用的是getObject(). 下面看一下源码
代码语言:javascript复制public class ProxyFactoryBean extends ProxyCreatorSupport
implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {
......
@Override
@Nullable
public Object getObject() throws BeansException {
/**
* 初始化通知链: 将通知放入链中
* 后面初始化的时候, 是通过责任链的方式调用这些通知链的的.
* 那么什么是责任链呢?
*/
initializeAdvisorChain();
if (isSingleton()) {
/**
* 创建动态代理
*/
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.info("Using non-singleton proxies with singleton targets is often undesirable. "
"Enable prototype proxies by setting the 'targetName' property.");
}
return newPrototypeInstance();
}
}
......
}
发送到initializeAdvisorChain是初始化各类型的Advisor通知, 比如, 我们上面定义的通知有三类: "baseAroundAdvice", "baseAfterReturnAdvice", "baseBeforeAdvice". 这里采用的是责任链调用的方式.
然后调用getSingletonInstance()创建动态代理.
代码语言:javascript复制private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.targetSource = freshTargetSource();
if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
// Rely on AOP infrastructure to tell us what interfaces to proxy.
Class<?> targetClass = getTargetClass();
if (targetClass == null) {
throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
}
setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
}
// Initialize the shared singleton instance.
super.setFrozen(this.freezeProxy);
/**
* 创建动态代理
*/
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}
调用getProxy(CreateAopProxy())调用代理创建动态代理. 我们这里使用接口的方式, 这里调用的是JDKDynamicAopProxy动态代理.
代码语言:javascript复制@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
/**
* 创建动态代理
*/
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
最终, 动态代理创建, 就是在JDKDynamicAopProxy了. 通过执行Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);创建动态代理实例.
其实我们通过ctx.getBean("calculateProxy")获得的类, 就是通过JDKDynamicAopProxy创建的动态代理类.
这里也看出, 为什么每次只能给一个类创建动态代理了.
上面提到了责任链, 那么什么是责任链呢? 如下图所示:
有一条流水线. 比如生产流水线. 里面有许多道工序. 完成工序1 ,才能进行工序2, 依此类推.
流水线上的工人也是各司其职. 工人1做工序1, 工人2做工序2, 工人3做工序3.....这就是一个简单的流水线模型.
工人的责任就是完成每一道工序, 那么所有工人的责任就是完成这条流水线. 这就是工人的责任链.
其实, 我们的通知也是一类责任链. 比如, 前置通知, 环绕通知, 后置通知, 异常通知. 他们的执行都是有顺序的. 一个工序完成, 做另一个工序.各司其职. 这就是责任链.
为了能工统一调度, 我们需要保证, 所有工人使用的都是同一个抽象. 这样, 就可以通过抽象类的调用方式. 各个工人有具体的工作实现.
通知也是如此. 需要有一个抽象的通知类Advicor. 进行统一调用.
结合上面的demo, 来看一个责任链调用的demo.
上面我们定义了两个方法. 一个是前置通知BaseBeforeAdvice 实现了MethodBeforeAdvice, 另一个是环绕通知BaseAroundAdvice 实现了MethodInterceptor. 如果想把这两个通知放在一个链上. 那么他们必须实现相同的接口. 但是, 现在不同.
我们知道环绕通知, 由两部分, 一部分是环绕通知的前置通知, 一部分是环绕通知的后置通知. 所以, 我们可以将前置通知看作是环绕通知的前置通知部分.
代码语言:javascript复制package com.lxl.www.aop.interfaceAop.chainDemo;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 为什么 我们可以让MethodBeforeAdvice 前置通知继承自环绕通知的接口呢?
* 主要原因是, 环绕通知的前半部分, 就是前置通知
*/
public class BeforeAdviceInterceptor implements MethodInterceptor {
// 前置通知
MethodBeforeAdvice methodBeforeAdvice;
public BeforeAdviceInterceptor(MethodBeforeAdvice methodBeforeAdvice) {
this.methodBeforeAdvice = methodBeforeAdvice;
}
/**
* 使用了环绕通知的前半部分. 就是一个前置通知
* @param invocation the method invocation joinpoint
* @return
* @throws Throwable
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
methodBeforeAdvice.before(invocation.getMethod(), invocation.getArguments(), invocation.getClass());
return invocation.proceed();
}
}
这段代码包装了前置通知, 让其扩展为实现MethodInterceptor接口. 这是一个扩展接口的方法.
接下来我们要创建一条链. 这条链就可以理解为流水线上各个工人. 每个工人处理一个工序. 为了能够统一调用. 所有的工人都要实现同一个接口. 责任链的定义如下:
代码语言:javascript复制 /**
* 把一条链上的都初始化
*
* 有一条链, 这条链上都有一个父类接口 MethodInterceptor.
* 也就是说, 链上都已一种类型的工人. 但每种工人的具体实现是不同的. 不同的工人做不同的工作
*
* 这里定义了一个责任链. 连上有两个工人. 一个是前置通知. 一个是环绕通知.
* 前置通知和环绕通知实现的接口是不同的. 为了让他们能够在一条链上工作. 我们自定义了一个MethodBeforeAdviceInterceptor
* 相当于为BaseBeforeAdvice()包装了一层MethodInterceptor
*/
List<MethodInterceptor> list = new ArrayList<>();
list.add(new BeforeAdviceInterceptor(new BaseBeforeAdvice()));
list.add(new BaseAroundAdvice());
这里定义了一个责任链. 连上有两个工人. 一个是前置通知. 一个是环绕通知.
前置通知和环绕通知实现的接口是不同的. 为了让他们能够在一条链上工作. 我们自定义了一个MethodBeforeAdviceInterceptor
相当于为BaseBeforAdvice()包装了一层MethodInterceptor
接下来是责任链的调用.
代码语言:javascript复制/**
* 责任链调用
*/
public static class MyMethodInvocation implements MethodInvocation {
// 这是责任链
protected List<MethodInterceptor> list;
// 目标类
protected final BaseCalculate target;
public MyMethodInvocation(List<MethodInterceptor> list) {
this.list = list;
this.target = new BaseCalculate();
}
int i = 0;
public Object proceed() throws Throwable {
if (i == list.size()) {
/**
* 执行到责任链的最后一环, 执行目标方法
*/
return target.add(2, 2);
}
MethodInterceptor interceptor = list.get(i);
i ;
/**
* 执行责任链调用
* 这个调用链第一环是: 包装后的前置通知
* 调用链的第二环是: 环绕通知.
* 都执行完以后, 执行目标方法.
*/
return interceptor.invoke(this);
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return null;
}
@Override
public Method getMethod() {
try {
return target.getClass().getMethod("add", int.class, int.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
}
@Override
public Object[] getArguments() {
return new Object[0];
}
}
}
这里重点看proceed() 方法. 我们循环获取了list责任链的通知, 然后执行invoke()方法
proceed() 方法是一个链式循环. 刚开始i=0, list(0)是前置通知, 当调用到前置通知的时候, BeforeAdviceInterceptor.invoke()方法, 又调用了invocation.proceed()方法, 回到了MyMethodInvocation.proceed()方法.
然后i=1, list(1)是环绕通知, 当调用环绕通知的时候, 又调用了invocation.proceed(); 有回到了MyMethodInvocation.proceed()方法.
这是已经是list的最后一环了, 后面不会在调用invoke()方法了. 而是执行目标方法. 执行结束以后, 整个调用结束.
这就是一个调用链.
对于责任链有两点:
1. 要有一个统一的调用, 也就是一个共同的抽象类.
2. 使用循环或者递归, 完成责任链的调用
总结:
上面这种方式, 使用的是ProxyFactoryBean 代理bean工厂的方式. 他有两个限制:
代码语言:javascript复制/**
* 使用接口方式, 一次只能给一个类增强, 如果想给多个类增强, 需要定义多个ProxyFactoryBean
* 而且, 曾增强类的粒度是到类级别的. 不能指定对某一个方法增强
* @return
*/
@Bean
public ProxyFactoryBean calculateProxy() {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setInterceptorNames("baseAfterReturnAdvice", "baseBeforeAdvice", "baseAroundAdvice");
proxyFactoryBean.setTarget(baseCalculate());
return proxyFactoryBean;
}
- 一次只能给1个类增强, 如果给多个类增强就需要定义多个ProxyFactoryBean
- 增强的粒度只能到类级别上, 不能指定给某个方法增强.
这样还是有一定的限制.
为了解决能够在类级别上进行增强, Spring引入了Advisor和Pointcut.
Advisor的种类有很多
代码语言:javascript复制RegexpMethodPointcutAdvisor 按正则匹配类
NameMatchMethodPointcutAdvisor 按方法名匹配
DefaultBeanFactoryPointcutAdvisor xml解析的Advisor
InstantiationModelAwarePointcutAdvisorImpl 注解解析的advisor(@Before @After....)
我们使用按方法名的粒度来增强, 所示使用的是NameMatchMethodPointcutAdvisor
代码语言:javascript复制/**
* Advisor 种类很多:
* RegexpMethodPointcutAdvisor 按正则匹配类
* NameMatchMethodPointcutAdvisor 按方法名匹配
* DefaultBeanFactoryPointcutAdvisor xml解析的Advisor
* InstantiationModelAwarePointcutAdvisorImpl 注解解析的advisor(@Before @After....)
*/
@Bean
public NameMatchMethodPointcutAdvisor aspectAdvisor() {
/**
* 通知和通知者的区别:
* 通知(Advice) :是我们的通知类 没有带切点
* 通知者(Advisor):是经过包装后的细粒度控制方式。 带了切点
*/
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
// 切入点增强的通知类型--前置通知
advisor.setAdvice(baseBeforeAdvice());
// 指定切入点方法名--div
advisor.setMappedNames("div");
return advisor;
}
这里设置了切入点需要增强的通知, 和需要切入的方法名.
这样就可以对类中的某个方法进行增强了. 我们依然需要使用ProxyFactoryBean()代理工厂类来进行增强
代码语言:javascript复制 @Bean
public ProxyFactoryBean calculateProxy() {
ProxyFactoryBean userService = new ProxyFactoryBean();
userService.setInterceptorNames("aspectAdvisor");
userService.setTarget(baseCalculate());
return userService;
}
注意, 这里增强的拦截器名称要写刚刚定义的 NameMatchMethodPointcutAdvisor 类型的拦截器.目标类还是我们的基础业务类baseCalculate()
这只是解决了可以对指定方法进行增强. 那么, 如何能够一次对多个类增强呢? Spring又引入了ProxyCreator.
代码语言:javascript复制/**
* Advisor 种类很多:
* RegexpMethodPointcutAdvisor 按正则匹配类
* NameMatchMethodPointcutAdvisor 按方法名匹配
* DefaultBeanFactoryPointcutAdvisor xml解析的Advisor
* InstantiationModelAwarePointcutAdvisorImpl 注解解析的advisor(@Before @After....)
*/
@Bean
public NameMatchMethodPointcutAdvisor aspectAdvisor() {
/*
* 通知和通知者的区别:
* 通知(Advice) :是我们的通知类 没有带切点
* 通知者(Advisor):是经过包装后的细粒度控制方式。 带了切点
*/
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
// 切入点增强的通知类型--前置通知
advisor.setAdvice(baseBeforeAdvice());
// 指定切入点方法名--div
advisor.setMappedNames("div");
return advisor;
}
/**
* autoProxy: BeanPostProcessor 手动指定Advice方式,
* @return
*/
@Bean
public BeanNameAutoProxyCreator autoProxyCreator() {
// 使用bean名字进行匹配
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setBeanNames("base*");
// 设置拦截链的名字
beanNameAutoProxyCreator.setInterceptorNames("aspectAdvisor");
return beanNameAutoProxyCreator;
}
如上代码, beanNameAutoProxyCreator.setBeanNames("base*"); 表示按照名字匹配以base开头的类, 对其使用的拦截器的名称是aspectAdvisor
而aspectAdvisor使用的是按照方法的细粒度进行增强. 这样就实现了, 对以base开头的类, 对其中的某一个或某几个方法进行增强. 使用的增强类是前置通知.
下面修改main方法, 看看运行效果
代码语言:javascript复制public class InterfaceMainClass{
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
IBaseCalculate calculate = context.getBean("baseCalculate", IBaseCalculate.class);
calculate.add(1, 3);
System.out.println("******************");
calculate.div(1, 3);
}
}
这里面执行了两个方法, 一个是add(), 另一个是div(). 看运行结果
代码语言:javascript复制执行目标方法: add
******************
===========进入beforeAdvice()============
前置通知--即将进入切入点方法
===========进入beforeAdvice() 结束============
执行目标方法: div
我们看到, add方法没有被增强, 而div方法被增强了, 增加了前置通知.
以上就是使用接口方式实现AOP. 到最后增加了proxyCreator, 能够根据正则表达式匹配相关的类, 还能够为某一个指定的方法增强. 这其实就是我们现在使用的注解类型AOP的原型了.
3.2 基于注解@Aspect的方式. 这种方式是最简单, 方便的. 这里虽然叫做AspectJ, 但实际上和AspectJ一点关系也没有.
3.2.1 @Aspect切面的解析原理
上面第一种方式详细研究了接口方式AOP的实现原理. 注解方式的AOP, 最后就是将@Aspect 切面类中的@Befor, @After等注解解析成Advisor. 带有@Before类会被解析成一个Advisor, 带有@After方法的类也会被解析成一个Advisor.....其他通知的方法也会被解析成Advisor 在Advisor中定义了增强的逻辑, 也就是@Befor和@After等的逻辑, 以及需要增强的方法, 比如div方法.
下面来分析一下使用注解@Aspect , @Before, @After的实现原理. 上面已经说了, 就是将@Before, @After生成Advisor
这里一共有三个部分.
- 第一部分: 解析@Aspect下带有@Before等的通知方法, 将其解析为Advisor
- 第二部分: 在createBean的时候, 创建动态代理
- 第三部分: 调用. 调用的时候, 执行责任链, 循环里面所有的通知. 最后输出结果.
下面我们按照这三个部分来分析.
第一步: 解析@Aspect下带有@Before等的通知方法, 将其解析为Advisor. 如下图:
第一步是在什么时候执行的呢?
在createBean的时候, 会调用很多PostProcessor后置处理器, 在调用第一个后置处理器的时候执行.
执行的流程大致是: 拿到所有的BeanDefinition,判断类上是不是带有@Aspect注解. 然后去带有@Aspect注解的方法中找@Before, @After, @AfterReturning, @AfterThrowing, 每一个通知都会生成一个Advisor
Advisor包含了增强逻辑, 定义了需要增强的方法. 只不过这里是通过AspectJ的execution的方式进行匹配的.
第二步: 在createBean的时候, 创建动态代理
createBean一共有三个阶段, 具体在哪一个阶段创建的动态代理呢?
其实, 是在最后一个阶段初始化之后, 调用了一个PostProcessor后置处理器, 在这里生成的动态代理
整体流程是:
在createBean的时候, 在初始化完成以后调用bean的后置处理器. 拿到所有的Advisor, 循环遍历Advisor, 然后根据execution中的表达式进行matchs匹配. 和当前创建的这个bean进行匹配, 匹配上了, 就创建动态代理.
pointcut的种类有很多. 上面代码提到过的有:
代码语言:javascript复制/**
* Advisor 种类很多:
* RegexpMethodPointcutAdvisor 按正则表达式的方式匹配类
* NameMatchMethodPointcutAdvisor 按方法名匹配
* DefaultBeanFactoryPointcutAdvisor xml解析的Advisor
* InstantiationModelAwarePointcutAdvisorImpl 注解解析的advisor(@Before @After....)
*/
而我们注解里面是按照execution表达式的方式进行匹配的
第三步: 调用. 调用的时候, 执行责任链, 循环里面所有的通知. 最后输出结果.
调用的类,如果已经生成了动态代理. 那么调用的方法, 就是动态代理生成的方法了.然后拿到所有的advisor, 作为一个责任链调用. 执行各类通知, 最后返回执行结果