你好,我是郭屹,今天我们继续手写MiniSpring,这也是AOP正文部分的最后一节。今天我们将完成一个有模有样的AOP解决方案。
前面,我们已经实现了通过动态代理技术在运行时进行逻辑增强,并引入了Pointcut,实现了代理方法的通配形式。到现在,AOP的功能貌似已经基本实现了,但目前还有一个较大的问题,具体是什么问题呢?我们查看aplicationContext.xml里的这段配置文件来一探究竟。
<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>
看这个配置文件可以发现,在ProxyFactoryBean的配置中,有个Object类型的属性:target。在这里我们的赋值ref是realactionbean,对应Action1这个类。也就是说,给Action1这个Bean动态地插入逻辑,达成AOP的目标。
在这里,一次AOP的配置对应一个目标对象,如果整个系统就只需要为一个对象进行增强操作,这自然没有问题,配置一下倒也不会很麻烦,但在一个稍微有规模的系统中,我们有成百上千的目标对象,在这种情况下一个个地去配置则无异于一场灾难。
一个实用的AOP解决方案,应该可以 用一个简单的匹配规则代理多个目标对象。这是我们这节课需要解决的问题。
在上节课,我们其实处理过类似的问题,就是当时我们的目标方法只能是一个固定的方法名doAction(),我们就提出了Pointcut这个概念,用一个模式来通配方法名,如 do*
、 do*Action
之类的字符串模式。
Pointcut这个概念解决了一个目标对象内部多个方法的匹配问题。这个办法也能给我们灵感,我们就借鉴这个思路,用类似的手段来解决匹配多个目标对象的问题。
因此,我们想象中当解决方案实现之后,应该是这么配置的。
<bean id="genaralProxy" class="GeneralProxy" >
<property type="String" name="pattern" value="action*" />
<property type="String" name="interceptorName" value="advisor" />
</bean>
上面的配置里有一个通用的ProxyBean,它用一个模式串pattern来匹配目标对象,作为例子这里就是 action*
,表示所有名字以action开头的对象都是目标对象。
这个想法好像成立,但是我们知道,IoC容器内部所有的Bean是相互独立且平等的,这个GeneralProxy也就是一个普通的Bean。那么作为一个普通的Bean,它怎么能影响到别的Bean呢?它如何能做到给别的Bean动态创建代理呢?这个办法有这样一个关键的难点。
我们反过来思考,如果能找个办法让这个General Proxy影响到别的Bean,再根据规则决定给这些Bean加上动态代理(这一点我们之前就实现过了),是不是就可以了?
那么在哪个时序点能做这个事情呢?我们再回顾一下Bean的创建过程:第一步,IoC容器扫描配置文件,加载Bean的定义。第二步,通过getBean()这个方法创建Bean实例,这一步又分成几个子步骤:
后三个子步骤,实际上都是在每一个Bean实例创建好之后可以进行的后期处理。那么我们就可以利用这个时序,把自动生成代理这件事情交给后期处理来完成。在我们的IoC容器里,有一个现成的机制,叫 BeanPostProcessor,它能在每一个Bean创建的时候进行后期修饰,也就是上面的3和5两个子步骤其实都是调用的BeanPostProcessor里面的方法。所以现在就比较清晰了,我们考虑用BeanPostProcessor实现自动生成目标对象代理。
创建动态代理的核心是 把传进来的Bean包装成一个ProxyFactoryBean,改头换面变成一个动态的代理,里面包含了真正的业务对象,这一点我们已经在前面的工作中做好了。现在是要自动创建这个动态代理,它的核心就是通过BeanPostProcessor来为每一个Bean自动完成创建动态代理的工作。
我们用一个BeanNameAutoProxyCreator类实现这个功能,顾名思义,这个类就是根据Bean的名字匹配来自动创建动态代理的,你可以看一下相关代码。
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(),跟上一节课的通配方法名一样。所以如果Bean的名称匹配上了,那我们就用和以前创建动态代理一样的办法来自动生成代理。
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(bean);
proxyFactoryBean.setBeanFactory(beanFactory);
proxyFactoryBean.setAopProxyFactory(aopProxyFactory);
proxyFactoryBean.setInterceptorName(interceptorName);
这里我们还是用到了ProxyFactoryBean,跟以前一样,只不过这里是经过了BeanPostProcessor。因此,按照IoC容器的规则,这一切不再是手工的了,而是对每一个符合规则Bean都会这样做一次动态代理,就可以完成我们的工作了。
现在我们只要把这个BeanPostProcessor配置到XML文件里就可以了。
<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都会过一遍手。
工具准备好了,这个BeanPostProcessor会自动创建动态代理。为了使用这个Processor,对应的AbstractBeanFactory类里的getBean()方法需要同步修改。你可以看一下修改后getBean的实现。
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;
}
上述代码中主要修改这一行:
singleton = applyBeanPostProcessorsBeforeInitialization(singleton, beanName);
代码里会调用Processor的postProcessBeforeInitialization方法,并返回singleton。这一段代码的功能是如果这个Bean的名称符合某种规则,就会自动创建Factory Bean,这个Factory Bean里面会包含一个动态代理对象用来返回自定义的实例。
于是,getBean的时候,除了创建Bean实例,还会用BeanPostProcessor进行后期处理,对满足规则的Bean进行包装,改头换面成为一个Factory Bean。
到这里,我们就完成自动创建动态代理的工作了,简单测试一下。
修改applicationContext.xml配置文件,增加一些配置。
<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增加一段代码。
@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,action和action2,每个Bean里面都有doAction()和doSomething()两个方法。
通过配置文件可以看到,在Processor的Pattern配置里,通配 action*
可以匹配所有以action开头的Bean。在Advisor的MappedName配置里,通配 do*
,就可以匹配所有以do开头的方法。
运行一下,就可以看到效果了。这两个Bean里的两个方法都加上了增强,说明系统在调用这些Bean的方法时自动插入了逻辑。
这节课,我们对匹配Bean的办法进行了扩展,使系统可以按照某个规则来匹配某些Bean,这样就不用一个Bean一个Bean地配置动态代理了。
实现的思路是利用Bean的时序,使用一个BeanPostProcessor进行后期处理。这个Processor接收一个模式串,而这个模式也是可以由用户配置在外部文件里的,然后提供isMatch() 方法,支持根据名称进行模式匹配。具体的字符串匹配工作,和上节课一样,也是采用从前到后的扫描技术,分节段进行校验。匹配上之后,还是利用以前的ProxyFactoryBean创建动态代理。这里要理解一点,就是系统会自动把应用程序员配置的业务Bean改头换面,让它变成一个Factory Bean,里面包含的是业务Bean的动态代理。
这个方案能用是因为之前IoC容器里提供的这个BeanPostProcessor机制,所以这里我们再次看到了IoC容器的强大之处。
到这里,我们的AOP方案就完成了。这是基于JDK的方案,对于理解AOP原理很有帮助。
完整源代码参见 https://github.com/YaleGuo/minis。
学完这节课,我也给你留一道思考题。AOP经常用来处理数据库事务,如何用我们现在的AOP架构实现简单的事务处理呢?欢迎你在留言区与我交流讨论,也欢迎你把这节课分享给需要的朋友。我们下节课见!