Spring基础系列——AOP
基础知识
AOP是Aspect Oriented Programing 的简称,译为“面向切面编程”。涉及到的概念有连接点、切点、增强、目标对象、引介、织入、代理、切面。
织入:
1)编译期织入,需要特殊的java编译器;
2)类装载期织入,需要特殊的类装载器;
3)动态代理织入,在运行期为目标类添加增强生成子类的方法;
Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public interface ForumService { public void removeTopic(int topicId); } public class ForumServiceImpl implements ForumService{ public void removeTopic(int topicId) { System.out.println("delete topic record:" + topicId); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } }
时间统计类代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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(); } } 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+" cost " + elapse + " ms."); } }
如果想对此service方法进行性能统计,传统方法只能在removeTopic方法中添加统计代码,记录开始,结束的时间然后计算差值。但是这样代码工作量巨大而且不易维护。
采用动态代理之后
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * Author: Leo Sun * Blog: http://fuxinci.com/ * Date: 3/25/13 */ public class PerformanceHandler implements InvocationHandler { private Object target; public PerformanceHandler(Object target) { this.target = target; } 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; } }
测试方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static void main(String[] args) { // test dynamic proxy ForumService target = new ForumServiceImpl(); PerformanceHandler handler = new PerformanceHandler(target); ForumService proxy = (ForumService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); proxy.removeTopic(1012); }
采用动态代理有一个限制,那就是只能为借口创建代理,对于没有使用借口定义的业务类如何织入呢?可以采用CGLib实现。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * Author: Leo Sun * Blog: http://fuxinci.com/ * Date: 3/25/13 */ public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz) { enhancer.setSuperclass(clazz); enhancer.setCallback(this); return enhancer.create(); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { PerformanceMonitor.begin(obj.getClass().getName() + "." + method.getName()); Object result = methodProxy.invokeSuper(obj, args); PerformanceMonitor.end(); return null; } }
测试方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static void main(String[] args) { // test cglib CglibProxy cglibProxy = new CglibProxy(); ForumServiceImpl forumService = (ForumServiceImpl)cglibProxy.getProxy(ForumServiceImpl.class); forumService.removeTopic(1012); }
运行上面的代码输出以下信息:
begin monitor... delete topic record:1012 end monitor... org.sun.aop.ForumServiceImpl$$EnhancerByCGLIB$$a5b47e2a.removeTopic cost 38 ms.
输出的代理名字是ForumServiceImpl$$EnhancerByCGLIB$$a5b47e2a,这个特殊的类就是CGLib为ForumServiceImpl动态创建的子类。所以CGLib不能对目标类中的final方法进行代理。
关于性能:
有研究表明CGLib所创建的动态代理对象性能比jdk所创建的代理对象的性能高不少(大概10倍),但是CGLib在创建代理的时候所花的时间却比jdk动态代理多(大概8倍)。
增强类
按照增强在目标类的方法的连接点的位置,可以分为以下5种:前置增强、后置增强、环绕增强、异常抛出增强、引介增强。
1.前置增强:
目标类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * Author: Leo Sun * Blog: http://fuxinci.com/ * Date: 3/26/13 */ public interface Waiter { void greetTo(String name); void serveTo(String name); } public class NaiveWaiter implements Waiter { public void greetTo(String name) { System.out.println("Greet to " + name); } public void serveTo(String name) { System.out.println("Serve to " + name); } }
增强内容
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * Author: Leo Sun * Blog: http://fuxinci.com/ * Date: 3/26/13 */ public class GreetingBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] objects, Object o) throws Throwable { String clientName = (String) objects[0]; System.out.println("How are you! Mr." + clientName + "."); } }
测试类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import org.springframework.aop.BeforeAdvice; import org.springframework.aop.framework.ProxyFactory; /** * Author: Leo Sun * Blog: http://fuxinci.com/ * Date: 3/26/13 */ 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("Leo"); } }
2.后置增强:
实现AfterReturningAdvice
3.环绕增强:
实现MethodInterceptor
4.异常抛出增强
实现ThrowsAdvice
5.引介增强
它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强是类级别的,而非方法级别。
切面
在介绍增强时,增强织入到目标类的所有方法中,如果我们想织入到目标类的特定方法中,那么就要使用切点进行目标连接点的定位了。
静态普通方法名切面,静态正则表达式方法匹配切面,动态切面,流程切面,符合切点切面,引介切面。