6.1 AOP 概述
6.1.1 AOP是什么
- AOP 是面向切面编程,将多个类中的重复代码抽离并定义一个公共的抽象类,通过继承的方式进行对该逻辑从复用。
- 主要适用于横切逻辑应用的场合;如:性能监测、访问控制、事务管理以及日志记录
6.1.2 AOP术语
连接点(Joinpoint)
- 程序执行的某个特定位置:如类开始初始前、后;某个方法调用前、后;方法抛出异常后。具有边界性质的特定点。
- 链接点有两个信息确定:第一是用方法表示的程序执行点;第二是相对点表示的方位。
切点(Pointcut)
- 定位到某一个链接点。通过Spring AOP的规则,解析引擎负责解析切点所设定的查询条件,找到对应的链接点。
- 链接点是方法执行前、后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以要定位到具体链接点上,还需要提供方位信息。
增强(Advice)
- 织入到目标类链接点上的一段程序代码,和链接点相关的信息执行点的方位。(结合执行点方位信息和切点信息,我们就可以找到特定的链接点。)
- Spring 提供的增强接口都是带有方位名:BeforeAdvice、AfterRetuningAdvice、ThrowsAdvice等。即只有结合切点和增强两者才能确定特定的链接点并实施增强逻辑。
目标对象(Target)
- 增强逻辑的织入目标类。如性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。
引入(Introduction)
- 是一种特殊的增强,它为类添加一些属性和方法。即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态的为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
织入(Weaving)
- 是将增强添加到对目标类具体连接点上的过程,AOP有三种织入的方法
- 编译期织入,这要求使用特殊的Java编译器;
- 类装载期织入,这要求使用特殊的类装载器;
- 动态代理织入,在运行期为目标类添加增强生成子类的方式。
- Spring 采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
代理(Proxy)
- 一个类被AOP织入增强后,就产生一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类即可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方法调用代理类。
切面(Aspect)
- 由切点和增强组成,它即包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
AOP 的工作重心在于如何将增强应用到目标对象的连接点上,其包含两个工作:
- 如何通过切点和增强定位到连接点上
- 如何在增强中编写切面的代码
6.1.3 AOP的实现者
AspectJ
- AspectJ语言级的AOP实现
- 能够在编译期提供横切代码的织入,所以它有一个专门的编译器用来生成遵守Java字节码规范的Class文件。
AspectWerkz
- 基于Java的简单、动态、轻量级的AOP框架
- 它支持运行期或类装载期织入横切代码,所以它拥有一个特殊的类装载器。
JBossAOP
- 应用程序服务器框架的扩展功能发布。
SpringAOP
- Spring AOP 使用纯Java实现,不需要专门的编译过程、类装载器,它在运行期通过代理方式向目标类注入增强代码。
6.2 基础知识
6.2.1 带有横切逻辑的实例
某些方法需要性能监视,就必须调整方法代码,在方法体前后分别添加上开启性能监视和结束性能监视的代码,而这些代码破坏了ForumServiceImpl业务逻辑的纯粹性。我们希望通过代理的方式,将业务类方法中开启和结束性能监视的这些横切代码从业务类中完全移除。并通过JDK 动态代理技术或GCLib动态代理技术将横切代码织入到目标方法的相应位置。
- ForumServiceImpl 包含性能监视横切代码
package com.baobaotao.proxy;
public class ForumServiceImpl implements ForumService{
@Override
public void removeTopic(int topicId) {
//@1 开始对该方法进行性能监视
PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeTopic");
System.out.println("模拟删除Topic记录:" + topicId);
try {
Thread.currentThread().sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException();
}
//@2 结束对该方法进行性能监视
PerformanceMonitor.end();
}
@Override
public void removeForum(int forumId) {
//@1 开始对该方法进行性能监视
PerformanceMonitor.begin("com.baobaotao.proxy.ForumServiceImpl.removeForum");
System.out.println("模拟删除Topic记录:" + forumId);
try {
Thread.currentThread().sleep(40);
} catch (InterruptedException e) {
throw new RuntimeException();
}
//@2 结束对该方法进行性能监视
PerformanceMonitor.end();
}
}
- PerformanceMonitor
package com.baobaotao.proxy;
public class PerformanceMonitor {
//@1 通过一个 ThreadLocal 保存调用线程相关的性能监视信息
private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();
//@2 启动对某一目标方法的性能监视
public static void begin(String method) {
System.out.println("begin monitor...");
MethodPerformance mp = new MethodPerformance(method);
performanceRecord.set(mp);
}
public static void end() {
System.out.println("end monitor...");
MethodPerformance mp = performanceRecord.get();
//@3 打印出方法性能监视的结果信息
mp.printPerformance();
}
}
- MethodPerformance
package com.baobaotao.proxy;
public class MethodPerformance {
private long begin;
private long end;
private String serviceMethod;
public MethodPerformance(String serviceMethod) {
this.serviceMethod = serviceMethod;
this.begin = System.currentTimeMillis();//记录目标类方法开始执行点的系统时间
}
public void printPerformance() {
end = System.currentTimeMillis();//获取目标类方法执行完成后系统系统时间,并进而计算出目标类方法执行时间
long elapse= end - begin;
System.out.println(serviceMethod + "花费" + elapse + "毫秒。");
}
}
- 测试代码
package com.baobaotao.proxy;
public class TestForumService {
public static void main(String[] args) throws InterruptedException {
ForumService forumService = new ForumServiceImpl();
forumService.removeForum(10);
forumService.removeTopic(1012);
}
}
begin monitor...
模拟删除Topic记录:10
end monitor...
com.baobaotao.proxy.ForumServiceImpl.removeForum花费42毫秒。
begin monitor...
模拟删除Topic记录:1012
end monitor...
com.baobaotao.proxy.ForumServiceImpl.removeTopic花费21毫秒。
6.2.2 JDK动态代理
- JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
- ForumServiceImpl 抽出横切性能监控代码
package com.baobaotao.proxy;
public class ForumServiceImpl implements ForumService{
@Override
public void removeTopic(int topicId) {
System.out.println("模拟删除Topic记录:" + topicId);
try {
Thread.currentThread().sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
@Override
public void removeForum(int forumId) {
System.out.println("模拟删除Topic记录:" + forumId);
try {
Thread.currentThread().sleep(40);
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
}
- PerformanceHandler 增强的安置接口
package com.baobaotao.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PerformanceHandler implements InvocationHandler{//@1 实现InvocationHandler
private Object target;
public PerformanceHandler(Object target) {//@1 target为目标的业务类
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
PerformanceMonitor.begin(target.getClass().getName()+"."+method.getName());
Object obj = method.invoke(target, args);//通过反射方法调用业务类的目标方法
PerformanceMonitor.end();
return obj;
}
}
- 测试代码
package com.baobaotao.proxy;
import java.lang.reflect.Proxy;
public class TestForumService {
public static void main(String[] args) throws InterruptedException {
//@1 希望被代理的目标业务类
ForumService target = new ForumServiceImpl();
//@2 根据编织了目标业务类逻辑和性能监测横切逻辑的 InvocationHandler实例创建代理实例
PerformanceHandler handler = new PerformanceHandler(target);
/**
* @3
* 第一个参数:类加载器
* 第二个参数:创建代理实例所需要实现的一组接口
* 第三个参数:整合了业务逻辑和横切逻辑的编织器对象
*/
ForumService proxy = (ForumService)Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler);
proxy.removeForum(10);
proxy.removeTopic(1012);
}
}
begin monitor...
模拟删除Topic记录:10
end monitor...
com.baobaotao.proxy.ForumServiceImpl.removeForum花费42毫秒。
begin monitor...
模拟删除Topic记录:1012
end monitor...
com.baobaotao.proxy.ForumServiceImpl.removeTopic花费21毫秒。
6.2.3 CGLib动态代理
使用JDK创建代理有一个限制,即它只能为接口创建代理实例,newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 方法传递第二个值需传递接口列表;
而CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。
- CglibProxy
package com.baobaotao.proxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor{
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);// 设置需要创建子类的类
enhancer.setCallback(this);
return enhancer.create();// 通过字节码技术动态创建子类实例
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
PerformanceMonitor.begin(obj.getClass().getName()+"."+method.getName());
Object result = proxy.invokeSuper(obj, args);
PerformanceMonitor.end();
return result;
}
}
- 测试 Cglib创建的代理类
package com.baobaotao.proxy;
public class TestForumService {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);
forumService.removeForum(10);
forumService.removeTopic(1023);
}
}
begin monitor...
模拟删除Topic记录:10
end monitor...
com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$c01368d4.removeForum花费51毫秒。
begin monitor...
模拟删除Topic记录:1023
end monitor...
com.baobaotao.proxy.ForumServiceImpl$$EnhancerByCGLIB$$c01368d4.removeTopic花费21毫秒。
6.2.4 AOP联盟
- 是众多开源AOP项目的联合组织,为制定一套规范描述AOP的标准,定义标准的AOP接口,以便各种遵守标准的具体实现可以相互调用。
6.2.5 代理知识小结
- Spring AOP的底层就是使用JDK动态代理或CGLib动态代理技术为目标 Bean 织入横切逻辑。
- 通过PerformanceHandler 或 CglibProxy 实现了性能监视横切逻辑的动态织入,但存在三个需改进的地方
- 1、目标类的所有方法都添加了性能监视横切逻辑,而有时,我们只期望对业务类中某些特定的方法添加横切逻辑;
- 2、我们通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码;
- 3、我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的程序代码,无法做到通用
- Spring 的主要工作就是围绕以上三点展开:Spring AOP 通过 Pointcut(切点)指定在哪些类的哪些方法织入横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等)。此外,Spring 通过 Advisor(切面)将Porintcut 和 Advice 两者组装起来。有了Advisor的信息,Spring 就可以利用JDK或CGLib的动态代理技术采用统一的方式为目标 Bean 创建织入切面的代理对象了。
6.3 创建增强类
- Spring 使用增强类定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括了在方法的哪一点加入横切代码的方位信息,所以增强既包含横切逻辑,还包含部分连接点信息
6.3.1 增强类型
- 前置增强:org.springframework.aop.BeforeAdvice 代表前置增强,因为Spring只支持方法级的增强,所以MethodBeforeAdvice是目标可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定义的;
- 后置增强:org.springframework.aop.AfterReturningAdvice 代表后置增强,表示在目标方法执行后实施增强;
- 环绕增强:org.aopalliance.intercept.MethodInterceptor 代表环绕增强,表示在目标方法执行前后实施增强;
- 异常抛出增强:org.springframework.aop.ThrowsAdvice 代表抛出异常增强,表示在目标方法抛出异常后实施增强;
- 引介增强:org.springframework.aop.IntroductionInterceptor 代表引入增强,表示在目标类中添加一些新的方法和属性。
这些增强接口都有一些方法,通过实现这些接口方法,在接口方法中定义横切逻辑,就可以将他们织入到目标类方法的相应连接点的位置。
6.3.2 前置增强
- Waiter
package com.baobaotao.advice;
public interface Waiter {
void greetTo(String name);
void serveTo(String name);
}
- NaiveWaiter
package com.baobaotao.advice;
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("serve to " + name + "...");
}
}
- GreetingBeforeAdvice
package com.baobaotao.advice;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object obj) throws Throwable {
String clientName = (String) args[0];
System.out.println("How are you! Mr." + clientName + ".");
}
}
- TestBeforeAdvice
package com.baobaotao.advice;
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
public class TestBeforeAdvice {
public static void main(String[] args) {
Waiter target = new NaiveWaiter();
BeforeAdvice advice = new GreetingBeforeAdvice();
//Spring 提供的代理工厂
ProxyFactory pf = new ProxyFactory();
//设置代理目标
pf.setTarget(target);
//为代理目标类添加增强
pf.addAdvice(advice);
//生成代理实例
Waiter proxy = (Waiter) pf.getProxy();
proxy.greetTo("John");
proxy.serveTo("Tom");
}
}
How are you! Mr.John.
greet to John...
How are you! Mr.Tom.
serve to Tom...
在 Spring 中配置
- xml 配置
<bean id="greetingAdvice" class="com.baobaotao.advice.GreetingBeforeAdvice"/>
<bean id="target" class="com.baobaotao.advice.NaiveWaiter"/>
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.baobaotao.advice.Waiter"
p:interceptorNames="greetingAdvice"
p:target-ref="target"/>
-
ProxyFactoryBean 是 FactoryBean 接口的实现类;FactoryBean 负责实例化一个Bean,ProxyFactoryBean 负责为其他Bean创建代理实例,它内部是由ProxyFactory来完成这一工作。常用的属性配置:
- target:代理的目标对象;
- proxyInterfaces:代理所要实现的接口,可以是多个接口。该属性还有一个别名属性interfaces;
- interceptorNames:需要植入目标对象的Bean列表,采用Bean的名称指定。这些Bean必须是实现 org.aopalliance.intercept. MethodInterceptor或 org.springframework.aop.Advisor 的Bean,配置中的顺序对应调用顺序;
- singleton:返回的代理是否是单实例,默认是单实例;
- optimize:当设置为true时,强制使用CGLib代理。对于singleton的代理,我们推荐使用CGLib,对于其他作用域类型的代理,最好使用JDK代理,原因时CGLib创建代理时速度慢,而创建出的代理对象运行效率较高,而使用JDK代理的表现正好相反;
- proxyTargetClass:是否对类进行代理(而不是对接口进行代理),设置为true时,使用CGLib代理。
-
测试方法
package com.baobaotao.advice;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestBeforeAdvice {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Waiter waiter = (Waiter) context.getBean("waiter");
waiter.greetTo("John");
}
}
How are you! Mr.John.
greet to John...
6.3.3 后置增强
- GreetingAfterAdvice
package com.baobaotao.advice;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class GreetingAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnObj, Method method, Object[] args,
Object obj) throws Throwable {
System.out.println("Please enjoy yourseIf!");
}
}
- xml 配置
<bean id="greetingAdvice" class="com.baobaotao.advice.GreetingBeforeAdvice"/>
<bean id="greetingAfter" class="com.baobaotao.advice.GreetingAfterAdvice"/>
<bean id="target" class="com.baobaotao.advice.NaiveWaiter"/>
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.baobaotao.advice.Waiter"
p:interceptorNames="greetingAdvice,greetingAfter"
p:target-ref="target" />
How are you! Mr.John.
greet to John...
Please enjoy yourseIf!
6.3.4 环境增强
- GreetingInterceptor
package com.baobaotao.advice;
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 yourseIf!");
return obj;
}
}
- xml 配置
<bean id="greetingAdvice" class="com.baobaotao.advice.GreetingBeforeAdvice"/>
<bean id="greetingAfter" class="com.baobaotao.advice.GreetingAfterAdvice"/>
<bean id="greetingAround" class="com.baobaotao.advice.GreetingInterceptor"/>
<bean id="target" class="com.baobaotao.advice.NaiveWaiter"/>
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.baobaotao.advice.Waiter"
p:interceptorNames="greetingAround"
p:target-ref="target" />
How are you! Mr.John.
greet to John...
Please enjoy yourseIf!
6.3.5 异常抛出增强
- ForumService
package com.baobaotao.advice;
import java.sql.SQLException;
public class ForumService {
public void removeForum(int forumId) {
throw new RuntimeException("运行异常。");
}
public void updateForum(Forum forum) throws Exception{
throw new SQLException("数据更新操作异常。");
}
}
- TransactionManager
package com.baobaotao.advice;
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
public class TransactionManager implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target,
Exception ex) throws Throwable {
System.out.println("-----------");
System.out.println("method:" + method.getName());
System.out.println("抛出异常:" + ex.getMessage());
System.out.println("成功回滚事务。");
}
}
void afterThrowing([Method method, Object[] args, Object target], Throwable);
方法名必须为afterThrowing,方法入参规定如下:前三个入参Method method, Object[] args, Object target 是可选的(要么三个入参提供,要么不提供)最后一个入参是Throwable或其子类。如以下方法都是合法的:
- afterThrowing(SQLException e);
- afterThrowing(RuntimeException e);
- afterThrowing(Method method, Object[] args, Object target, RuntimeException e)。
- xml 配置
<bean id="transactionManager" class="com.baobaotao.advice.TransactionManager"/>
<bean id="forumServiceTarget" class="com.baobaotao.advice.ForumService"/>
<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="transactionManager"
p:target-ref="forumServiceTarget"
p:proxyTargetClass="true"/>
- 测试类方法
package com.baobaotao.advice;
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestBeforeAdvice {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
ForumService forumService = (ForumService) context.getBean("forumService");
Forum forum = new Forum();
forumService.updateForum(forum);
}
}
-----------
method:removeForum
抛出异常:运行异常。
成功回滚事务。
-----------
method:updateForum
抛出异常:数据更新操作异常。
成功回滚事务。
6.3.6 引入增强
- ForumService
package com.baobaotao.advice;
import java.sql.SQLException;
public class ForumService {
public void removeForum(int forumId) {
System.out.println("模拟删除Forum记录:" + forumId);
}
public void removeTopic(int topicId) {
System.out.println("模拟删除Topic记录:" + topicId);
}
}
- Monitorable
package com.baobaotao.advice;
public interface Monitorable {
void setMonitorActive(boolean active);
}
- ControllablePerformanceMonitor
package com.baobaotao.advice;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import com.baobaotao.proxy.PerformanceMonitor;
public class ControllablePerformanceMonitor extends DelegatingIntroductionInterceptor implements Monitorable{
private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>();
@Override
public void setMonitorActive(boolean active) {
MonitorStatusMap.set(active);
}
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;
}
}
- xml 配置
<bean id="pmonitor" class="com.baobaotao.advice.ControllablePerformanceMonitor"/>
<bean id="forumServiceTarget" class="com.baobaotao.advice.ForumService"/>
<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interfaces="com.baobaotao.advice.Monitorable"
p:target-ref="forumServiceTarget"
p:interceptorNames="pmonitor"
p:proxyTargetClass="true"/>
- 测试类
package com.baobaotao.advice;
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestBeforeAdvice {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
ForumService forumService = (ForumService) context.getBean("forumService");
forumService.removeForum(10);
forumService.removeTopic(1022);
Monitorable moniterable = (Monitorable)forumService;
moniterable.setMonitorActive(true);
forumService.removeForum(10);
forumService.removeTopic(1022);
}
}
模拟删除Forum记录:10
模拟删除Topic记录:1022
begin monitor...
模拟删除Forum记录:10
end monitor...
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.removeForum花费0毫秒。
begin monitor...
模拟删除Topic记录:1022
end monitor...
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.removeTopic花费0毫秒。
6.4 创建切面
Spring AOP 如何定位连接点:
- Spring 通过 org.springframework.aop.Pointcut 接口描述切点,Pointcut 有 ClassFilter和MethodMatch 构成,它通过ClassFilter定位到某些类上,通过MethodMatch定位到某些特定方法上,这样pointcut就拥有了描述某些类的某些特定方法的能力。
- Spring支持两种方法匹配器:静态方法匹配器和动态方法匹配器。
- 所谓静态匹配器,它仅对方法名签名(包括方法名和入参类型及顺序)进行匹配;静态匹配器仅会判别一次;
- 动态方法匹配器,会在运行期检查方法入参的值; 而动态匹配因为每次调用方法的入参都可能不一样,所以每次调用方法都必须判断,
- 因此,动态匹配对性能影响很大。一般情况下,动态匹配不常使用。
- 方法匹配器的类型由isRuntime()返回值决定,方法false表示静态方法匹配器,返回true表示是动态方法匹配器。
6.4.1 切点类型
Spring 提供了六种类型切点
静态方法切点
- org.springframework.aop.support.StaticMethodMatcherPointcut 是静态方法切点的抽象基类,默认情况下它匹配所有的类。StaticMethodMatcherPointcut包括两个主要的子类,分别是NameMatchMethodPointcut和AbstractRegexpMethodPointcut,前者提供简单字符串匹配方法签名,而后者使用正则表达式匹配方法签名。
动态方法切点
- org.springframework.aop.support.DynamicMethodMatcherPointcut是动态方法切点的抽象基类,默认情况下它匹配所有的类。DynamicMethodMatcherPointcut类已经过时,可以使用DefaultPointcutAdvisor和DynamicMethodMatcherPointcut动态方法匹配器代替之;
注解切点
- org.springframework.aop.support.annotation.AnnotationMatchingPointcut 实现类表示注解切点。使用AnnotationMatchingPointcut支持在Bean中直接通过JDK5.0注解标签定义的切点。
表达式切点
- org.springframework.aop.support.ExpressionPointcut 接口主要是为了支持AspectJ切点表达式语法而定义的接口
流程切点
- org.springframework.aop.support.ControlFlowPointcut 实现类表示控制流程切点。ControlFlowPointcut是一种特殊的切点,他根据程序执行堆栈的信息查看目标方法是否由某一方法直接或间接发起调用,以此判断是否为匹配的连接点;
复合切点
- org.springframework.aop.support.ComposablePointcut 实现类是为创建多个切点而提供的方便操作类。它所有的方法都返回ComposablePointcut类,这样,我们就可以使用连接表达式对切点进行操作,形如:Pointcut pc = new ComposablePointcut().union(classFilter).intersection(methodMatcher).intersection(pointcut)。
6.4.2 切面类型
由于增强即包含横切代码,又包含部分的连接点信息(方法前、后等方位信息),所以我们可以通过增强类生成一个切面。但切点仅代表目标类连接点的部分信息(类和方法的定位),所以仅有切点,我们无法制作出一个切面,必须结合增强才能制作出切面。Spring 使用 org.springframework.aop.Advisor 接口表示切面的概念,一个切面同时包含横切代码和连接点信息。切面可以分为三类:一般切面、切点切面和引介切面:
- Advisor:代表一般切面,它仅包含一个Advice。因为Advice包含了横切代码和连接点的信息,所以Advice 本身就是一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面太宽泛,所以一般不会直接使用;
- PointcutAdvisor:代表具有切点的切面,它包含Advice和Pointcut两个类,这样,我们就可以通过类,方法名以及方法方位等信息灵活的定义切面的连接点,提供更具有适用性的切面。
- IntroductionAdvisor:代表引介切面。引介切面是对应引介增强的特殊切面,它应用于类层面上,所以引介切点使用ClassFilter进行定义。
- PointcutAdvisor 主要由6个具体的实现类:
- DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice定义一个切面,唯一不支持的是引介的切面类型,一般可以通过扩展该类实现自定义的切面;
- NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面;
- RegexpMethodPointcutAdvisor:对于按正则表达式匹配方法名进行切点定义的切面,可以通过扩展该实现类进行操作。 RegexpMethodPointcutAdvisor允许用户以正则表达式模式串定义方法匹配的切点,其内部通过JdkRegexpMethodPointcut 构造出正则表达式方法名切点。
- StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下匹配所有的目标类;
- AspectJExpressionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面;
- AspectJPointcutAdvisor:用于AspectJ语法定义切点的切面;
6.4.3 静态普通方法名匹配切面
- Waiter
package com.baobaotao.advisor;
public class Waiter {
public void greetTo(String name) {
System.out.println("waiter greet to" + name + "...");
}
public void serveTo(String name) {
System.out.println("waiter serveTo" + name + "...");
}
}
- Seller
package com.baobaotao.advisor;
public class Seller {
public void greetTo(String name) {
System.out.println("seller greet to" + name + "...");
}
}
- GreetingAdvisor
- StaticMethodMatcherPointcutAdvisor 抽象类唯一需要定义的事matches()方法。在默认情况下,该切面匹配所有的类,我们通过覆盖 getClassFilter()方法,让它仅匹配 Waiter类及其子类。
package com.baobaotao.advisor;
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor{
//切点方法匹配规则:方法名为greetTo
@Override
public boolean matches(Method method, Class targetClass) {
return "greetTo".equals(method.getName());
}
public ClassFilter getClassFilter() {//切点类匹配规则:为Waiter的类或子类
return new ClassFilter() {
public boolean matches(Class clazz) {
return Waiter.class.isAssignableFrom(clazz);
}
};
}
}
- GreetingBeforeAdvice
package com.baobaotao.advisor;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class GreetingBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object obj) throws Throwable {
System.out.println(obj.getClass().getName() + "." + method.getName());
String clientName = (String) args[0];
System.out.println("How are you! Mr." + clientName + ".");
}
}
- beans.xml 配置
- 在@1处,我们将greetAdvice增强装配到 greetingAdvisor的切面中。StaticMethodMatcherPointcutAdvisor除了advice属性除外,还可以定义另外两个属性:
- classFilter:类匹配过滤器,在GreetingAdvisor 中,我们用编码的方式设定了classFilter;
- order:切面织入时的顺序,该属性用于定义Ordered接口表示的顺序。
- 由于需要分别为waiter 和 seller 两个Bean定义代理器,两者有很多公共的配置信息,我们使用了一个父 < bean > 简化配置,如@2处所示。在@3和@4处,通过引用父< bean >轻松定义了两个织入切面的代理。
<bean id="waiterTarget" class="com.baobaotao.advisor.Waiter" />
<bean id="sellerTarget" class="com.baobaotao.advisor.Seller" />
<bean id="greetingAdvice" class="com.baobaotao.advisor.GreetingBeforeAdvice" />
<bean id="greetingAdvisor" class="com.baobaotao.advisor.GreetingAdvisor"
p:advice-ref="greetingAdvice"/>@1
<bean id="parent" abstract="true"
class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="greetingAdvisor"
p:proxyTargetClass="true"/>@2
<bean id="waiter" parent="parent" p:target-ref="waiterTarget"/>@3
<bean id="seller" parent="parent" p:target-ref="sellerTarget"/>@4
- 测试类
package com.baobaotao.advisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter");
Seller seller = (Seller) ctx.getBean("seller");
waiter.greetTo("John");
waiter.serveTo("John");
seller.greetTo("John");
}
}
com.baobaotao.advisor.Waiter.greetTo
How are you! Mr.John.
waiter greet toJohn...
waiter serveToJohn...
seller greet toJohn...
6.4.4 静态正则表达式方法匹配切面
- 通过正则表达式定义切面
<bean id="waiterTarget" class="com.baobaotao.advisor.Waiter" />
<bean id="sellerTarget" class="com.baobaotao.advisor.Seller" />
<bean id="greetingAdvice" class="com.baobaotao.advisor.GreetingBeforeAdvice" />
<bean id="greetingAdvisor" class="com.baobaotao.advisor.GreetingAdvisor"
p:advice-ref="greetingAdvice"/>
<bean id="regexpAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="greetingAdvice">
<property name="patterns">
<list>
<value>.*greet.*</value>
</list>
</property>
</bean>
<bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="regexpAdvisor"
p:target-ref="waiterTarget"
p:proxyTargetClass="true"/>
<bean id="seller1" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="regexpAdvisor"
p:target-ref="sellerTarget"
p:proxyTargetClass="true"/>
- 测试代码
package com.baobaotao.advisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter1");
Seller seller = (Seller) ctx.getBean("seller1");
waiter.serveTo("John1");
waiter.greetTo("John2");
seller.greetTo("John3");
}
}
waiter serveToJohn1...
com.baobaotao.advisor.Waiter.greetTo
How are you! Mr.John2.
waiter greet toJohn2...
com.baobaotao.advisor.Seller.greetTo
How are you! Mr.John3.
seller greet toJohn3...
- 正则表达式符合
符号 | 说明 | 实例 |
---|---|---|
. | 匹配除换行符外的所有单个的字符 | .n匹配 nay,an apple is on the tree 中的 an 和 on,但不匹配nay |
* | 匹配*前面的字符0次或n次 | bo*匹配 A ghost booooed 中的 boooo 或 A bird warbled中的b,但不匹配Agoat grunted 中的任何字符 |
+ | 匹配+号前面的字符1次或n次。等价于{1,} | a+匹配candy中的 a 和 caaaaaaandy. 中的所有a |
^ | 表示匹配的字符必须在最前边 | ^A 不匹配 an A 中的 A,但匹配 An A.中最前面的A |
$ | 于^类似,匹配最末的字符 | t$不匹配 eater 中的t,但匹配 eat 中的 t |
? | 匹配?前面的字符0次或1次 | e?le?匹配 angel 中的el 和 angle 中的 le |
x l y | 匹配x或者y | green l red 匹配 green apple中的 green 和 red apple.中的red |
[xyz] | 一字符列表,匹配列出中的任一字符。可以通过连字符-指出一个字符范围 | [abc]跟[a-c]一样。他们匹配brisket中的b 和ache中的 a和c |
{n} | {n}这里的n是一个正整数。匹配前面的n个字符 | a{2} 不匹配 candy 中的 a,但匹配 caandy 中的两个a |
{n,} | 这里的n是一个正整数。匹配至少n个前面的字符 | a{a,} 不匹配 candy中的 a,但匹配 caandy 中的所有a 和 caaaaaandy.中的所有a |
{n,m} | 这里的n 和 m都是正整数。匹配至少n个 最多m个前面的字符 | a{1,2} 不匹配 |
\ | 将下一个字符标记为一个特殊字符 | 例如,n匹配字符n。\n匹配一个换行符。语法中的特殊字符需要通过转义符表示,如.表示. 而{表示{ |
转义字符 |
---|
\d 匹配一个数字字符。等价于 [0-9] |
\D 匹配一个非数字字符。等价于 [^0-9] |
\f 匹配一个换页符。等价于\x0c 和 \cL |
\n 匹配一个换行符。等价于\x0a 和 \cJ |
\r 匹配一个回车符。等价于\x0d 和 \cM |
\s 匹配任何空白字符。包括空格、制表符、换页符等。等价于[\f\n\r\t\v] |
\S 匹配任何非空白字符。等价于[^\f\n\r\t\v] |
\t 匹配一个制表符。等价于\x09和\cI |
\v 匹配一个垂直制表符。等价于\x0b和\cK |
\w 匹配包括下划线的任何单词字符。等价于[A-Za-z0-9_] |
\W 匹配任何非单词字符。等价于[^A-Za-z0-9_] |
- 举例
- . * set . * 表示所有类中的以set前缀的方法,如com.baobaotao.Waiter.setSalary(),Person.setName()等;
- com\ .baobaotao\ .advisor\ …* 表示 com.baobaotao.advisor 包下所有类的所有方法;
- com\ .baobaotao\ .service\ …*Service\ … * 匹配com.baobaotao.service包下所有类名以Service 结尾的类的所有方法,如com.baobaotao.service.UserService.save(User user)、baobaotao.service.ForumService.update(Forum forum)等方法;
- com\ .baobaotao\ .service\ …* \ .save.+,匹配所有以save为前缀的方法,该方法后还必须拥有至少一个字符,且这些方法位于 com.baobaotao.service包中以Service为后缀的类中。如匹配com.baobaotao.service.UserService类的saveUser()和 saveLoginLog()方法,但不匹配该类中的save() 方法。
6.4.5 动态切面
- DynamicMethodMatcherPointcut 是一个抽象类,它将 isRuntime()标识为final且返回true,这样其子类就一定是一个动态的切点了,该抽象类默认匹配所有的类和方法,因此需要通过扩展该类编写符合要求的动态切点。
- GreetingDynameicPointcut 类即有用于静态切点检查的方法,也有用于动态切点检查的方法。由于动态切点检查会对性能造成很大的影响,我们应当尽量避免在运行时每次都对目标类的各个方法进行动态检查。Spring采用的以下机制:
- 在创建代理时对目标类的每个连接点使用静态切点检查,如果仅通过静态切点检查就可以知道连接点是不匹配的,则在运行时就不再进行动态检查了;
- 如果静态切点检查是匹配的,在运行时才进行动态切点检查;
package com.baobaotao.advisor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;
public class GreetingDynameicPointcut extends DynamicMethodMatcherPointcut{
private static List<String> specialClientList = new ArrayList<String>();
static {
specialClientList.add("John");
specialClientList.add("Tom");
}
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> clazz) {
System.out.println("调用getClassFilter()对" + clazz.getName() + "做静态检查");
return Waiter.class.isAssignableFrom(clazz);
}
};
}
public boolean matches(Method method, Class clazz) {
System.out.println("调用matches(method,clazz)" + clazz.getName() + "." + method.getName() + "做静态检查");
return "greetTo".equals(method.getName());
}
@Override
public boolean matches(Method method, Class clazz, Object[] args) {
System.out.println("调用matches(method,clazz)" + clazz.getName() + "." + method.getName() + "做动态检查");
String clientName = (String)args[0];
return specialClientList.contains(clientName);
}
}
- xml 配置信息
<bean id="waiterTarget" class="com.baobaotao.advisor.Waiter" />
<bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<bean class="com.baobaotao.advisor.GreetingDynameicPointcut"/>@1
</property>
<property name="advice">
<bean class="com.baobaotao.advisor.GreetingBeforeAdvice"/>
</property>
</bean>
<bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="dynamicAdvisor"
p:target-ref="waiterTarget"
p:proxyTargetClass="true"/>
- 动态切面的配置和静态切面的配置没有什么区别:
- 我们使用 DefaultPointcutAdvisor定义切面,在@1 处使用内部Bean方式注入动态切点 GreetingDynamicPointcut,增强依旧使用前面定义的GreetingBeforeAdvice。此外,DefaultPointcutAdvisor 还有一个 order 属性,用于定义切面的织入顺序。
调用getClassFilter()对com.baobaotao.advisor.Waiter做静态检查
调用matches(method,clazz)com.baobaotao.advisor.Waiter.greetTo做静态检查
调用getClassFilter()对com.baobaotao.advisor.Waiter做静态检查
调用matches(method,clazz)com.baobaotao.advisor.Waiter.serveTo做静态检查
调用getClassFilter()对com.baobaotao.advisor.Waiter做静态检查
调用matches(method,clazz)com.baobaotao.advisor.Waiter.toString做静态检查
调用getClassFilter()对com.baobaotao.advisor.Waiter做静态检查
调用matches(method,clazz)com.baobaotao.advisor.Waiter.clone做静态检查
调用getClassFilter()对com.baobaotao.advisor.Waiter做静态检查
调用matches(method,clazz)com.baobaotao.advisor.Waiter.serveTo做静态检查
waiter serveToPeter...
调用getClassFilter()对com.baobaotao.advisor.Waiter做静态检查
调用matches(method,clazz)com.baobaotao.advisor.Waiter.greetTo做静态检查
调用matches(method,clazz)com.baobaotao.advisor.Waiter.greetTo做动态检查
waiter greet toPeter...
waiter serveToJohn...
调用matches(method,clazz)com.baobaotao.advisor.Waiter.greetTo做动态检查
com.baobaotao.advisor.Waiter.greetTo
How are you! Mr.John.
waiter greet toJohn...
6.4.6 流程切面
- WaiterDelegate
package com.baobaotao.advisor;
public class WaiterDelegate {
private Waiter waiter;
public void service(String clientName) {
waiter.greetTo(clientName);
waiter.serveTo(clientName);
}
public void setWaiter(Waiter waiter) {
this.waiter = waiter;
}
}
- xml 配置
- ControlFlowPointcut 有两个构造函数,分别是 ControlFlowPointcut(Class clazz)和ControlFlowPointcut(Class clazz, String methodName)。第一个构造函数指定一个类作为流程切点;第二个构造函数指定一个类和某一个方法作为流程切点。
<bean id="greetingAdvice" class="com.baobaotao.advisor.GreetingBeforeAdvice"/>
<bean id="waiterTarget" class="com.baobaotao.advisor.Waiter"/>
<bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
<constructor-arg type="java.lang.Class"
value="com.baobaotao.advisor.WaiterDelegate" />
<constructor-arg type="java.lang.String"
value="service"/>
</bean>
<bean id="controlFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
p:pointcut-ref="controlFlowPointcut"
p:advice-ref="greetingAdvice"/>
<bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="controlFlowAdvisor"
p:target-ref="waiterTarget"
p:proxyTargetClass="true"/>
- 测试类
package com.baobaotao.advisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter3");
WaiterDelegate wd = new WaiterDelegate();
wd.setWaiter(waiter);
waiter.serveTo("Peter");
waiter.greetTo("Peter");
wd.service("Peter");
}
}
waiter serveToPeter...
waiter greet toPeter...
com.baobaotao.advisor.Waiter.greetTo
How are you! Mr.Peter.
waiter greet toPeter...
com.baobaotao.advisor.Waiter.serveTo
How are you! Mr.Peter.
waiter serveToPeter...
6.4.7 复核切点切面
-
使用Spring 为我们提供的 ComposablePointcut 可以将多个切点以并集或交集的方式组合起来,提供了切点之间复合运算的功能。
-
ComposablePointcut 本身也是一个切点,它实现了 Pointcut 的接口。
-
ComposablePointcut 构造函数:
- ComposablePointcut():构造一个匹配所有类所有方法的复合切点;
- ComposablePointcut(ClassFilter classFilter):构造一个匹配特定类所有方法的复合切点;
- ComposablePointcut(MethodMatcher methodMatcher):构造一个匹配所有类特定方法的复合切点;
- ComposablePointcut(ClassFilter classFilter, MethodMatcher methodMatcher):构造一个匹配特定类特定方法的复合切点。
-
ComposablePointcut 提供三个交集运算的方法:
- ComposablePointcut intersection(ClassFilter filter):将复合切点和一个 ClassFilter 对象进行交集运算,得到一个结果复合切点;
- ComposablePointcut intersection(MethodMatcher mm):将复合切点和一个 MethodMatcher对象进行交集运算,得到一个结果复合切点;
- ComposablePointcut intersection(Pointcut other):将复合切点和一个切点对象进行交集运算,得到一个结果复合切点。
-
ComposablePointcut 提供了两个交集运算的方法:
- ComposablePointcut union(ClassFilter filter):将复合点和一个ClassFilter 对象进行并集运算,得到一个结果复合切点;
- ComposablePointcut union(MethodMatcher mm):将复合切点和一个MethodMatcher对象进行并集运算,得到一个结果复合切点。
-
ComposablePointcut 没有提供直接对两个切点进行交并集运算的方法,如果需要对两个切点进行交并集运算,可以使用Spring 提供的 org.springframework.aop.support.Pointcuts 工具类,该工具类中有两个非常好用的静态方法;
- Pointcut intersection(Pointcut a, Pointcut b):对两个切点进行交集运算,返回一个结果切点,该切点即是 ComposablePointcut 对象的实例;
- Pointcut union(Pointcut a, Pointcut b):对两个切点进行并集运算,返回一个结果切点,该切点即使ComposablePointcut 对象的实例。
-
GreetingComposablePointcut
package com.baobaotao.advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.NameMatchMethodPointcut;
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(pt2);
}
}
- xml 配置
<bean id="greetingAdvice" class="com.baobaotao.advisor.GreetingBeforeAdvice"/>
<bean id="waiterTarget" class="com.baobaotao.advisor.Waiter"/>
<bean id="gcp" class="com.baobaotao.advisor.GreetingComposablePointcut"/>
<bean id="composableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
p:pointcut="#{gcp.intersectionPointcut}"
p:advice-ref="greetingAdvice"/>
<bean id="waiter4" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="composableAdvisor"
p:target-ref="waiterTarget"
p:proxyTargetClass="true"/>
- 测试类
package com.baobaotao.advisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter4");
WaiterDelegate wd = new WaiterDelegate();
wd.setWaiter(waiter);
waiter.serveTo("Peter");
waiter.greetTo("Peter");
wd.service("Peter");
}
}
waiter serveToPeter...
waiter greet toPeter...
com.baobaotao.advisor.Waiter.greetTo
How are you! Mr.Peter.
waiter greet toPeter...
waiter serveToPeter...
6.4.8 引入切面
- IntroductionAdvisor 有两个实现类,分别是 DefaultIntroductionAdvisor 和 DeclareParentsAdvisor,前者是引介切面最常用的实现类,后者实现使用AspectJ 语言的DeclareParent主键表示的引介切面。
- DefaultIntroductionAdvisor拥有三个构造函数:
- DefaultIntroductionAdvisor(Advice advice):通过一个增强创建的引介切面,引介切面为目标对象新增增强对象中所有接口的实现;
- DefaultIntroductionAdvisor(DynamicIntroductionAdvice advice, Class clazz):通过一个增强和一个指定的接口类创建引介切面,仅为目标对象新增clzz接口的实现;
- DefaultIntroductionAdvisor(Advice advice, IntroductionInfo introductionInfo):通过一个增强和一个IntroductionInfo创建一个引介切面,目标对象需要实现哪些接口,有introductionInfo对象的getInterfaces() 表示。
<bean id="introduceAdvisor" class="org.springframework.aop.support.DefaultIntroductionAdvisor">
<constructor-arg>
<bean class="com.baobaotao.introduce.ControllablePerformanceMonitor"/>
</constructor-arg>
</bean>
<bean id="forumServiceTarget" class="com.baobaotao.introduce.ForumService"/>
<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="introduceAdvisor"
p:target-ref="forumServiceTarget"
p:proxyTargetClass="true"/>
6.5 自动创建代理
6.5.1 实现类介绍
这些基于BeanPostProcessor 的自动代理创建器的实现类,将根据一些规则自动在容器实例化 Bean 生成代理实例。代理创建器可以分为以下三类:
- 基于 Bean 配置名规则的自动代理创建器:允许为一组特定配置名的 Bean 自动创建代理实例的代理创建器,实例类为 BeanNameAutoProxyCreator;
- 基于 Advisor 匹配机制的自动代理创建器:它会对容器中所有的 Advisor 进行扫描,自动将这些切面应用到匹配的 Bean 中(即为目标 Bean 创建代理实例),实现类为 DefaultAdvisorAutoProxyCreator;
- 基于 Bean 中 AspjectJ 注解标签的自动代理创建器:为包含 AspectJ 注解的 Bean 自动创建代理实例,它的实现类是 AnnotationAwareAspectJAutoProxyCreator,该类Spring2.0 的新增类。
6.5.2 BeanNameAutoProxyCreator
- xml 配置
<bean id="waiter" class="com.baobaotao.advisor.Waiter"/>
<bean id="seller" class="com.baobaotao.advisor.Seller"/>
<bean id="greetingAdvice" class="com.baobaotao.advisor.GreetingBeforeAdvice"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
p:beanNames="*er"
p:interceptorNames="greetingAdvice"
p:optimize="true" />
- 测试类
package com.baobaotao.advisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter");
Seller seller = (Seller) ctx.getBean("seller");
waiter.greetTo("John");
seller.greetTo("Tom");
}
}
com.baobaotao.advisor.Waiter.greetTo
How are you! Mr.John.
waiter greet toJohn...
com.baobaotao.advisor.Seller.greetTo
How are you! Mr.Tom.
seller greet toTom...
6.5.3 静态普通方法名匹配切面
<bean id="waiter" class="com.baobaotao.advisor.Waiter"/>
<bean id="seller" class="com.baobaotao.advisor.Seller"/>
<bean id="greetingAdvice" class="com.baobaotao.advisor.GreetingBeforeAdvice"/>
<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:patterns=".*greet.*"
p:advice-ref="greetingAdvice"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
package com.baobaotao.advisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAdvisor {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter");
Seller seller = (Seller) ctx.getBean("seller");
waiter.greetTo("John");
seller.greetTo("Tom");
}
}
com.baobaotao.advisor.Waiter.greetTo
How are you! Mr.John.
waiter greet toJohn...
com.baobaotao.advisor.Seller.greetTo
How are you! Mr.Tom.
seller greet toTom...