提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 第十二章 将AOP融入Bean生命周期
- 背景
- 目标
- 设计
- 实现
- 代码结构
- 类图
- 实现步骤
- 测试
- 事先准备
- 自定义拦截方法
- Spring.xml 配置AOP
- 测试用例
- 测试结果:
- 总结
第十二章 将AOP融入Bean生命周期
背景
-
在上一章节我们基于 Proxy.newProxyInstance 代理操作中处理方法匹配和方法拦截,对匹配的对象进行自定义的处理操作。并把这样的技术核心内容拆解到 Spring 中,用于实现 AOP 部分,通过拆分后基本可以明确各个类的职责,包括你的代理目标对象属性、拦截器属性、方法匹配属性,以及两种不同的代理操作 JDK 和 CGlib 的方式。
-
基于目前实现的一个 AOP 核心功能,我们可以通过单元测试的方式进行验证切面功能对方法进行拦截。但如果这是一个面向用户使用的功能,就不太可能让用户这么复杂且没有与 Spring 结合的方式单独使用 AOP,虽然可以满足需求,但使用上还是过于分散。
目标
在本章节完成 AOP 核心功能接入Bean生命周期,实现与 Spring 框架的整合,最终能通过 Spring 配置的方式完成切面的操作。
设计
- 其实在有了AOP的核心功能实现后,把这部分功能服务融入到 Spring 其实也不难,只不过要解决几个问题,包括:怎么借着
BeanPostProcessor 把动态代理融入到 Bean 的生命周期
中,以及如何组装各项切点、拦截、前置的功能
和适配对应的代理器
。整体设计结构如下图:
-
对象创建过程中,为了能把xml配置的代理对象、切面相关类对象实例化,这里需要用到BeanPostProcessor提供的方法,可以分别作用于Bean对象执行初始化前后修改Bean对象扩展信息。这里需要基于 BeanPostProcessor 实现新的接口和实现类,这样才能定向获取对应的类信息。
-
创建的代理对象不是普通对象,因此需要前置于其他对象的创建。对应在
AbstractAutowireCapableBeanFactory#createBean
优先完成Bean对象的判断,如果需要代理,则直接返回代理对象。在Spring的源码中会有 createBean 和 doCreateBean 的方法拆分 -
需要解决方法拦截器的具体功能,提供一些 BeforeAdvice、AfterAdvice 的实现,让用户可以更简化的使用切面功能。
-
需要包装切面表达式以及拦截方法的整合,以及提供不同类型的代理方式的代理工厂,来包装我们的切面服务。
实现
代码结构
源码实现:https://github.com/swg209/spring-study/tree/main/step12-aop-spring-bean
类图
- 整个类关系图中可以看到,在以
BeanPostProcessor 接口
实现继承的InstantiationAwareBeanPostProcessor 接口
后,做了一个自动代理创建的类DefaultAdvisorAutoProxyCreator
,这个类的就是用于处理整个 AOP 代理融入到 Bean 生命周期中的核心类。 - DefaultAdvisorAutoProxyCreator 会依赖于
拦截器MethodBeforeAdviceInterceptor
、代理工厂ProxyFacotry
和提供切面、拦截方法和表达式,包装了Pointcut与Advisor的服务AspectJExpressionPointcutAdvisor
。 - Spring 的 AOP 把 Advice 细化了 BeforeAdvice、AfterAdvice、AfterReturningAdvice、ThrowsAdvice,目前我们做的测试案例中只用到了 BeforeAdvice,这部分可以对照 Spring 的源码进行补充测试。
实现步骤
1. 定义Advice拦截器链
BeforeAdvice (前置通知)
public interface BeforeAdvice extends Advice {
}
MethodBeforeAdvice
- 在 Spring 框架中,
Advice 都是通过方法拦截器 MethodInterceptor 实现
的。环绕 Advice 类似一个拦截器的链路,Before Advice、After advice等,不过暂时我们需要那么多就只定义了一个 MethodBeforeAdvice 的接口定义。
public interface MethodBeforeAdvice extends BeforeAdvice {/*** 前置通知.* Callback before a given method is invoked.** @param method* @param args* @param target* @throws Throwable*/void before(Method method, Object[] args, Object target) throws Throwable;
}
2. 定义 Advisor 访问者
Advisor接口定义(顾问)
public interface Advisor {/*** 返回这个切面(Aspect)的通知部分。通知可能是一个拦截器(Interceptor),前置通知(Before Advice),抛出通知(Throws Advice)等** @return the advice that should apply if the pointcut matches.*/Advice getAdvice();
}
PointcutAdvisor
- Advisor 承担了 Pointcut 和 Advice 的组合,Pointcut 用于获取 JoinPoint,而 Advice 决定于 JoinPoint 执行什么操作。
*/
public interface PointcutAdvisor extends Advisor {/*** Get the Pointcut that drives this advisor.*/Pointcut getPointcut();
}
AspectJExpressionPointcutAdvisor 类
- AspectJExpressionPointcutAdvisor 实现了 PointcutAdvisor 接口,把切面 pointcut、拦截方法 advice 和具体的拦截表达式包装在一起。这样就可以在 xml 的配置中定义一个 pointcutAdvisor 切面拦截器了。
public class AspectJExpressionPointcutAdvisor implements PointcutAdvisor {// 切面private AspectJExpressionPointcut pointcut;// 具体的拦截方法private Advice advice;// 表达式private String expression;public void setExpression(String expression) {this.expression = expression;}@Overridepublic Advice getAdvice() {return advice;}public void setAdvice(Advice advice) {this.advice = advice;}@Overridepublic Pointcut getPointcut() {if (null == pointcut) {pointcut = new AspectJExpressionPointcut(expression);}return pointcut;}
}
3. 方法拦截器
- MethodBeforeAdviceInterceptor 实现了 MethodInterceptor 接口,在 invoke 方法中调用 advice 中的 before 方法,传入对应的参数信息。
- 而这个 advice.before 则是用于自己实现 MethodBeforeAdvice 接口后做的相应处理。其实可以看到具体的 MethodInterceptor 实现类,其实和我们之前做的测试是一样的,只不过现在交给了 Spring 来处理
public class MethodBeforeAdviceInterceptor implements MethodInterceptor {// 前置通知 拦截方法private MethodBeforeAdvice advice;public MethodBeforeAdviceInterceptor() {}public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {this.advice = advice;}@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {this.advice.before(methodInvocation.getMethod(), methodInvocation.getArguments(), methodInvocation.getThis());return methodInvocation.proceed();}
}
4. 代理工厂
ProxyFactory
- 其实这个代理工厂主要解决的是关于 JDK 和 Cglib 两种代理的选择问题,有了代理工厂就可以按照不同的创建需求进行控制。
public class ProxyFactory {private AdvisedSupport advisedSupport;public ProxyFactory(AdvisedSupport advisedSupport) {this.advisedSupport = advisedSupport;}public Object getProxy() {return createAopProxy().getProxy();}private AopProxy createAopProxy() {if (advisedSupport.isProxyTargetClass()) {return new Cglib2AopProxy(advisedSupport);}return new JdkDynamicAopProxy(advisedSupport);}}
5. 融入Bean生命周期的自动代理创建者
DefaultAdvisorAutoProxyCreator
- DefaultAdvisorAutoProxyCreator 类的主要核心实现在于
postProcessBeforeInstantiation 方法
中,从通过 beanFactory.getBeansOfType 获取 AspectJExpressionPointcutAdvisor 开始。 - 获取了 advisors 以后就可以遍历相应的 AspectJExpressionPointcutAdvisor 填充对应的属性信息,包括:目标对象、拦截方法、匹配器,之后返回代理对象即可。
- 那么现在调用方获取到的这个 Bean 对象就是一个已经被切面注入的对象了,当调用方法的时候,则会被按需拦截,处理用户需要的信息。
public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {private DefaultListableBeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = (DefaultListableBeanFactory) beanFactory;}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}@Overridepublic Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {if (isInfrastructureClass(beanClass)) {return null;}Collection<AspectJExpressionPointcutAdvisor> advisors = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();for (AspectJExpressionPointcutAdvisor advisor : advisors) {ClassFilter classFilter = advisor.getPointcut().getClassFilter();if (!classFilter.matches(beanClass)) {continue;}AdvisedSupport advisedSupport = new AdvisedSupport();TargetSource targetSource = null;try {targetSource = new TargetSource(beanClass.getDeclaredConstructor().newInstance());} catch (Exception e) {e.printStackTrace();}advisedSupport.setTargetSource(targetSource);advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());advisedSupport.setProxyTargetClass(false);return new ProxyFactory(advisedSupport).getProxy();}return null;}/*** 判断当前类是否为基础设施类: Advice/Pointcut/Advisor** @param beanClass* @return*/private boolean isInfrastructureClass(Class<?> beanClass) {return Advice.class.isAssignableFrom(beanClass)|| Pointcut.class.isAssignableFrom(beanClass)|| Advisor.class.isAssignableFrom(beanClass);}
}
测试
事先准备
IUserService接口.
public interface IUserService {String queryUserInfo();String register(String userName);}
UserService
- 在 UserService 中提供了2个不同方法,另外你还可以增加新的类来加入测试。后面我们的测试过程,会给这个两个方法添加我们的拦截处理,打印方法执行耗时。
public class UserService implements IUserService {@Overridepublic String queryUserInfo() {try {Thread.sleep(new Random(1).nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}return "小苏,111111,广州";}@Overridepublic String register(String userName) {try {Thread.sleep(new Random(1).nextInt(100));} catch (InterruptedException e) {e.printStackTrace();}return "注册用户: " + userName + " success!";}
}
注意pom中要引入额外的依赖。
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.7</version><scope>test</scope></dependency><dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency><!-- https://mvnrepository.com/artifact/aopalliance/aopalliance --><dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.7</version></dependency><!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-cli --><dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-cli</artifactId><version>0.14</version></dependency>
自定义拦截方法
- 与上一章节的拦截方法相比,我们不再是实现 MethodInterceptor 接口,而是实现 MethodBeforeAdvice 环绕拦截。在这个方法中我们可以获取到方法的一些信息,如果还开发了它的 MethodAfterAdvice 则可以两个接口一起实现。
public class UserServiceBeforeAdvice implements MethodBeforeAdvice {@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println("前置拦截方法:" + method.getName());}
}
Spring.xml 配置AOP
- 这回再使用 AOP 就可以像 Spring 中一样,通过在 xml 中配置即可。因为我们已经把 AOP 的功能融合到 Bean 的生命周期里去了,你的新增拦截方法都会被自动处理。
<?xml version="1.0" encoding="UTF-8"?>
<beans><bean id="userService" class="cn.suwg.springframework.test.bean.UserService"/><bean class="cn.suwg.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/><bean id="beforeAdvice" class="cn.suwg.springframework.test.bean.UserServiceBeforeAdvice"/><bean id="methodInterceptor" class="cn.suwg.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor"><property name="advice" ref="beforeAdvice"/></bean><bean id="pointcutAdvisor" class="cn.suwg.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor"><property name="expression" value="execution(* cn.suwg.springframework.test.bean.IUserService.*(..))"/><property name="advice" ref="methodInterceptor"/></bean></beans>
测试用例
- 在单元测试中你只需要按照正常获取和使用 Bean 对象即可,不过这个时候如果被切面拦截了,那么其实你获取到的就是对应的代理对象里面的处理操作了。
public class ApiTest {@Testpublic void testAop() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");IUserService userService = applicationContext.getBean("userService", IUserService.class);System.out.println("测试结果:" + userService.queryUserInfo());}}
测试结果:
测试通过,从日志可以看到,通过代理方式、方法匹配和拦截后,在对应的目标方法下,做了拦截操作进行监控信息打印,内容都可以在控制台完整输出。
总结
-
本章节实现 AOP 功能的外在体现主要是把以前自己在单元测试中的切面拦截,交给 Spring 的 xml 配置了,也就不需要自己手动处理了。那么这里有一个非常重要的知识点,就是把相应的功能如何与 Spring 的 Bean 生命周期结合起来,本章节用到的 BeanPostProcessor,因为它可以解决在 Bean 对象执行初始化方法之前,用于修改新实例化 Bean 对象的扩展点,所以我们也就可以处理自己的 AOP 代理对象逻辑了。
-
一个功能的实现往往包括核心部分、组装部分、链接部分,为了这些各自职责的分工,则需要创建接口和类,由不同关系的继承、实现进行组装。只有明确了各个职责分工,才好灵活的扩展相应的功能逻辑,否则很难驾驭大型系统的开发和建设,也就是那种不好把握的感觉。
-
目前我们实现的 AOP 与 Spring 源码中的核心逻辑是类似的,但更会偏简单一些,也不会考虑更多的复杂场景遇到的问题,包括是否有构造函数、是否为代理中的切面等。其实也可以看出只要是 Java 中的一些特性,都需要在真实使用的 Spring 中进行完整的实现,否则在使用这些功能的时候就会遇到各种问题。
参考书籍:《手写Spring渐进式源码实践》
书籍源代码:https://github.com/fuzhengwei/small-spring