【重写SpringFramework】第二章aop模块:自动代理上(chapter 2-6)

1. 前言

在前边几节内容中,我们介绍了 Spring AOP 的三大核心,增强、切面和代理,此时已经可以实现一个完整的 AOP 的功能。但仔细想一下,代理对象需要一个个去创建,显得异常繁琐,因此我们需要把创建代理对象的流程自动化。具体思路是,依托 Spring 容器强大的管理能力,在 Bean 创建流程中检查对象是否需要被代理,从而实现自动创建代理的功能。更为重要的是,通过自动代理组件将 AOP 机制与 Spring 容器联系起来,这意味着 Spring 框架的两大基石就此得以确立。

2. 自动代理组件

2.1 继承结构

ProxyConfig 类有两个分支,AdvisedSupport 及其子类实现了创建代理的功能,ProxyProcessorSupport 及其子类则将创建代理的功能整合到 BeanFactory 体系之中。尤其是 AbstractAutoProxyCreator 依赖 ProxyFactory,这说明自动代理的底层仍是通过手动地方式创建代理,手动代理是自动代理的基础

  • AopInfrastructureBean:标记接口,表示一个 AOP 组件类,自身不应该被代理。

  • ProxyProcessorSupport:提供辅助功能,供子类使用。

  • AbstractAutoProxyCreator:自动创建代理的核心类,最大的特点是通过 ProxyFactory 来创建代理对象。

  • AbstractAdvisorAutoProxyCreator:使用 Spring 容器中已有的 Advisor 实例构建拦截器链。

  • InfrastructureAdvisorAutoProxyCreator:排除自定义的 Advisor,使用 Spring 内置的组件。

2.2 AbstractAutoProxyCreator

整理了这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处】即可免费获取

AbstractAutoProxyCreator 作为自动代理的核心类,实现了 InstantiationAwareBeanPostProcessor 接口,拥有了对 Bean 进行处理的能力,同时将 AOP 功能与 IOC 容器联系在一起。

  • advisedBeans:缓存所有已处理的 Bean,确保每个对象只被处理一次
  • earlyProxyReferences:缓存提前创建的代理对象,解决了代理对象的循环依赖问题
 

java

代码解读

复制代码

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements InstantiationAwareBeanPostProcessor, BeanFactoryAware { private BeanFactory beanFactory; private AdvisorAdapterRegistry advisorAdapterRegistry = new DefaultAdvisorAdapterRegistry(); private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256); private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16); }

AbstractAutoProxyCreator 实现了 InstantiationAwareBeanPostProcessor 接口及其父接口 BeanPostProcessor 的三个方法,它们都能用来创建代理对象,区别在于调用时机和用途。

  • postProcessAfterInitialization 方法在初始化之后执行,是创建代理对象的主要方式。

  • getEarlyBeanReference 方法在填充对象之前执行,专门负责处理代理对象的循环依赖。

  • postProcessBeforeInstantiation 方法在实例化之前执行,是一种很特殊的代理方式。

三个方法的调用时机如下图所示,本节我们只讨论 postProcessBeforeInstantiation 方法的实现,也就是初始化后创建代理的流程,另外两种代理方式在下一节介绍。

3. 初始化后创建代理

3.1 主流程

AbstractAutoProxyCreator 实现了 BeanPostProcessor 接口的 postProcessAfterInitialization 方法,该方法在初始化之后执行。此时实例已创建,依赖关系已解析,完成度相当高,正是创建代理对象的最佳时机。postProcessAfterInitialization 方法起到了辅助作用,首先检查 earlyProxyReferences 字段中是否缓存了当前对象,如果没有提前暴露代理对象,则调用 wrapIfNecessary 方法创建代理对象。

 

java

代码解读

复制代码

//所属类[cn.stimd.spring.aop.framework.AbstractAutoProxyCreator] //初始化后回调 @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean != null) { Object cacheKey = StringUtils.hasLength(beanName) ? beanName : bean.getClass(); //检查是否提前创建代理,确保创建代理的操作只会执行一次 if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return null; }

wrapIfNecessary 方法是创建代理的核心流程,如果不需要被代理,则返回当前对象本身。wrapIfNecessary 方法可以分为三步,最重要的是第二步,如下所示:

  1. 前置检查,如果当前对象已经处理过,或者本身是 AOP 相关的组件类,则不处理
  2. 获取适用于当前对象的拦截器集合,如果不为空,说明需要被代理
  3. 通过 ProxyFactory 完成代理对象的创建工作
 

java

代码解读

复制代码

//所属类[cn.stimd.spring.aop.framework.AbstractAutoProxyCreator] //如有必要,创建代理对象 private Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { //1. 前置检查 //1.1 如果目标Bean已经在缓存中,且不需要代理,直接返回 if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } //1.2 如果是AOP相关的组件类,则加入缓存并标记为不处理 Class<?> beanClass = bean.getClass(); if (isInfrastructureClass(beanClass)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } //2. 获取增强器(模板方法,由子类实现) Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != null) { this.advisedBeans.put(cacheKey, Boolean.TRUE); // 3. 创建代理对象 Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } //不需要代理,加入缓存,标记为false this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }

3.2 获取增强器集合

getAdvicesAndAdvisorsForBean 方法由子类 AbstractAdvisorAutoProxyCreator 实现,类名说明了增强器的类型是 Advisor。具体的逻辑是由 findEligibleAdvisors 方法完成的,可以分为三步:

  1. 从 Spring 容器中找出所有的 Advisor 组件
  2. 过滤适用于当前类的 Advisor 集合
  3. 对符合条件的 Advisor 进行排序并返回
 

java

代码解读

复制代码

//所属类[cn.stimd.spring.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator] //获取适用于指定类的所有Advisor集合 @Override protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) { List<Advisor> eligibleAdvisors = findEligibleAdvisors(beanClass); if (eligibleAdvisors.isEmpty()) { return null; } return eligibleAdvisors.toArray(); } protected List<Advisor> findEligibleAdvisors(Class<?> beanClass) { //1. 从BeanFactory寻找候选的Advisor集合 List<Advisor> candidateAdvisors = findCandidateAdvisors(); //2. 过滤出符合当前类的所有Advisor List<Advisor> eligibleAdvisors = AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); //3. 排序 if (!eligibleAdvisors.isEmpty()) { AnnotationAwareOrderComparator.sort(eligibleAdvisors); } return eligibleAdvisors; }

第一步,寻找候选的 Advisor 组件。首先找出 Spring 容器中所有 Advisor 类型的单例,然后遍历这些组件,调用 isEligibleAdvisorBean 方法判断 Advisor 是否符合条件,如果符合条件,则将 Advisor 组件添加到候选列表中。

 

java

代码解读

复制代码

//cn.stimd.spring.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator //查找候选的Advisor protected List<Advisor> findCandidateAdvisors(){ List<String> advisorNames = getBeanFactory().getBeanNamesForType(Advisor.class); List<Advisor> advisors = new LinkedList<>(); for (String name : advisorNames) { if (isEligibleAdvisorBean(name)) { //从容器中获取Advisor组件,并添加到候选列表中 advisors.add(getBeanFactory().getBean(name, Advisor.class)); } } return advisors; }

子类 InfrastructureAdvisorAutoProxyCreator 重写了 isEligibleAdvisorBean 方法,判定逻辑有两个,一是 Spring 容器中存在 Advisor 实例,二是单例的角色是 ROLE_INFRASTRUCTURE,也就是框架内部使用。

 

java

代码解读

复制代码

public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator { @Override protected boolean isEligibleAdvisorBean(String beanName) { ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) getBeanFactory(); return beanFactory.containsBeanDefinition(beanName) && beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE; } }

第二步,过滤出符合指定类的 Advisor 集合。AopUtils 工具类的 findAdvisorsThatCanApply 方法对所有候选的 Advisor 进行检查,判断逻辑由 canApply 方法实现。

 

java

代码解读

复制代码

//所属类[cn.stimd.spring.aop.support.AopUtils] //检索适用于指定类的Advisor列表 public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) { List<Advisor> eligibleAdvisors = new LinkedList<>(); for (Advisor candidate : candidateAdvisors) { if (canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); } } return eligibleAdvisors; }

AopUtils 类有两个重载的 canApply 方法,先来看第一个。canApply 方法将 Advisor 分为切点和引入两种类型分别处理。需要注意的是,如果引入或切点都匹配失败,则默认该 Advisor 是符合条件的。为什么匹配失败还会认为是符合条件的?这是说,当 Advisor 失去了切面的特性,就退化成了 Advice 组件,可以应用于所有的对象。

 

java

代码解读

复制代码

//所属类[cn.stimd.spring.aop.support.AopUtils] //检查Advisor是否可以应用于指定类 private static boolean canApply(Advisor advisor, Class<?> targetClass) { //IntroductionAdvisor略 //PointcutAdvisor if (advisor instanceof PointcutAdvisor) { PointcutAdvisor pa = (PointcutAdvisor) advisor; return canApply(pa.getPointcut(), targetClass); } //Advisor失去切面特性,默认适用于所有类 return true; }

我们不关心引入,来看第二个 canApply 方法的实现,可以分为三步:

  1. 检查 ClassFliter 是否匹配当前类,以及 MethodMatcher 是否默认适用所有方法。
  2. 获取当前类实现的所有接口类型,再加上当前类自身,得到一个 Class 集合。在创建代理对象的时候,需要实现目标对象的接口。
  3. 遍历这个集合中的所有方法,如果 MethodMatcher 匹配了至少一个方法,则认为 Advisor 是符合条件的。(JDK 动态代理必须要定义接口,因此可以匹配接口或者实现类上的方法。对于 CGLIB 代理来说,只检查当前类自身的所有方法即可)
 

java

代码解读

复制代码

//所属类[cn.stimd.spring.aop.support.AopUtils] //检查切点是否匹配指定的类 private static boolean canApply(Pointcut pointcut, Class<?> targetClass){ //1. 检查ClassFilter和MethodMatcher是否匹配 if (!pointcut.getClassFilter().matches(targetClass)) { return false; } MethodMatcher methodMatcher = pointcut.getMethodMatcher(); if(methodMatcher == MethodMatcher.TRUE){ return true; } //2. 获取当前类的所有接口类型 Set<Class<?>> classes = new LinkedHashSet<>(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); classes.add(targetClass); //3. 遍历当前类及接口的所有方法,只要有一个符合条件,则认为PointcutAdvisor适用于当前类 for (Class<?> clazz : classes) { Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz); for (Method method : methods) { if(methodMatcher.matches(method, targetClass)){ return true; } } } return false; }

第三步,由于多个切面可以对同一个方法进行增强,因此有必要确定多个切面的执行顺序。AbstractPointcutAdvisor 类实现了 Ordered 接口,调用 AnnotationAwareOrderComparator 的静态方法 sort 进行排序。

3.3 创建代理对象

在执行完 getAdvicesAndAdvisorsForBean 方法后,返回一个拦截器的集合,如果拦截器集合不为空,说明当前实例应当被代理。这一点非常重要,手动代理只解决了创建代理的问题,哪些对象有资格被代理才是自动的含义所在。

接下来是创建代理对象的工作,由 createProxy 方法完成,实际上重复了手动代理的流程,前边已经详细介绍过了。需要注意的是,对于每个需要代理的对象,都创建了一个代理工厂的的实例。这一点也很好理解,ProxyFactory 的父类 AdvisedSupport 保存了目标对象和 Advisor 集合等重要信息,这些内容对于每个代理来说都是唯一的。

 

java

代码解读

复制代码

//所属类[cn.stimd.spring.aop.framework.AbstractAutoProxyCreator] //创建代理对象 protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); //将AopConfig复制一份给代理对象 //判断目标对象使用JDK代理还是Cglib代理 if (!proxyFactory.isProxyTargetClass()) { evaluateProxyInterfaces(beanClass, proxyFactory); } //将各种Advice转换为统一的Advisor Advisor[] advisors = buildAdvisors(specificInterceptors); for (Advisor advisor : advisors) { proxyFactory.addAdvisor(advisor); } proxyFactory.setTargetSource(targetSource); //使用代理工厂创建代理 return proxyFactory.getProxy(); }

4. 配置自动代理组件

InfrastructureAdvisorAutoProxyCreator 是 Spring 内置的自动代理组件,对于 AOP 功能来说该组件是标配,因此可以将注册和配置的逻辑固定下来方便使用。AopConfigUtils 工具类提供了两个静态方法,如下所示:

  • registerAutoProxyCreatorIfNecessary 方法:注册 InfrastructureAdvisorAutoProxyCreator 组件
  • forceAutoProxyCreatorToUseClassProxying 方法:将 proxyTargetClass 属性设置为 true,也就是通过 CGLIB 框架来创建代理
 

java

代码解读

复制代码

public class AopConfigUtils { public static final String AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator"; //注册AutoProxyCreator组件类 public static void registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) { if (!registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { RootBeanDefinition rbd = new RootBeanDefinition(InfrastructureAdvisorAutoProxyCreator.class); rbd.getPropertyValues().addPropertyValue("order", Ordered.HIGHEST_PRECEDENCE); rbd.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, rbd); } } //强行将proxyTargetClass属性设置为true public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) { if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition definition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); definition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.TRUE); } } }

注:在实际使用中,@EnableAspectJAutoProxy 注解的作用是开启 AOP 功能,@EnableTransactionManagement 注解的作用是开启事务,而事务建立在 AOP 功能的基础之上。这两个注解都是通过 AopConfigUtils 来注册和配置 AOP 组件的。

5. 测试

5.1 准备工作

本次测试需要的类比较多,首先是 LoggerInterceptorLoggerPointcut 和 LoggerAdvisor 三个类提供 AOP 功能的支持,其次是 Logger 注解类起到了标识的作用,最后是 LoggerTarget 作为创建代理的目标类。

Logger 是简单的注解类,声明在方法上,表示当前类需要被代理。

 

java

代码解读

复制代码

//测试类:日志注解 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Logger {}

LoggerInterceptor 实现了 MethodInterceptor 接口,增强的逻辑是在执行目标方法前打印日志。

 

java

代码解读

复制代码

//测试类:日志拦截器 public class LoggerInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("日志拦截: " + invocation.getThis().getClass().getSimpleName() + " 方法:" + invocation.getMethod().getName()); return invocation.proceed(); } }

LoggerPointcut 继承了 StaticMethodMatcherPointcut,说明对方法进行静态匹配。matches 方法实现了匹配逻辑,检查目标方法是否声明了 @Logger 注解。

 

java

代码解读

复制代码

//测试类:日志切点 public class LoggerPointcut extends StaticMethodMatcherPointcut { @Override public boolean matches(Method method, Class<?> targetClass) { AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(method, Logger.class, false, false); return attributes != null; } }

LoggerAdvisor 主要起到了整合的作用,使用 LoggerPointcut 作为切点,使用 LoggerInterceptor 作为增强器。

 

java

代码解读

复制代码

//测试类:日志Advisor public class LoggerAdvisor extends AbstractGenericPointcutAdvisor { private Pointcut pointcut = new LoggerPointcut(); public LoggerAdvisor() { //设置增强器为LoggerInterceptor setAdvice(new LoggerInterceptor()); } @Override public Pointcut getPointcut() { return this.pointcut; } }

LoggerTarget 是普通对象,foo 方法声明了 @Logger 注解,bar 方法没有声明注解,作为对照组。

 

java

代码解读

复制代码

//测试类 public class LoggerTarget { @Logger public void foo(){ System.out.println("执行foo方法..."); } public void bar() { System.out.println("执行bar方法..."); } }

5.2 自动代理

测试方法可以分为三步,第一步注册 Advisor,需要指定角色为 ROLE_INFRASTRUCTURE,否则无法识别。第二步注册自动代理的组件 InfrastructureAdvisorAutoProxyCreator。第三步注册目标对象,当调用 getBean 方法时,会回调 AbstractAutoProxyCreator 的 postProcessAfterInitialization 方法,完成创建代理对象的工作。

 

java

代码解读

复制代码

//测试方法 @Test public void testAutoproxy(){ DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); //注册Advisor RootBeanDefinition definition = new RootBeanDefinition(LoggerAdvisor.class); definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); factory.registerBeanDefinition("loggerAdvisor", definition); //注册自动代理组件 InfrastructureAdvisorAutoProxyCreator processor = new InfrastructureAdvisorAutoProxyCreator(); processor.setBeanFactory(factory); factory.addBeanPostProcessor(processor); //注册目标对象,完成代理流程 factory.registerBeanDefinition("target", new RootBeanDefinition(LoggerTarget.class)); LoggerTarget target = factory.getBean("target", LoggerTarget.class); target.foo(); target.bar(); }

从测试结果可以看到,foo 方法作为增强方法执行了日志拦截的操作,而 bar 方法仅作为普通方法调用。这说明代理对象创建成功,并且切面是起作用的。

 

erlang

代码解读

复制代码

日志拦截: LoggerTarget 方法:foo 执行foo方法... 执行bar方法...

6. 总结

Spring AOP 代理是实现面向切面编程的基本单元,对于每个有增强需求的对象,都需要创建一个代理对象。问题在于创建代理对象是一个复杂的过程,即使通过代理工厂屏蔽了一部分细节,仍有相当多的工作需要手动完成。鉴于此,Spring 提供了一种标准化的自动创建代理对象的机制,我们可以从三个方面来进行考量。

首先,为了参与对象的创建流程,必须与 BeanFactory 建立关联,这一点是通过 BeanPostProcessor 接口提供的扩展性实现的。

其次,Advisor 组件封装了增强和切面,并对各种增强实现进行统一地适配,其设计的目的就是作为独立实体被 Spring 容器管理。此时,Spring 容器既有增强组件,又有普通对象,且两者之间是多对多的关系。一个对象可以对应多个 Advisor,一个 Advisor 也可以对应多个对象。也就是说,自动代理的主要工作就是为指定对象寻找符合条件的 Advisor 集合。

第三,Spring 提供了若干自动代理组件,最重要的是 AbstractAutoProxyCreator,该类定义了三个创建代理的方法,这些方法的调用时机和用途均不相同。本节只讨论了 postProcessAfterInitialization 方法,调用时机是在初始化之后,这是创建代理最主要的途径。

7. 项目信息

新增修改一览,新增(12),修改(1)。

 

scss

代码解读

复制代码

aop └─ src ├─ main │ └─ java │ └─ cn.stimd.springwheel.aop │ ├─ config │ │ └─ AopConfigUtils.java (+) │ ├─ framework │ │ ├─ autoproxy │ │ │ ├─ AbstractAdvisorAutoProxyCreator.java(+) │ │ │ └─ InfrastructureAdvisorAutoProxyCreator.java(+) │ │ ├─ AbstractAutoProxyCreator.java (+) │ │ ├─ AopInfrastructureBean.java (+) │ │ └─ ProxyProcessorSupport.java (+) │ └─ support │ └─ AopUtils.java (+) └─ aop.test └─ proxy ├─ autoproxy │ ├─Logger.java(+) │ ├─LoggerAdvisor.java(+) │ ├─LoggerInterceptor.java(+) │ ├─LoggerPointcut.java(+) │ └─LoggerTarget.java(+) └─ ProxyTest.java (*) 注:+号表示新增、*表示修改

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值