20 | AutoProxyCreator:如何自动添加动态代理?

你好,我是郭屹,今天我们继续手写MiniSpring,这也是AOP正文部分的最后一节。今天我们将完成一个有模有样的AOP解决方案。

问题的引出

前面,我们已经实现了通过动态代理技术在运行时进行逻辑增强,并引入了Pointcut,实现了代理方法的通配形式。到现在,AOP的功能貌似已经基本实现了,但目前还有一个较大的问题,具体是什么问题呢?我们查看applicationContext.xml里的这段配置文件来一探究竟。

1
2
3
4
5
<bean id="realaction" class="com.test.service.Action1" />
<bean id="action" class="com.minis.aop.ProxyFactoryBean">
    <property type="String" name="interceptorName" value="advisor" />
    <property type="java.lang.Object" name="target" ref="realaction"/>
</bean>

AOP配置文件分析

在分析配置文件时,我们注意到ProxyFactoryBean中有一个Object类型的属性target,其值通过ref属性指向realactionbean,即Action1类。这意味着我们可以为Action1这个Bean动态地插入逻辑,实现AOP的目标。

AOP配置的挑战

虽然为单个对象配置AOP逻辑并不复杂,但在一个大型系统中,如果需要为成百上千的对象进行增强操作,逐个配置将变得非常繁琐。因此,我们需要一个能够用简单的匹配规则代理多个目标对象的AOP解决方案。

匹配多个目标对象的思路

在之前的课程中,我们已经探讨了如何通过Pointcut概念来匹配一个目标对象内部的多个方法,例如使用模式do*do*Action等。这种模式匹配方法解决了目标对象内部方法匹配的问题,也为我们提供了解决匹配多个目标对象问题的灵感。

解决方案设想

我们期望实现的解决方案应该能够通过配置,使得一个AOP配置能够匹配并代理多个目标对象。具体配置方式如下:

1
2
3
4
<bean id="genaralProxy" class="GeneralProxy" >
    <property type="String" name="pattern" value="action*" />
    <property type="String" name="interceptorName" value="advisor" />
</bean>

利用BeanPostProcessor自动创建代理

在Spring框架中,IoC容器中的所有Bean都是相互独立且平等的。然而,我们可以通过特定机制让一个普通的Bean(例如GeneralProxy)影响到其他Bean,并根据规则给这些Bean动态地创建代理。

Bean创建过程回顾

当我们通过IoC容器创建一个Bean时,这个过程可以分解为以下几个步骤:

  1. IoC容器扫描配置文件并加载Bean定义。
  2. 当调用getBean()方法时,开始创建Bean实例,这一步骤包括:
  • 创建Bean的基本实例(毛坯实例)。

  • 填充Properties属性。

  • 执行postProcessBeforeInitialization方法。

  • 调用初始化方法(如果配置了init-method)。

  • 执行postProcessAfterInitialization方法。

自动创建代理的关键点

上述过程中,postProcessBeforeInitializationpostProcessAfterInitialization这两个阶段是进行后期处理的理想时机。Spring提供了一个特殊的接口BeanPostProcessor,它允许我们在Bean生命周期的这两个关键时刻对Bean进行修改或增强。

使用BeanNameAutoProxyCreator

为了实现根据Bean的名字匹配来自动创建动态代理的功能,我们可以创建一个名为BeanNameAutoProxyCreator的类。这个类将实现BeanPostProcessor接口,并利用模式串(如action*)来匹配目标Bean,然后为匹配到的Bean生成代理。

实现思路

  • BeanNameAutoProxyCreator 类需要实现 BeanPostProcessor 接口。
  • postProcessBeforeInitializationpostProcessAfterInitialization 方法中,检查当前Bean的名字是否符合预设的模式串。
  • 如果匹配成功,则使用ProxyFactoryBean或其他代理创建工具为该Bean创建一个代理对象。
  • 返回创建好的代理对象替换原始Bean,以便后续操作都针对代理对象进行。 通过这种方式,我们可以确保只有符合条件的Bean才会被包装成具有额外行为(如事务管理、日志记录等)的代理对象,从而达到AOP编程的目的。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.minis.aop.framework.autoproxy;
public class BeanNameAutoProxyCreator implements BeanPostProcessor{
    String pattern; //代理对象名称模式,如action*
    private BeanFactory beanFactory;
    private AopProxyFactory aopProxyFactory;
    private String interceptorName;
    private PointcutAdvisor advisor;
    public BeanNameAutoProxyCreator() {
        this.aopProxyFactory = new DefaultAopProxyFactory();
    }
    //核心方法。在bean实例化之后,init-method调用之前执行这个步骤。
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (isMatch(beanName, this.pattern)) {
            ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(); //创建以恶ProxyFactoryBean
            proxyFactoryBean.setTarget(bean);
            proxyFactoryBean.setBeanFactory(beanFactory);
            proxyFactoryBean.setAopProxyFactory(aopProxyFactory);
            proxyFactoryBean.setInterceptorName(interceptorName);
            return proxyFactoryBean;
        }
        else {
            return bean;
        }
    }
    protected boolean isMatch(String beanName, String mappedName) {
        return PatternMatchUtils.simpleMatch(mappedName, beanName);
    }
}

postProcessBeforeInitialization方法中,我们首先会判断Bean的名称是否与给定的规则相匹配。这一过程是通过调用isMatch(beanName, this.pattern)来实现的。进一步探究后可以发现,这里的isMatch()方法实际上是对PatternMatchUtils.simpleMatch()方法的直接调用,这与我们在上一节课中学到的通配符模式匹配机制是一致的。 aaaaaaa 如果经过上述匹配过程,确定了某个Bean的名字符合我们的预设模式,接下来的操作就会创建一个针对该Bean的动态代理对象。这种做法允许我们为匹配到的特定Beans附加额外的行为或逻辑,而无需改变它们原有的实现细节。 整个流程如下:

  1. 在Spring容器初始化Bean之前,postProcessBeforeInitialization方法被触发。
  2. 使用PatternMatchUtils.simpleMatch()检查当前处理中的Bean名字是否满足指定模式。

当发现Bean名确实符合要求时,就采取类似于以往课程中介绍过的步骤来自动生成一个代理对象,以此来扩展或修改原Bean的功能。

1
2
3
4
5
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(bean);
proxyFactoryBean.setBeanFactory(beanFactory);
proxyFactoryBean.setAopProxyFactory(aopProxyFactory);
proxyFactoryBean.setInterceptorName(interceptorName);

在Spring框架中,我们经常使用ProxyFactoryBean来创建动态代理,以实现AOP(面向切面编程)。以下是使用BeanPostProcessor自动化这一过程的步骤:

  1. 理解ProxyFactoryBeanProxyFactoryBean是Spring提供的一个工厂Bean,用于创建动态代理对象。

  2. BeanPostProcessor的作用BeanPostProcessor是一个接口,它允许我们在Spring容器初始化Bean之后,执行自定义的逻辑。通过实现这个接口,我们可以对每个Bean进行动态代理。

  3. 自动化动态代理:将BeanPostProcessor应用到ProxyFactoryBean,Spring容器会为每个符合规则的Bean自动创建动态代理,从而实现AOP功能。

  4. 配置XML文件:要使上述自动化过程生效,需要将BeanPostProcessor配置到Spring的XML配置文件中。这样,Spring容器就会自动为我们处理动态代理的创建。 通过这种方式,我们不再需要手动为每个Bean创建代理,Spring容器会为我们自动完成这一工作。

1
2
3
4
<bean id="autoProxyCreator" class="com.minis.aop.framework.autoproxy.BeanNameAutoProxyCreator" >
    <property type="String" name="pattern" value="action*" />
    <property type="String" name="interceptorName" value="advisor" />
</bean>

IoC容器扫描配置文件的时候,会把所有的BeanPostProcessor对象加载到Factory中生效,每一个Bean都会过一遍手。

getBean方法的修改

工具准备好了,这个BeanPostProcessor会自动创建动态代理。为了使用这个Processor,对应的AbstractBeanFactory类里的getBean()方法需要同步修改。你可以看一下修改后getBean的实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    public Object getBean(String beanName) throws BeansException{
      Object singleton = this.getSingleton(beanName);
      if (singleton == null) {
         singleton = this.earlySingletonObjects.get(beanName);
         if (singleton == null) {
            BeanDefinition bd = beanDefinitionMap.get(beanName);
            if (bd != null) {
               singleton=createBean(bd);
               this.registerBean(beanName, singleton);
               if (singleton instanceof BeanFactoryAware) {
                  ((BeanFactoryAware) singleton).setBeanFactory(this);
               }
               //用beanpostprocessor进行后期处理
               //step 1 : postProcessBeforeInitialization调用processor相关方法
               singleton = applyBeanPostProcessorsBeforeInitialization(singleton, beanName);
               //step 2 : init-method
               if (bd.getInitMethodName() != null && !bd.getInitMethodName().equals("")) {
                  invokeInitMethod(bd, singleton);
               }
               //step 3 : postProcessAfterInitialization
               applyBeanPostProcessorsAfterInitialization(singleton, beanName);
               this.removeSingleton(beanName);
               this.registerBean(beanName, singleton);
            }
            else {
               return null;
            }
         }
      }
      else {
      }
      //process Factory Bean
      if (singleton instanceof FactoryBean) {
         return this.getObjectForBeanInstance(singleton, beanName);
      }
      else {
      }
      return singleton;
   }

上述代码中主要修改这一行:

1
singleton = applyBeanPostProcessorsBeforeInitialization(singleton, beanName);

代码里会调用Processor的postProcessBeforeInitialization方法,并返回singleton。这一段代码的功能是如果这个Bean的名称符合某种规则,就会自动创建Factory Bean,这个Factory Bean里面会包含一个动态代理对象用来返回自定义的实例。于是,getBean的时候,除了创建Bean实例,还会用BeanPostProcessor进行后期处理,对满足规则的Bean进行包装,改头换面成为一个Factory Bean。到这里,我们就完成自动创建动态代理的工作了,简单测试一下。修改applicationContext.xml配置文件,增加一些配置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<bean id="autoProxyCreator" class="com.minis.aop.framework.autoproxy.BeanNameAutoProxyCreator" >
    <property type="String" name="pattern" value="action*" />
    <property type="String" name="interceptorName" value="advisor" />
</bean>

<bean id="action" class="com.test.service.Action1" />
<bean id="action2" class="com.test.service.Action2" />

<bena id="beforeAdvice" class="com.test.service.MyBeforeAdvice" />
<bean id="advisor" class="com.minis.aop.NameMatchMethodPointcutAdvisor">
    <property type="com.minis.aop.Advice" name="advice" ref="beforeAdvice"/>
    <property type="String" name="mappedName" value="do*"/>
</bean>

这里我们配置了两个Bean,BeanPostProcessor和Advisor。

相应地,controller层的HelloWorldBean增加一段代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Autowired
IAction action;

@RequestMapping("/testaop")
public void doTestAop(HttpServletRequest request, HttpServletResponse response) {
	action.doAction();
}
@RequestMapping("/testaop2")
public void doTestAop2(HttpServletRequest request, HttpServletResponse response) {
	action.doSomething();
}

@Autowired
IAction action2;

@RequestMapping("/testaop3")
public void doTestAop3(HttpServletRequest request, HttpServletResponse response) {
	action2.doAction();
}
@RequestMapping("/testaop4")
public void doTestAop4(HttpServletRequest request, HttpServletResponse response) {
	action2.doSomething();
}

在这节课中,我们探讨了如何通过配置文件中的模式匹配来动态地为一组Bean创建代理,并对这些Bean的方法进行增强。以下是本课内容的总结:

Bean与方法的匹配

  • 我们有两个Bean:actionaction2,每个都拥有doAction()doSomething()两个方法。

  • 在Processor的Pattern配置里,使用action*可以匹配所有以action开头的Bean。

  • Advisor的MappedName配置里,利用do*来匹配所有以do开头的方法。

动态代理的实现

  • 当系统运行时,根据上述配置,会自动为匹配到的Bean及其方法添加额外逻辑。

  • 这是通过BeanPostProcessor机制,在Bean初始化后但还未被使用前,对其进行后期处理实现的。

  • BeanPostProcessor接收一个模式字符串,该模式由用户在外部配置文件中指定。

  • isMatch()方法用于基于名称进行模式匹配,匹配成功后,ProxyFactoryBean将用来创建动态代理。

  • 最终,应用程序员配置的业务Bean会被IoC容器转换成包含动态代理的Factory Bean。

IoC容器的作用

  • 本方案之所以可行,是因为IoC容器提供的BeanPostProcessor机制。

  • 它展示了IoC容器的强大功能,能够灵活地管理对象的生命周期以及它们之间的依赖关系。

AOP架构完成

  • 至此,基于JDK的AOP解决方案已经构建完毕。

  • 此方案有助于深入理解AOP的基本原理。

  • 更多详细信息及源代码,请参考GitHub上的minis项目

课后思考题

  • AOP常用于数据库事务处理。请考虑如何运用当前所学的AOP架构实现简单的事务管理。

  • 欢迎大家在评论区讨论你的想法,并分享给需要的朋友。