在 《Spring AOP 切面开发详解》中演示了Spring中通过切面如何进行横切逻辑的抽取。但是所有的例子,都是通过ProxyFactoryBean创建织入切面的代理,每一个需要被代理的Bean都需要使用一个ProxyFactoryBean进行配置,虽然可以使用父子进行改造,但还是很麻烦。
幸运的是,Spring提供了自动代理机制,让容器自动生成代理,把用户从烦琐的配置工作中解放出来。在内部,Spring使用BeanPostProcessor自动完成这项工作。
1.原理介绍:
这些基于BeanPostProcessor的自动代理创建器的实现类,将根据一些规则自动在容器实例化Bean时为匹配的Bean生成代理实例。这些代理创建器可以分为以下3类
注:虚线箭头表示实现,实线箭头表示继承
-
第一类:基于Bean配置名规则的自动代理创建器。
允许为一组特定配置名的Bean自动创建代理实例。实现类是:BeanNameAutoProxyCreator 。
-
第二类:基于Advisor匹配机制的自动代理创建器。
它会扫描容器中的所有Advisor,自动将这些切面匹配到Bean中。
实现类是:DefaultAdvisorAutoProxyCreator 。之所以能将Advisor自动匹配到Bean,是因为切面Advisor本身就包含了增强和切点信息,能够过滤出Bean.
-
第三类:基于Bean中AspectJ注解标签的自动代理创建器。
实现类是:AnnotationAwareAspectJAutoProxyCreator。
但凡可以通过配置实现,注解往往也可以,而且目前SpringBoot中大量使用注解,所以此种方式是最常用的。
从上面的类图,我们可以清楚地看到所有的自动代理创建器类,都实现了BeanPostProcessor,在容器实例化Bean时,BeanPostProcessor将对它进行加工处理,因此,自动代理创建器有机会对满足匹配规则的Bean自动创建代理对象。
第三类代理创建器是最常用的,我会在下一篇通过一整篇文章详细介绍。而BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator 的使用可以说是AspectJ的基础,原理是一致的,只是后者采用了注解的形式。
2.BeanNameAutoProxyCreator:
我们通过BeanNameAutoProxyCreator 改造上一篇中的切面例子。如果没有看上篇文章,建议先看一下,当然,直接往下看也是没有任何问题的。我们采用了上篇普通方法名匹配切面的例子:
这里为了直观作对比,我先贴出不采用自动代理创建器的配置文件:
<!-- 目标对象配置 -->
<bean id="waiterTarget" class="com.smart.advisor.Waiter" />
<bean id="sellerTarget" class="com.smart.advisor.Seller" />
<!--增强配置-->
<bean id="greetingAdvice" class="com.smart.advisor.GreetingBeforeAdvice" />
<!--切面配置-->
<bean id="greetingAdvisor" class="com.smart.advisor.GreetingAdvisor"
p:advice-ref="greetingAdvice" />
<!--代理类配置-->
<bean id="parent" abstract="true"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="greetingAdvisor" p:proxyTargetClass="true" />
<bean id="waiter" parent="parent" p:target-ref="waiterTarget" />
<bean id="seller" parent="parent" p:target-ref="sellerTarget" />
这里有两个类将会使用到切面,所以代理类的配置已经不少,而且是通过Parent节点简化后的配置。
下面通过BeanNameAutoProxyCreator以更优雅、更便捷的方式完成相同的功能。
<bean id="waiter" class="com.smart.advisor.Waiter" />
<bean id="seller" class="com.smart.advisor.Seller" />
<!--增强配置-->
<bean id="greetingAdvice" class="com.smart.advisor.GreetingBeforeAdvice" />
<!--切面配置-->
<bean id="greetingAdvisor" class="com.smart.advisor.GreetingAdvisor"
p:advice-ref="greetingAdvice" />
<!-- 通过Bean名称自动创建代理 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
p:beanNames="*er" p:interceptorNames="greetingAdvisor"
p:optimize="true"/>
说明:
BeanNameAutoProxyCreator有一个beanNames属性,它允许用户指定一组需要自动代理的Bean名称,Bean名称可以使用*通配符。
当然使用通配符会带来一定的风险,在这个例子中,假设一个其他的Bean名称也以“er”结尾,则自动代理创建器也会为该Bean创建代理。所以为了保险起见,用户可以使用下面的方式配置beanNames属性:value=“waiter,seller”。即:
<bean
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
p:beanNames="waiter,seller"
p:interceptorNames="greetingAdvisor"
p:optimize="true">
</bean>
optimise为true表明强制使用CGLib动态代理技术。
使用到的切面类:
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {
public boolean matches(Method method, Class clazz) {
return "greetTo".equals(method.getName());
}
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class clazz) {
return Waiter.class.isAssignableFrom(clazz);
}
};
}
}
使用到的增强类:
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object obj) throws Throwable {
String clientName = (String) args[0];
System.out.println("How are you!Mr." + clientName + ".");
}
}
测试代码不变:
public class AutoProxyAdvisorTest {
private String configPath ="";
private ApplicationContext ctx =null;
@BeforeClass
public void init(){
configPath = "com/smart/autoproxy/beans.xml";
ctx = new ClassPathXmlApplicationContext(configPath);
}
@Test
public void staticMethod() {
Waiter waiter = (Waiter)ctx.getBean("waiter");
Seller seller = (Seller)ctx.getBean("seller");
waiter.greetTo("John");
waiter.serveTo("John");
seller.greetTo("John");
}
}
打印结果:和预期一致,只有Waiter的greetTo方法被织入增强。
How are you!Mr.John.
waiter greet to John...
waiter serving John...
seller greet to John...
3.DefaultAdvisorAutoProxyCreator:
DefaultAdvisorAutoProxyCreator能够扫描容器中的Advisor,并将Advisor自动织入匹配的目标Bean中,即为匹配的目标Bean自动创建代理。
我们知道切面Advisor是切点和增强的复合体,Advisor本身已经包含了足够的信息:横切逻辑(要织入什么)以及连接点(织入哪里),这是该种方式的底层逻辑。
下面我们通过示例看它如何使用:实际上只需要将配置文件改动一下,其他都不需要变动
<bean id="waiter" class="com.smart.advisor.Waiter" />
<bean id="seller" class="com.smart.advisor.Seller" />
<bean id="greetingAdvice" class="com.smart.advisor.GreetingBeforeAdvice" />
<bean id="greetingAdvisor" class="com.smart.advisor.GreetingAdvisor"
p:advice-ref="greetingAdvice" />
<!-- 通过Bean名称自动创建代理 -->
<!--<bean-->
<!--class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"-->
<!--p:beanNames="waiter,seller"-->
<!--p:interceptorNames="greetingAdvisor"-->
<!--p:optimize="true">-->
<!--</bean>-->
<!--通过Advisor自动创建代理-->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
只需要加入一个org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
就可以扫描容器中的切面,然后给匹配的Bean生成代理。
思考:如果将上面通过名称自动创建代理的代码也放开,会出现什么状况?是同时给符合的Bean创建多个代理吗? 答案是,Spring容器中不允许重复代理,所以放开代码,运行会报错。所以,一般只会使用一种自动生成代理策略。
总结:
Spring AOP可以说是Spring核心中的核心,它贯穿着Spring的整个生态系统,包括目前的SpringBoot等很多地方依然是采用AOP思想来实现。AOP的基本逻辑就是:在使用目标对象时,不是直接使用,而是通过代理间接使用,这是因为,通过代理,让Spring容器有机会在代理中根据配置织入横切逻辑,而这也是AOP的精髓所在。代理类的创建,可以通过配置的方式实现,但是不够优雅,所以我们介绍了自动生成代理器。包括通过配置Bean名称来匹配进行自动创建代理类和通过Advisor自动匹配进行创建两种方式。第三种更为流行的注解方式,下篇文章将详细讨论。