Spring AOP 创建增强类
AOP联盟为增强定义了org.aopalliance.aop.Advice接口,Spring支持5种类型的增强:
1)前置增强:org.springframework.aop.BeforeAdvice 代表前置增强,因为Spring 只支持方法级的增强,所有MethodBeforeAdvice是目前可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定义的;
2)后置增强:org.springframework.aop.AfterReturningAdvice 代表后增强,表示在目标方法执行后实施增强;
3)环绕增强:org.aopalliance.intercept.MethodInterceptor 代表环绕增强,表示在目标方法执行前后实施增强;
4)异常抛出增强:org.springframework.aop.ThrowsAdvice 代表抛出异常增强,表示在目标方法抛出异常后实施增强;
5)引介增强:org.springframework.aop.IntroductionInterceptor 代表引介增强,表示在目标类中添加一些新的方法和属性。
1、前置增强
模拟服务员向顾客表示欢迎和对顾客提供服务。
Waiter接口:
package com.yyq.advice; public interface Waiter { void greetTo(String name); void serveTo(String name); }
NaiveWaiter服务类:
package com.yyq.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("serving to " + name + "..."); } }
前置增强实现类:
package com.yyq.advice; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class GreetingBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { String clientName = (String) objects[0]; System.out.println("How are you ! Mr." + clientName + "."); } }
测试方法:
@Test public void testBeforeAdvice(){ Waiter target = new NaiveWaiter(); BeforeAdvice advice = new GreetingBeforeAdvice(); ProxyFactory pf = new ProxyFactory(); //Spring提供的代理工厂 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.
serving to Tom...
在Spring中配置:beans.xml
<bean id="greetingAdvice" class="com.yyq.advice.GreetingBeforeAdvice"/> <bean id="target" class="com.yyq.advice.NaiveWaiter"/> <bean id="waiter1" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.yyq.advice.Waiter" p:interceptorNames="greetingAdvice" p:target-ref="target"/>
ProxyFactoryBean 是FactoryBean接口的实现类。
· target:代理的目标对象;
· proxyInterfaces:代理所实现的接口,可以是多个接口。该属性还有一个别名属性interfaces;
· interceptorNames:需要织入目标对象的Bean列表,采用Bean的名称指定。这些Bean必须是实现了org.aopalliance.intercept.Method 或 org.springframework.aop.Advisor的Bean,配置中的顺序对应调用的顺序;
· singleton:返回的代理是否是单实例,默认为单实例;
· optimize:当设置为true时,强制使用CGLib代理。对于singleton的代理,我们推荐使用CGLib,对于其他作用域类型的代理,最好使用JDK代理。原因是CGLib创建代理时速度慢,而创建出的代理对象运行效率较高,而使用JDK代理的表现正好相反;
· proxyTargetClass:是否对类进行代理(而不是对接口进行代理),设置为true时,使用CGLib代理。
测试方法:
@Test public void testBeforeAdvice2(){ String configPath = "com\\yyq\\advice\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter1"); waiter.greetTo("Joe"); }
输出结果:
How are you ! Mr.Joe.
greet to Joe...
2、后置增强
后置增强在目标类方法调用后执行。模拟服务员在每次服务后使用礼貌用语。
GreetingAfterAdvice后置增强实现类:
package com.yyq.advice; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; public class GreetingAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o2) throws Throwable { System.out.println("Please enjoy yourself."); } }
在beans.xml文件添加后置增强:
<bean id="greetingBefore" class="com.yyq.advice.GreetingBeforeAdvice"/> <bean id="greetingAfter" class="com.yyq.advice.GreetingAfterAdvice"/> <bean id="waiter2" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.yyq.advice.Waiter" p:interceptorNames="greetingBefore,greetingAfter" p:target-ref="target"/>
测试方法:
@Test public void testBeforeAndAfterAdvice(){ String configPath = "com\\yyq\\advice\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter2"); waiter.greetTo("Joe"); }
结果输出:
How are you ! Mr.Joe.
greet to Joe...
Please enjoy yourself.
3、环绕增强
环绕增强允许在目标类方法调用前后织入横切逻辑,它综合实现了前置、后置增强两者的功能。
GreetingInterceptor环绕增强实现类:
package com.yyq.advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class GreetingInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object[] args = methodInvocation.getArguments(); String clientName = (String) args[0]; System.out.println("Hi,Mr " + clientName + "."); Object obj = methodInvocation.proceed(); System.out.println("Please enjoy yourself~"); return obj; } }
Spring 直接使用AOP联盟所定义的MethodInterceptor作为环绕增强的接口。该接口拥有唯一的接口方法 Object invoke(MethodInvocation invocation), MethodInvocation不但封装目标方法及其入参数组,还封装了目标方法所在的实例对象,通过MethodInvocation的getArguments()可以获取目标方法的入参数组,通过proceed()反射调用目标实例相应的方法。
在beans.xml文件添加环绕增强:
<bean id="greetingAround" class="com.yyq.advice.GreetingInterceptor"/> <bean id="waiter3" class="org.springframework.aop.framework.ProxyFactoryBean" p:proxyInterfaces="com.yyq.advice.Waiter" p:interceptorNames="greetingAround" p:target-ref="target"/>
测试方法:
@Test public void testAroundAdvice(){ String configPath = "com\\yyq\\advice\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); Waiter waiter = (Waiter)ctx.getBean("waiter3"); waiter.greetTo("Joe"); }
结果输出:
Hi,Mr Joe.
greet to Joe...
Please enjoy yourself~
4、异常抛出增强
异常抛出增强最适合的应用场景是事务管理,当参与事务的某个Dao发送异常时,事务管理器就必须回滚事务。
Forum业务类:
package com.yyq.advice; public class Forum { private int forumId; public int getForumId() { return forumId; } public void setForumId(int forumId) { this.forumId = forumId; } }
ForumService业务类:
package com.yyq.advice; import java.sql.SQLException; public class ForumService { public void removeForum(int forumId){ System.out.println("removeForum...."); throw new RuntimeException("运行异常"); } public void updateForum(Forum forum)throws Exception{ System.out.println("updateForum"); throw new SQLException("数据更新操作异常。"); } }
TransactionManager异常抛出增强实现类:
package com.yyq.advice; import org.springframework.aop.ThrowsAdvice; import java.lang.reflect.Method; 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("成功回滚事务。"); } }
ThrowAdvice异常抛出增强接口没有定义任何方法,它是一个标识接口,在运行期Spring使用反射的机制自行判断,我们采用以下签名形式定义异常抛出的增强方法:void afterThrowing(Mehod method, Object[] args, Object target, Throwable);方法名必须为afterThrowing,方法入参规定,前三个参数是可选的,要么三个入参提供,要么不提供,最后一个入参是Throwable或者子类。
在beans.xml文件添加异常抛出增强:
<bean id="transactionManager" class="com.yyq.advice.TransactionManager"/> <bean id="forumServiceTarget" class="com.yyq.advice.ForumService"/> <bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean" p:interceptorNames="transactionManager" p:target-ref="forumServiceTarget" p:proxyTargetClass="true"/>
测试方法:
@Test public void testThrowsAdvice(){ String configPath = "com\\yyq\\advice\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); ForumService fs = (ForumService)ctx.getBean("forumService"); try{ fs.removeForum(10); } catch (Exception e) {} try{ fs.updateForum(new Forum()); } catch (Exception e) {} }
结果输出:
----------------
method:removeForum
抛出异常:运行异常
成功回滚事务。
updateForum
----------------
method:updateForum
抛出异常:数据更新操作异常。
成功回滚事务。
5、引介增强
引介增强为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,我们可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建实现某个接口的代理。Spring定义了引介增强接口IntroductionInterceptor,该接口没有定义任何的方法,Spring为该接口提供了DelegatingIntroductionInterceptor实现类。
Monitorable:用于标识目标类是否支持性能监视的接口
package com.yyq.advice; public interface Monitorable { void setMonitorActive(boolean active); }
ControllablePerformanceMonitor 为引介增强实现类:
package com.yyq.advice; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.DelegatingIntroductionInterceptor; 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; } }
PerformanceMonitor监视类:
package com.yyq.advice; public class PerformanceMonitor { private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>(); 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(); mp.printPerformance(); } }
MethodPerformance记录性能信息:
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 + "毫秒。"); } }
在beans.xml文件添加引介增强:
<bean id="pmonitor" class="com.yyq.advice.ControllablePerformanceMonitor"/> <bean id="forumServiceImplTarget" class="com.yyq.advice.ForumServiceImpl"/> <bean id="forumService2" class="org.springframework.aop.framework.ProxyFactoryBean" p:interfaces="com.yyq.advice.Monitorable" p:interceptorNames="pmonitor" p:target-ref="forumServiceImplTarget" p:proxyTargetClass="true"/>
测试方法:
@Test public void testIntroduce(){ String configPath = "com\\yyq\\advice\\beans.xml"; ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath); ForumServiceImpl forumServiceImpl = (ForumServiceImpl)ctx.getBean("forumService2"); forumServiceImpl.removeForum(23); forumServiceImpl.removeTopic(1023); Monitorable monitorable = (Monitorable)forumServiceImpl; monitorable.setMonitorActive(true); forumServiceImpl.removeForum(22); forumServiceImpl.removeTopic(1023); }
结果输出:
模拟删除Forum记录:23
模拟删除Topic记录:1023
begin monitor...
模拟删除Forum记录:22
end monitor...
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeForum花费40毫秒。
begin monitor...
模拟删除Topic记录:1023
end monitor...
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeTopic花费21毫秒。