spring AOP详解三
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的结束拦截所有父类方法的调用,并顺势织入横切逻辑。我们采用CGLib技术可以编写一个可以为任何类创建织入横切逻辑代理对象的代理创建器,下面看一个使用CGLib代理技术实现横切的一个例子:
1.CglibProxy.java
package spring.aop.demo2; import java.lang.reflect.Method; import spring.aop.demo1.PerformanceMonitor; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor { // private static CglibProxy proxy = new CglibProxy(); private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) { enhancer.setSuperclass(clazz);// 设置需要创建子类的类 enhancer.setCallback(this); return enhancer.create();// 通过字节码技术动态创建子类实例 } @Override public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable { PerformanceMonitor.begin(arg0.getClass().getName() + "." + arg1.getName()); Object result = arg3.invokeSuper(arg0, arg2); PerformanceMonitor.end(); return result; } }
2.UserServiceImpl.java
package spring.aop.demo2; public class UserServiceImpl{ public void removeUser(int userId) { System.out.println("模拟删除用户:" + userId); } public void addUser(int userId) { // TODO Auto-generated method stub } public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); UserServiceImpl userService =(UserServiceImpl)proxy.getProxy(UserServiceImpl.class); userService.removeUser(7); } }
输出:
begin monitor...
模拟删除用户:7
end monitor...
spring.aop.demo2.UserServiceImpl$$EnhancerByCGLIB$$4d9bdf63.removeUser花费15毫秒
总结:用户通过getProxy(Class clazz)为一个类创建动态代理对象,该代理对象通过扩展clazz创建代理对象,在这个代理对象中,我们织入横切逻辑代码。intercept是CGLib定义的Interceptor接口的方法,它拦截所有目标方法的调用,参数arg0表示目标类的实例;参数arg1表示目标类方法的反射对象;arg2表示目标类方法的参数的反射对象;arg3表示代理类实例;
我们看到输出spring.aop.demo2.UserServiceImpl$$EnhancerByCGLIB$$4d9bdf63.removeUser,这个特殊的类就是CGLib为UserService动态创建的子类。
Spring AOP的底层就是通过代理(JDK动态代理或CGlib代理)来实现AOP的,但是这种实现方式存在三个明显需要改进的地方:
a.目标类的所有方法都添加了横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定的方法添加横切逻辑;
b.我们通过硬编码的方式制定了织入横切逻辑的织入点,即在目标业务方法的开始和结束前织入代码;
c.我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用;
CGLib所创建的动态代理对象的性能比JDK的高大概10倍,但CGLib在创建代理对象的时间比JDK大概多8倍,所以对于singleton的代理对象或者具有实例池的代理,因为无需重复的创建代理对象,所以比较适合CGLib动态代理技术,反之选择JDK代理。值得一提的是由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中final的方法进行代理。
三、创建增强类
Spring使用增强定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括了在方法的哪一点加入横切代码的方位信息,所以增强既包含横切逻辑,还包含部分连接点的信息。
前置增强:org.springframework.aop.BeforeAdvice 代表前置增强,因为Spring只支持方法级的增强,所以MethodBeforeAdvice是目前可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定义的,下面我们看一个前置通知的小例子:
1.Waiter.java
package spring.aop.beforeadvicedemo; public interface Waiter { void greetTo(String name); void serverTo(String name); }
2.GreetingBeforeAdvice.java 实现前置增强接口的横切逻辑
package spring.aop.beforeadvicedemo; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class GreetingBeforeAdvice implements MethodBeforeAdvice { //arg0是目标类的方法,arg1是目标类方法的参数,arg2是目标类的实例 @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { String clientName = (String)arg1[0]; System.out.println("How are you! Mr." + clientName); } }
3.目标类NaiveWaiter.java和测试代码
package spring.aop.beforeadvicedemo; import org.springframework.aop.BeforeAdvice; import org.springframework.aop.framework.ProxyFactory; public class NaiveWaiter implements Waiter { @Override public void greetTo(String name) { System.out.println("great to " + name); } @Override public void serverTo(String name) { System.out.println("serving "+ name); } public static void main(String[] args) { BeforeAdvice advice = new GreetingBeforeAdvice(); Waiter waiter = new NaiveWaiter(); //Spring提供的代理工厂 ProxyFactory pf = new ProxyFactory(); //设置代理目标 pf.setTarget(waiter); //为代理目标添加增强 pf.addAdvice(advice); //生成代理实例 Waiter waiterProxy = (Waiter)pf.getProxy(); waiterProxy.greetTo("nicholaslee"); waiterProxy.serverTo("nicholaslee"); } }
输出:
How are you! Mr.nicholaslee
great to nicholaslee
How are you! Mr.nicholaslee
serving nicholaslee
说明:在测试代码中,我们用到了org.springframework.aop.framework.ProxyFactory,这个内部就是使用了我们之前的JDK代理或者CGLib代理的技术,将增强应用到目标类中。Spring定义了org.springframework.aop.framework.AopProxy接口,并提供了两个final的实现类,其中:
Cglib2AopProxy使用CGLib代理技术创建代理,而JdkDynamicAopProxy使用JDK代理技术创建代理;
如果通过ProxyFactory的setInterfaces(Class[] interfaces)指定针对接口进行代理,ProxyFactory就使用JdkDynamicAopProxy;
如果是通过类的代理则使用Cglib2AopProxy,另外也可以通过ProxyFactory的setOptimize(true)方法,让ProxyFactory启动优化代理模式,这样针对接口的代理也会使用Cglib2AopProxy。
BeforeAdvice advice = new GreetingBeforeAdvice(); Waiter waiter = new NaiveWaiter(); //Spring提供的代理工厂 ProxyFactory pf = new ProxyFactory(); pf.setInterfaces(waiter.getClass().getInterfaces());
以上代码就指定了JdkDynamicAopProxy进行代理;
BeforeAdvice advice = new GreetingBeforeAdvice(); Waiter waiter = new NaiveWaiter(); //Spring提供的代理工厂 ProxyFactory pf = new ProxyFactory(); pf.setInterfaces(waiter.getClass().getInterfaces()); pf.setOptimize(true);
以上代码虽然指定了代理的接口,但由于setOptimize(true),所以还是使用了Cglib2AopProxy代理;
我们使用了addAdvice来添加一个增强,用户可以用该方法添加多个增强,形成一个增强链,调用顺序和添加顺序一致,下标从0开始:
package spring.aop.beforeadvicedemo; import java.lang.reflect.Method; import org.springframework.aop.MethodBeforeAdvice; public class GreetingBeforeAdvice2 implements MethodBeforeAdvice { @Override public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println( "我是第一个横切逻辑"); } }
public static void main(String[] args) { BeforeAdvice advice = new GreetingBeforeAdvice(); BeforeAdvice advice2 = new GreetingBeforeAdvice2(); Waiter waiter = new NaiveWaiter(); //Spring提供的代理工厂 ProxyFactory pf = new ProxyFactory(); pf.setInterfaces(waiter.getClass().getInterfaces()); pf.setOptimize(true); //设置代理目标 pf.setTarget(waiter); //为代理目标添加增强 pf.addAdvice(0,advice2); pf.addAdvice(1,advice); //生成代理实例 Waiter waiterProxy = (Waiter)pf.getProxy(); waiterProxy.greetTo("nicholaslee"); waiterProxy.serverTo("nicholaslee"); }
输出:
我是第一个横切逻辑
How are you! Mr.nicholaslee
great to nicholaslee
我是第一个横切逻辑
How are you! Mr.nicholaslee
serving nicholaslee
我们还可以将以上代码更加优化一下,可以通过依赖注入来实例化:
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" p:target-ref="targetWaiter" p:proxyTargetClass="true"> <property name="interceptorNames"> <list> <value>greetingAdvice</value> <value>greetingAdvice2</value> </list> </property> </bean>
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); Waiter waiter = (Waiter)ctx.getBean("waiter",Waiter.class); waiter.greetTo("nicholaslee"); }
输出:
How are you! Mr.nicholaslee
我是第一个横切逻辑
great to nicholaslee
参数说明:
target:代理的目标对象;
proxyInterfaces:代理索要实现的接口,可以是多个接口,另一个别名属性是interfaces;
interceptorNames:需要织入目标对象的Bean列表,必须是实现了MethodInterceptor或者aop.Advisor的Bean,配置的顺序对应调用顺序;
singleton:返回的代理是否单实例,默认为单实例;
optimize:当设置为true的时候,强制使用CGLib代理;
proxyTargetClass:是否对类进行代理(而不是针对接口进行代理),设置为true后,使用CGLib代理;
这个时候我们使用了JDK代理技术,如果我们想使用CGLib代理,则可以更改参数:
p:proxyTargetClass="true"
因为CGLib代理创建代理慢,但是创建的代理对象效率非常高,所以比较适合singleton的代理;
下面我们看一个后置增强,org.springframework.aop.AfterReturningAdvice,表示在目标方法执行后试试增强:
package spring.aop.afteradvicedemo; import java.lang.reflect.Method; import org.springframework.aop.AfterReturningAdvice; public class GreetingAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable { System.out.println("Please enjoy youself~"); } }
下面看一下环绕增强,org.aopalliance.intercept.MethodInterceptor,表示在目标方法执行前后实施增强:
package spring.aop.interceptoradvicedemo; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class GreetingInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation arg0) throws Throwable { Object[] args = arg0.getArguments();// 获取目标方法参数 String clientName = (String) args[0]; System.out.println("How are you:" + clientName); Object obj = arg0.proceed(); System.out.println("just enjoy yourself"); return obj; } }
在环绕增强时,arg0.proceed()通过proceed反射调用目标实例相应的方法;
下面是异常抛出增强:org.springframework.aop.ThrowsAdvice,表示在目标方法抛出异常后实施增强;异常抛出增强最适合的应用场景是事务管理,当参与事务的某个Dao发生异常时,事务管理器就必须去回滚事务,下面看一个模拟的例子:
package spring.aop.throwsadvicedemo; import java.lang.reflect.Method; import org.springframework.aop.ThrowsAdvice; public class TransactionManager implements ThrowsAdvice { //ThrowsAdvice异常抛出增强接口没有定义任何方法,它只是一个标示接口 //在运行期Spring使用反射的机制自行判断,我们必须采用以下签名的增强方法 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("成功回滚事务。"); } }
package spring.aop.throwsadvicedemo; import java.sql.SQLException; import org.springframework.aop.framework.ProxyFactory; public class UserServiceImpl { public void removeUser(int userId) { System.out.println("模拟删除用户:" + userId); throw new RuntimeException("运行异常。"); } public void addUser(int userId) { System.out.println("添加用户" + userId); throw new RuntimeException("数据库插入异常。"); } public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); TransactionManager tran = new TransactionManager(); ProxyFactory pf = new ProxyFactory(); pf.setTarget(userService); pf.addAdvice(tran); UserServiceImpl user = (UserServiceImpl)pf.getProxy(); user.removeUser(0); user.addUser(1); } }
输出:
模拟删除用户:0
---------------
method:removeUser
抛出异常:运行异常。
成功回滚事务。
添加用户1
---------------
method:addUser
抛出异常:数据库插入异常。
成功回滚事务。
也可以配置注入方式:
<bean id="throwsManager" class="spring.aop.throwsadvicedemo.TransactionManager" /> <bean id="throwsTarget" class="spring.aop.throwsadvicedemo.UserServiceImpl" /> <bean id="throwsAdvice" class="org.springframework.aop.framework.ProxyFactoryBean" p:target-ref="throwsTarget" p:proxyTargetClass="true" p:singleton="false" > <property name="interceptorNames"> <list> <value>throwsManager</value> </list> </property> </bean>
最后来看一下引介增强:org.springframework.aop.IntroductionInterceptor,表示在目标类中添加一些新的方法和属性;引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,我们可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介可以为目标类创建实现某接口的代理。
package spring.aop.introductionadvicedemo; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.support.DelegatingIntroductionInterceptor; import spring.aop.demo1.PerformanceMonitor; public class ControllablePerformanceMonitor extends DelegatingIntroductionInterceptor implements Monitorable { /** * */ private static final long serialVersionUID = -5983845636084465442L; private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>(); 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); } }
package spring.aop.introductionadvicedemo; public interface Monitorable { void setMonitorActive(boolean active); }
package spring.aop.introductionadvicedemo; import org.springframework.aop.framework.ProxyFactory; public class ForumService { public void removeUser(int userId) { System.out.println("模拟删除用户:" + userId); } public void addUser(int userId) { System.out.println("添加用户" + userId); } public static void main(String[] args) { ForumService forumService = new ForumService(); ControllablePerformanceMonitor advice = new ControllablePerformanceMonitor(); ProxyFactory pf = new ProxyFactory(); pf.setInterfaces(Monitorable.class.getInterfaces()); pf.setTarget(forumService); pf.setOptimize(true); pf.addAdvice(advice); ForumService forum = (ForumService)pf.getProxy(); Monitorable monitorAble =(Monitorable)forum; monitorAble.setMonitorActive(true); forum.removeUser(1); } }
输出:
begin monitor...
模拟删除用户:1
end monitor...
org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.removeUser花费32毫秒
说明:
1. Monitorable monitorAble =(Monitorable)forum; 我们可以这么转换,说明返回的代理实例确实引入了Monitorable接口方法的实现;
2. pf.setInterfaces(Monitorable.class.getInterfaces()); 引介增强需要制定所实现的接口;
3. pf.setOptimize(true); 由于只能通过为目标类创建子类的方式生成音节增强的代理,所以必须选择CGLib代理;