【重写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
AbstractAutoProxyCreator
作为自动代理的核心类,实现了 InstantiationAwareBeanPostProcessor
接口,拥有了对 Bean 进行处理的能力,同时将 AOP 功能与 IOC 容器联系在一起。
advisedBeans
:缓存所有已处理的 Bean,确保每个对象只被处理一次earlyProxyReferences
:缓存提前创建的代理对象,解决了代理对象的循环依赖问题
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
方法创建代理对象。
//所属类[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
方法可以分为三步,最重要的是第二步,如下所示:
- 前置检查,如果当前对象已经处理过,或者本身是 AOP 相关的组件类,则不处理
- 获取适用于当前对象的拦截器集合,如果不为空,说明需要被代理
- 通过
ProxyFactory
完成代理对象的创建工作
//所属类[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
方法完成的,可以分为三步:
- 从 Spring 容器中找出所有的
Advisor
组件 - 过滤适用于当前类的
Advisor
集合 - 对符合条件的
Advisor
进行排序并返回
//所属类[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
组件添加到候选列表中。
//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
,也就是框架内部使用。
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
方法实现。
//所属类[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
组件,可以应用于所有的对象。
//所属类[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
方法的实现,可以分为三步:
- 检查
ClassFliter
是否匹配当前类,以及MethodMatcher
是否默认适用所有方法。 - 获取当前类实现的所有接口类型,再加上当前类自身,得到一个
Class
集合。在创建代理对象的时候,需要实现目标对象的接口。 - 遍历这个集合中的所有方法,如果
MethodMatcher
匹配了至少一个方法,则认为Advisor
是符合条件的。(JDK 动态代理必须要定义接口,因此可以匹配接口或者实现类上的方法。对于 CGLIB 代理来说,只检查当前类自身的所有方法即可)
//所属类[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
集合等重要信息,这些内容对于每个代理来说都是唯一的。
//所属类[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 框架来创建代理
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 准备工作
本次测试需要的类比较多,首先是 LoggerInterceptor
、LoggerPointcut
和 LoggerAdvisor
三个类提供 AOP 功能的支持,其次是 Logger
注解类起到了标识的作用,最后是 LoggerTarget
作为创建代理的目标类。
Logger
是简单的注解类,声明在方法上,表示当前类需要被代理。
//测试类:日志注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Logger {}
LoggerInterceptor
实现了 MethodInterceptor
接口,增强的逻辑是在执行目标方法前打印日志。
//测试类:日志拦截器
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
注解。
//测试类:日志切点
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
作为增强器。
//测试类:日志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
方法没有声明注解,作为对照组。
//测试类
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
方法,完成创建代理对象的工作。
//测试方法
@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
方法仅作为普通方法调用。这说明代理对象创建成功,并且切面是起作用的。
日志拦截: LoggerTarget 方法:foo
执行foo方法...
执行bar方法...
6. 总结
Spring AOP 代理是实现面向切面编程的基本单元,对于每个有增强需求的对象,都需要创建一个代理对象。问题在于创建代理对象是一个复杂的过程,即使通过代理工厂屏蔽了一部分细节,仍有相当多的工作需要手动完成。鉴于此,Spring 提供了一种标准化的自动创建代理对象的机制,我们可以从三个方面来进行考量。
首先,为了参与对象的创建流程,必须与 BeanFactory
建立关联,这一点是通过 BeanPostProcessor
接口提供的扩展性实现的。
其次,Advisor
组件封装了增强和切面,并对各种增强实现进行统一地适配,其设计的目的就是作为独立实体被 Spring 容器管理。此时,Spring 容器既有增强组件,又有普通对象,且两者之间是多对多的关系。一个对象可以对应多个 Advisor
,一个 Advisor
也可以对应多个对象。也就是说,自动代理的主要工作就是为指定对象寻找符合条件的 Advisor
集合。
第三,Spring 提供了若干自动代理组件,最重要的是 AbstractAutoProxyCreator
,该类定义了三个创建代理的方法,这些方法的调用时机和用途均不相同。本节只讨论了 postProcessAfterInitialization
方法,调用时机是在初始化之后,这是创建代理最主要的途径。
7. 项目信息
新增修改一览,新增(12),修改(1)。
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 (*)
注:+号表示新增、*表示修改
注:项目的 master 分支会跟随教程的进度不断更新,如果想查看某一节的代码,请选择对应小节的分支代码。
欢迎关注公众号【Java编程探微】,加群一起讨论。
原创不易,觉得内容不错请关注、点赞、收藏。
转载自:https://juejin.cn/post/7391694596692295730