上一篇文章介绍了SpringAOP的基础知识以及所依赖的底层Java技术,本篇来说下SpringAOP的增强类型以及切面类型。
AOP联盟为增强定义了org.aopaliance.aop.Advice
接口,下图为增强接口继承关系图:
带《spring》标识的接口是Spring所定义的扩展增强接口;带《aopalliance》标识的接口则是AOP联盟定义的接口。按照增强在目标类方法的连接点位置,可以分为以下5类:
org.springframework.aop.BeforeAdvice
代表前置增强,因为Spring只支持方法级别的增强,所以MethodBeforeAdvice是目前可用的前置增强,表示在方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定义的;org.springframework.aop.AfterReturningAdvice
代表后增强,表示在目标方法执行后实施增强;org.springframework.intercept.MethodInterceptor
代表环绕增强,表示在目标方法执行前后实施增强;org.springframework.aop.ThrowsAdvice
代表抛出异常增强,表示在目标方法抛出异常后实施增强;org.springframework.aop.IntroductionInterceptor
代表引介增强,表示在目标类中添加一些新的方法和属性。在了解了基础信息后,下面就来分别看下各种增强的用法。通过使用 “保证服务生使用礼貌用语的例子” 来说明各种增强的使用方式。
服务生接口
package com.hhxs.bbt.advice;
public interface Waiter {
/**
* 欢迎顾客
* @param name
*/
void greetTo(String name);
/**
* 对顾客提供服务
* @param name
*/
void serveTo(String name);
}
现在,来看一个未参加过培训的服务生的服务情况:
package com.hhxs.bbt.advice;
import org.springframework.stereotype.Component;
@Component
public class NaiveWaiter implements Waiter {
@Override
public void greetTo(String name) {
System.out.println("greet to " + name + "...");
}
@Override
public void serveTo(String name) {
System.out.println("serving " + name + "...");
}
}
NaiveWaiter只是简单的向顾客打招呼,直接提供服务。下面,我们使用前置增强对NaiveWaiter的服务进行规范,让他们在提供服务之前,必须先对顾客使用礼貌用语。
package com.hhxs.bbt.advice;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
@Component
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
// 在目标类方法调用前执行
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
String clientName = (String)args[0];
System.out.println("How are you! Mr." + clientName + ".");
}
}
上述代码中,我们看到了MethodBeforeAdvice接口的唯一方法before(Method method, Object[] args, Object obj) throws Throwable
,method为目标类的方法;args为目标类方法的入参;而obj为目标类实例。当该方法发生异常时,将阻止目标类方法的执行。
下面编写测试例子,看下具体的执行情况:
package com.hhxs.bbt.advice;
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
public class TestBeforeAdvice {
public static void main(String[] args) {
Waiter waiter = new NaiveWaiter();
BeforeAdvice advice = new GreetingBeforeAdvice();
// Spring提供的代理工厂
ProxyFactory pf = new ProxyFactory();
// 设置代理目标
pf.setTarget(waiter);
// 为代理目标添加增强
pf.addAdvice(advice);
// 生成代理实例
Waiter proxy = (Waiter) pf.getProxy();
proxy.greetTo("张三");
proxy.serveTo("李四");
}
}
运行上面的代码,控制台输出信息如下,我们可以看到,通过前置增强在方法前面引入了礼貌用语。
How are you! Mr.张三.
greet to 张三...
How are you! Mr.李四.
serving 李四...
解剖ProxyFactory
在TestBeforeAdvice中,我们使用org.springframework.aop.framework.ProxyFactory
代理工厂将GreetingBeforeAdvice的增强织入到目标类NaiveWaiter中。有没有发现,调用方式与上一节介绍的JDK代理和CgLib代理很相似?不错,ProxyFactory的内部就是使用了JDK代理或CGLib技术。下图为AopProxy类结构:
其中,Cglib2AopProxy使用CGLib代理技术创建代理,而JdkDynamicAopProxy使用JDK代理技术创建代理。如果通过ProxyFactory的setInterfaces(Class[] interfaces)
指定针对接口进行代理,ProxyFactory就使用JdkDynamicAopProxy;如果针对类的代理,则使用Cglib2AopProxy。此外,还可以通过ProxyFactory的setOptimize(true)
方法,让ProxyFactory启动优化代理方式,这样,针对接口的代理也会使用CglibAopProxy。需要注意的是,用户可以通过调用ProxyFactory的addAdvice(Advice)
方法添加多个增强,增强的调用顺序和添加顺序一致。
在Spring中配置
虽然使用ProxyFactory比直接使用CGLib或JDK代理技术创建代理省事很多,但是正如大家预想的一样,还可以通过Sping配置的方式来声明代理。
// spring1.xml代码片段
greetingBeforeAdvice
package com.hhxs.bbt.advice;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeforeAdviceClient {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring/advice/spring1.xml");
Waiter waiter = (Waiter) context.getBean("waiter");
waiter.greetTo("张三");
waiter.serveTo("李四");
}
}
下面介绍下ProxyFactoryBean的几个常用的可配置属性:
org.aopalliance.intercept.MethodInterceptor
或org.springframework.aop.Advisor
的Bean,配置中的顺序对应调用的顺序;后置增强在目标类方法调用后执行,接着上面的例子,增加服务后用语。
import org.springframework.aop.AfterReturningAdvice;
public class GreetingAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("Please enjoy yourself!");
}
}
其中returnValue
为目标实例方法返回的结果;method
为目标类的方法;args
为目标实例的方法的入参;而target
为目标类实例。
// spring2.xml代码片段
...
greetingBeforeAdvice
greetingAfterAdvice
...
public class AfterAdviceClient {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring/advice/spring2.xml");
Waiter waiter = (Waiter) context.getBean("waiter");
waiter.greetTo("张三");
waiter.serveTo("李四");
}
}
运行上面的代码,控制台输出信息如下:
How are you! Mr.张三.
greet to 张三...
Please enjoy yourself!
How are you! Mr.李四.
serving 李四...
Please enjoy yourself!
环绕增强允许在目标类方法调用前后织入横切逻辑,综合实现了前置、后置增强两者的功能。下面我们使用环绕增强实现上面的功能。
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class GreetingInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取目标方法入参
Object[] args = invocation.getArguments();
String clientName = (String) args[0];
System.out.println("How are you! Mr." + clientName + ".");
// 通过反射机制调用目标方法
Object obj = invocation.proceed();
System.out.println("Please enjoy yourself!");
return obj;
}
}
Spring直接使用AOP联盟所定义的 MethodInterceptor 作为环绕增强接口。该接口唯一的接口方法invoke(MethodInvocation invocation) throws Throwable
,其中 MethodInvocation 不但封装目标方法及入参数组,还封装了目标方法所在的实例对象。
// spring3.xml代码片段
...
greetingInterceptor
...
public class AroundAdviceClient {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring/advice/spring3.xml");
Waiter waiter = (Waiter) context.getBean("waiter");
waiter.greetTo("张三");
waiter.serveTo("李四");
}
}
异常抛出增强最合适的应用场景是事务管理,当参与事务的某个Dao发生异常时,事务管理器就必须回滚事务。
ThrowsAdvice异常抛出增强接口没有定义任何方法,它是一个 标识接口,在运行期Spring使用反射的机制自行判断,但必须采用以下签名形式定义异常抛出的增强方法:void afterThrowing([Method method, Object[] args, Object target], Throwable)
,方法名必须为 afterThrowing,方法入参规定:前三个入参Method method
, Object[] args
, Object target
要么都提供,要么都不提供,而最后一个入参是Throwable或其子类。
标识接口是没有任何方法和属性的接口,标识接口不对实现类有任何语义上的要求,仅仅表明它的实现类属于一个特定的类型。它非常类似Web2.0中TAG的概念,Java使用它标识某一类对象。主要有两个用途:第一,通过标识接口标识同一类型的类,这些类本身可能并没有具有相同的方法,如Advice接口;第二,通过标识接口使程序或JVM采取一些特殊处理,如
java.io.Serializable
,它告诉JVM对象可以被序列化。
引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的。
Spring定义了引介增强接口 IntroductionInterceptor,该接口没有定义任何的方法,Spring为该接口提供了 DelegatingIntroductionInterceptor实现类,一般情况下,我们通过扩展该实现类定义自己的引介增强类。下面通过对上一篇文章SpringAOP基础里的性能监视例子进行改造,实现业务类性能监视功能的激活和关闭。
public interface Monitorable {
void setMonitorActive(boolean active);
}
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
public class ControllablePerformanceMonitor extends DelegatingIntroductionInterceptor implements Monitorable {
private static final long serialVersionUID = 1L;
private ThreadLocal monitorStatusMap = new ThreadLocal();
public Object invoke(MethodInvocation mi) throws Throwable {
Object obj = null;
if(monitorStatusMap.get() != null && monitorStatusMap.get()) {
PerformanceMonitor.begin(mi.getClass().getName() + "." + mi.getMethod().getName());
obj = super.invoke(mi);
PerformanceMonitor.end();
} else {
obj = super.invoke(mi);
}
return obj;
}
@Override
public void setMonitorActive(boolean active) {
monitorStatusMap.set(active);
}
}
// beans.xml代码片段
p:target-ref="forumServiceTarget"
p:interceptorNames="pmonitor"
p:proxyTargetClass="true" />
public class TestIntroduce {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/hhxs/bbt/introduce/beans.xml");
ForumService forumService = (ForumService) ctx.getBean("forumService");
forumService.removeForum(10);
forumService.removeTopic(1024);
Monitorable monitorable = (Monitorable) forumService;
monitorable.setMonitorActive(true);
forumService.removeForum(10);
forumService.removeTopic(1024);
}
}
通过前面的学习,我们知道增强只提供了连接点的方位信息:如织入到方法前面,后面等,而切点进一步描述了织入到哪些类的哪些方法上。
Spring通过org.springframework.aop.Pointcut
接口描述切点,Pointcut 由 ClassFilter 和 MethodMatcher 构成,它通过 ClassFilter 定位到某些特定类上,通过 MethodMatcher 定位到某些特定方法上。
Spring使用org.springframework.aop.Advisor
接口表示切面的概念,一个切面同时包含横切代码和连接点信息。切面可以分为三类:一般切面、切点切面和引介切面。
** 下面看下PointcutAdvisor的6个具体实现类:**
下面我们通过实例来了解下具体用法:
@Component
public class Waiter {
public void greetTo(String name) {
System.out.println("waiter greet to " + name + "...");
}
public void serverTo(String name) {
System.out.println("waiter serving " + name + "...");
}
}
@Component
public class Seller {
public void greetTo(String name) {
System.out.println("seller greet to " + name + "...");
}
}
Seller拥有一个和Waiter相同名称的方法greetTo()。现在希望通过 StaticMethodMatcherPointcutAdvisor 定义一个切面,在Waiter#greetTo()方法调用前织入一个增强。
// 切面类代码
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {
private static final long serialVersionUID = 1L;
// 切点方法匹配规则:方法名为greetTo
public boolean matches(Method method, Class> targetClass) {
return "greetTo".equals(method.getName());
}
// 切点类匹配规则:为Waiter的类或子类
@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class> clazz) {
return Waiter.class.isAssignableFrom(clazz);
}
};
}
}
因为 StaticMethodMatcherPointcutAdvisor 抽象类唯一需要定义的是 matches() 方法。默认情况下匹配所有类,我们可以通过覆盖 getClassFilter() 方法,让它仅匹配Waiter类及其子类。
// 增强类
@Component
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "." + method.getName());
String clientName = (String) args[0];
System.out.println("How are you! Mr. " + clientName + ".");
}
}
// beans.xml 配置切面: 静态方法匹配切面
...
...
StaticMethodMatcherPointcutAdvisor 除了advice属性外,还可以定义另外两个属性:
public class TestStaticMethodAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/hhxs/bbt/advisor/beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter_c");
Seller seller = (Seller) ctx.getBean("seller_c");
waiter.greetTo("John");
waiter.serverTo("John");
seller.greetTo("John");
}
}
运行以上代码,输出一下信息:
com.hhxs.bbt.advisor.Waiter.greetTo
How are you! Mr. John.
waiter greet to John...
waiter serving John...
seller greet to John...
在 StaticMethodMatcherPointcutAdvisor 中,我们仅能通过方法名定义切点,这种描述方式不够灵活。假设目标类中有多个方法,且它们都满足一定的命名规范,若能使用正则表达式进行匹配描述就灵活的多了。RegexpMethodPointcutAdvisor 是正则表达式方法匹配的切面实现类,需要注意的是:匹配模式串匹配的是目标类方法的全限定名,即带类名的方法名。下面我们用这个实现类对上面的实例进行改写:
...
<context:component-scan base-package="com.hhxs.bbt.advisor" />
<bean id="greetingAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="greetingBeforeAdvice">
<property name="patterns">
<list>
<value>.*greet.*value>
list>
property>
bean>
<bean id="parent" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="greetingAdvisor"
p:proxyTargetClass="true" />
<bean id="waiter_c" parent="parent" p:target-ref="waiter"/>
<bean id="seller_c" parent="parent" p:target-ref="seller"/>
...
运行以上代码,输出一下信息:
com.hhxs.bbt.advisor.Waiter.greetTo
How are you! Mr. John.
waiter greet to John...
waiter serving John...
com.hhxs.bbt.advisor.Seller.greetTo
How are you! Mr. John.
seller greet to John...
PS: 除了例子中所使用的 patterns 和 advice 两属性外,还有另外两个属性:
在低版本中,Spring提供了用于创建动态切面的 DynamicMethodMatcherPointcutAdvisor 抽象类,因为该类在功能上和其他类有重叠,目前已过期。我们可以使用 DefaultPointcutAdvisor 和 DynamicMethodMatcherPointcut 来完成相同的功能。下面直接看例子:
@Component
public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut {
private static List specialClientList = new ArrayList();
static {
specialClientList.add("John");
specialClientList.add("Tom");
}
@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class> clazz) {
System.out.println("调用getClassFilter()对" + clazz.getName() + "做静态检查.");
return Waiter.class.isAssignableFrom(clazz);
}
};
}
@Override
public boolean matches(Method method, Class> targetClass) {
System.out.println("调用matches(method, targetClass)" + targetClass.getName() + "." + method.getName() + "做静态检查.");
return "greetTo".equals(method.getName());
}
@Override
public boolean matches(Method method, Class> targetClass, Object[] args) {
System.out.println("调用matches(method, targetClass)" + targetClass.getName() + "." + method.getName() + "做动态检查.");
String clientName = (String) args[0];
return specialClientList.contains(clientName);
}
}
GreetingDynamicPointcut 类既有静态切点检查的方法,也有用于动态切点检查的方法。由于动态切点检查会对性能造成很大的影响,应当尽量避免在运行时每次都对目标类的各个方法进行动态检查。Spring采用这样的机制:在创建代理时对目标类的每个连接点使用静态切点检查,如果仅通过静态切点检查就可以知道连接点是不匹配的,则在运行时就不再进行动态检查了;如果静态切点检查是匹配的,在运行时才进行动态切点检查。
// dynamic_beans.xml 代码片段
...
...
// 动态切面测试代码
public class TestDynamicAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/hhxs/bbt/advisor/dynamic_beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter2");
waiter.serverTo("Peter");
waiter.greetTo("Peter");
waiter.serverTo("John");
waiter.greetTo("John");
}
运行以上代码,在控制台输出一下信息:
// Spring在创建代理织入切面时,对目标类中所有方法进行静态切点检查
调用getClassFilter()对com.hhxs.bbt.advisor.Waiter做静态检查.
调用matches(method, targetClass)com.hhxs.bbt.advisor.Waiter.serverTo做静态检查.
调用getClassFilter()对com.hhxs.bbt.advisor.Waiter做静态检查.
调用matches(method, targetClass)com.hhxs.bbt.advisor.Waiter.greetTo做静态检查.
调用getClassFilter()对com.hhxs.bbt.advisor.Waiter做静态检查.
调用matches(method, targetClass)com.hhxs.bbt.advisor.Waiter.toString做静态检查.
调用getClassFilter()对com.hhxs.bbt.advisor.Waiter做静态检查.
调用matches(method, targetClass)com.hhxs.bbt.advisor.Waiter.clone做静态检查.
// 对应waiter.serverTo(“Peter”): 第一次调用serverTo()方法时,执行静态
调用getClassFilter()对com.hhxs.bbt.advisor.Waiter做静态检查.
调用matches(method, targetClass)com.hhxs.bbt.advisor.Waiter.serverTo做静态检查.
waiter serving Peter...
// 对应waiter.greetTo(“Peter”) : 第一次调用greetTo()时,执行静态动态检查
调用getClassFilter()对com.hhxs.bbt.advisor.Waiter做静态检查.
调用matches(method, targetClass)com.hhxs.bbt.advisor.Waiter.greetTo做静态检查.
调用matches(method, targetClass)com.hhxs.bbt.advisor.Waiter.greetTo做动态检查.
waiter greet to Peter...
// 对应waiter.serverTo("John") :第二次调用serverTo()时,不在执行静态
waiter serving John...
// 对应waiter.greetTo("John") :第二次调用greetTo()时,只执行动态检查
调用matches(method, targetClass)com.hhxs.bbt.advisor.Waiter.greetTo做动态检查.
com.hhxs.bbt.advisor.Waiter.greetTo
How are you! Mr. John.
waiter greet to John...
在定义切点时,切勿忘记同时覆盖getClassFilter()
和matches(Method method, Class targetClass)
方法,通过静态切点检查可以排除掉大部分方法。
最后要说明的是,在Spring中,不管是静态切面还是动态切面都是通过动态代理技术实现的。所谓静态切面是指在生成代理对象时,就确定了增强是否需要织入到目标类连接点上,而动态切面是指必须在运行期根据方法入参的值来判断增强是否织入到目标类连接点上。
Spring的流程切面由 DefaultPointcutAdvisor 和 ControlFlowPointcut 实现。流程切点代表由某个方法直接或间接发起调用的其他方法。来看下面的实例,我们通过一个WaiterDelegat类代理Waiter所有的方法:
public class WaiterDelegate {
private Waiter waiter;
public void service(String clientName) {
waiter.greetTo(clientName);
waiter.serverTo(clientName);
}
public void setWaiter(Waiter waiter) {
this.waiter = waiter;
}
}
假设我们需要实现所有由WaiterDelegate#service()方法发起调用的其他方法都织入GreetingBeforeAdvice增强,就必须使用流程切面来完成目标。
// controlflow_beans.xml 代码片段
...
...
ControlFlowPointcut有两个构造函数,分别是ControlFlowPointcut(Class clazz)
和ControlFlowPointcut(Class clazz, String methodName)
。第一个构造函数指定一个类作为流程切点;第二个构造函数指定一个类和某一个方法作为流程切点。
在这里,我们指定WaiterDelegate#service()
方法作为切点,表示所有通过该方法直接或间接发起的调用匹配切点。
public class TestControlFlowAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/hhxs/bbt/advisor/controlflow_beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter3");
WaiterDelegate wd = new WaiterDelegate();
wd.setWaiter(waiter);
waiter.serverTo("John");
waiter.greetTo("John");
wd.service("John");
}
}
waiter serving John...
waiter greet to John...
com.hhxs.bbt.advisor.Waiter.greetTo
How are you! Mr. John.
waiter greet to John...
com.hhxs.bbt.advisor.Waiter.serverTo
How are you! Mr. John.
waiter serving John...
对于流程切面来说,代理对象在每次调用目标类方法时,都需要判断方法调用堆栈中是否满足流程切点的要求。因此,和动态切面一样,流程切面对性能的影响也很大。
在前面的例子中,我们所定义的切面仅有一个切点,有时,一个切点可能难以描述目标连接点的信息。比如上面流程切点的例子中,假如我们希望由WaiterDelegate#service()
发起调用且被调用的方法是Waiter#greetTo()
时才织入增强,这个切点就是复合切点。Spring为我们提供了 ComposablePointCut,可以将多个切点以并集或交集的方式组合起来,提供起点之间复合运算功能。
下面,我们通过 ComposablePointcut 创建一个流程切点和方法名切点的相交切点,代码如下:
@Component
public class GreetingComposablePointcut {
public Pointcut getIntersectionPointcut() {
// 创建一个复合切点
ComposablePointcut cp = new ComposablePointcut();
// 创建一个流程切点
Pointcut pt1 = new ControlFlowPointcut(WaiterDelegate.class,"service");
// 创建一个方法名切点
NameMatchMethodPointcut pt2 = new NameMatchMethodPointcut();
pt2.addMethodName("greetTo");
return cp.intersection(pt1).intersection((Pointcut)pt2);
}
}
// composable_beans.xml 代码片段
...
...
public class TestComposableAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/hhxs/bbt/advisor/composable_beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter4");
WaiterDelegate wd = new WaiterDelegate();
wd.setWaiter(waiter);
waiter.serverTo("Peter");
waiter.greetTo("Peter");
wd.service("Peter");
}
}
运行以上代码,在控制台输出一下信息:
waiter serving Peter...
waiter greet to Peter...
com.hhxs.bbt.advisor.Waiter.greetTo
How are you! Mr. Peter.
waiter greet to Peter...
waiter serving Peter...
引介切面是引介增强的封装器,通过引介切面,我们更容易为现有对象添加任何接口的实现。IntroductionInfo 描述了目标类需要实现的新接口。IntroductionAdvisor 有两个实现类,分别是 DefaultIntroductionAdvisor 和 DeclareParentsAdvisor,前者是引介切面最常用的实现类,而后者是Spring2.0新添的实现类,它用于实现使用AspectJ语言的DeclareParent注解表示的引介切面。
DefulatIntroductionAdvisor拥有三个构造函数:
getInterfaces()
表示。下面,我们通过DefaultIntroductionAdvisor为 前面的引介增强示例 配置切面:
// introduce_beans.xml 代码片段
...
<bean id="pmonitor" class="com.hhxs.bbt.introduce.ControllablePerformanceMonitor" />
<bean id="forumServiceTarget" class="com.hhxs.bbt.introduce.ForumService" />
<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interfaces="com.hhxs.bbt.introduce.Monitorable"
p:target-ref="forumServiceTarget"
p:interceptorNames="pmonitor"
p:proxyTargetClass="true" />
...
public class TestIntroduceAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/hhxs/bbt/advisor/introduce_beans.xml");
ForumService forumService = (ForumService) ctx.getBean("forumService");
forumService.removeForum(10);
forumService.removeTopic(1024);
Monitorable monitorable = (Monitorable) forumService;
monitorable.setMonitorActive(true);
forumService.removeForum(10);
forumService.removeTopic(1024);
}
}
在前面的例子中,可以看出,每一个需要被代理的 Bean 都需要使用一个 ProxyFactoryBean 进行配置,为每一个目标类手工配置一个切面是比较繁琐的。幸运的是,Spring为我们提供了自动代理机制,让容器为我们自动生成代理。
Spring使用 BeanPostProcessor 来完成这项工作。基于 BeanPostProcessor 的自动代理创建器的实现类,将根据一些规则自动在容器实例化Bean时为匹配的Bean生成代理实例。这些代理创建器可以分为以下三类:
在静态正则表达式方法匹配切面中,通过配置两个ProxyFactoryBean分别为waiter和seller的Bean创建代理对象。下面,我们通过BeanNameAutoProxyCreator来完成相同功能:
...
...
BeanNameAutoProxyCreator有一个beanNames属性,它允许用户指定一组需要自动代理Bean名称,Bean名称可以使用*
通配符。当然使用通配符会带来一定风险,在上面的例子中,假设一个其他的Bean名称以“er”结尾,则自动代理创建器也会为该Bean创建代理。所以,保险的方式是直接使用beanid,如value=“waiter,seller”。
public class TestBeanNameAutoProxyCreator {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/hhxs/bbt/autoproxy/beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter");
Seller seller = (Seller) ctx.getBean("seller");
waiter.greetTo("John");
waiter.serverTo("John");
seller.greetTo("John");
}
}
运行以上代码,在控制台输出一下信息:
com.hhxs.bbt.advisor.Waiter.greetTo
How are you! Mr. John.
waiter greet to John...
com.hhxs.bbt.advisor.Waiter.serverTo
How are you! Mr. John.
waiter serving John...
com.hhxs.bbt.advisor.Seller.greetTo
How are you! Mr. John.
seller greet to John...
DefaultAdvisorAutoProxyCreator能够扫描容器中的Advisor,并将Advisor自动织入到匹配的目标Bean中,即为匹配的目标Bean自动创建代理。
在静态方法匹配切面中,我们通过ProxyFactoryBean为waiter配置了代理,在这里,我们引入DefaultAdvisorAutoProxyCreator为容器中所有带“greet”方法名的目标Bean自动创建代理:
// beans.xml代码片段
...
...
public class TestBeanNameAutoProxyCreator {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/hhxs/bbt/autoproxy/beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter");
Seller seller = (Seller) ctx.getBean("seller");
waiter.greetTo("John");
waiter.serverTo("John");
seller.greetTo("John");
}
}
运行以上代码,在控制台输出一下信息:
com.hhxs.bbt.advisor.Waiter.greetTo
How are you! Mr. John.
waiter greet to John...
waiter serving John...
com.hhxs.bbt.advisor.Seller.greetTo
How are you! Mr. John.
seller greet to John...