标签:
一、什么是AOP
AOP 是Aspect Oriented Programing 的简称,被译为“面向方面编程”。相信看到这个术语,刚接触的人肯定是很难理解的。下面个人就按照自己的理解将其解释下,如果有什么不妥的地方,还请指出~
一般情况下,如果我们的代码出现了很多重复的,比如在 Pig、Horse、Cat 等类中,它们都拥有共同的方法 eat(),run(), 按照软件重构的思想理念,我们可以将这些方法写到一个父类 Animal中,并将Pig、Horse 和 Cat 等继承它。这样确实减少了很多重复性的代码,但是有时情况并非如此简单。如下面的代码。
Service接口:
package com.zkp.service; public interface FormService { public void removeTopic(int topicId); public void removeMessage(int messageId); }
package com.zkp.service.impl; import com.zkp.service.FormService; public class FormServiceImpl implements FormService { private Monitor monitor; public void removeTopic(int topicId){ monitor.start(); System.out.println("模拟删除topic记录"); try{ Thread.currentThread().sleep(10); }catch(Exception e){ throw new RuntimeException(e); } monitor.end(); } public void removeMessage(int recordId){ monitor.start(); System.out.println("模拟删除消息记录"); try{ Thread.currentThread().sleep(10); }catch(Exception e){ throw new RuntimeException(e); } monitor.end(); } //省略各种初始化和set、get等方法 ... }对上面这种情况,每个列出的方法里面都包含了启动和结束监听器monitor的代码,假设我们这个monitor是用来监视一个函数运行时的性能的。对于这种情况,我们没法采取之前以继承父类的方式去消除这些重复代码。因此便有了AOP,将这些重复的代码以横向切割的方式抽取出来,放到一个独立的模块中,让原本的业务类里面只含有业务代码。
二、AOP术语
1、连接点(Joinpoint)
在程序执行到某个特定的位置,如一个函数执行前、执行后,或者是某个类初始化前和初始化后等等,这些地方都可以叫做连接点。连接点就是AOP把横切代码植入的地方。它由两个信息确定,一个是用方法表示的程序执行点,即确定到某个方法上。二是用相对点表示的方位,能够确定到方法内的某个地方。
2、切点(Pointcut)
每个程序类都拥有一些方法,这些方法都有多个连接点。但是我们如果要找到特定的感兴趣的连接点,就要通过“切点”来定位,而且需要注意的是,一个切点可以定位多个感兴趣的连接点。在Spring中,切点是通过 org.springframework.aop.Pointcut 接口进行描述的,它使用类和方法作为连接点的查找条件,因此切点只能定位到某个方法上。
3、增强(Advice)
增强就是我们常说的植入到目标类连接点上的一段横切代码。不过,它除了拥有这段代码逻辑的属性外,也包含了植入横切代码的方位信息。结合切点信息和方位信息,一段横切代码便能被准确地植入到某些特定的位置中了。由于增强带有方位信息,因此在 spring 中,它所包含的增强接口都是带方位名的,如AfterRetuningAdvice、ThrowsAdvice和BeforeAdvice等。
4、目标类(Target)
很容易理解,目标类就是我们要植入横切代码的目标类。在AOP的帮助下,我们可以将刚刚的 FormServiceImpl 类中有关 monitor 的代码抽取出来,让AOP动态地植入到相关的一些连接点中。
三、AOP实现
利用AOP思想向目标类植入横切代码时,个人理解是:它其实是生成了一个新的类,在这个新类的被增强的方法中,就包含了业务代码和横切代码,它也是我们常说的代理。虽然我们在spring中还是向往常一样去调用自己写的方法,但是对于被植入了横切代码的类,我们实质上是调用了它的一个代理,然后执行了横切逻辑和相关的业务代码。AOP底层也用到了Java的反射技术,它的实现有以下两种,
1、基于JDK动态代理
JDK动态代理主要涉及到java.lang.reflect包中的两个类,Proxy和InvocationHandler。其中InvocationHandler是个接口,可以通过实现此接口来定义横切逻辑,并通过反射调用目标类代码,动态地将横切逻辑和业务逻辑编织在一起。其中Handler代码如下所示。在invoke方法中,proxy是最终生成的代理实例,一般不会用到,method是被代理目标实例的某个具体方法,通过它可以发起对目标实例方法的反射调用;args是传递给代理实例某个方法的入参数组,在方法反射时调用:
package com.zkp.base; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class PerformanceHandler implements InvocationHandler{ private Object object; private Monitor monitor; /** * object作为目标业务类 * */ public PerformanceHandler(Object object){ this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { monitor.start(); // 性能监视代码 Object obj = method.invoke(object, args); monitor.end(); // 性能监视代码 return obj; } ... }
package com.zkp.service.impl; import com.zkp.service.FormService; public class FormServiceImpl implements FormService { public void removeTopic(int topicId){ System.out.println("模拟删除topic记录"); try{ Thread.currentThread().sleep(10); }catch(Exception e){ throw new RuntimeException(e); } } public void removeMessage(int recordId){ System.out.println("模拟删除消息记录"); try{ Thread.currentThread().sleep(10); }catch(Exception e){ throw new RuntimeException(e); } } }
package com.zkp.test; import java.lang.reflect.Proxy; import com.zkp.base.PerformanceHandler; import com.zkp.service.FormService; import com.zkp.service.impl.FormServiceImpl; public class TestFormService { public static void main(String[] args){ FormService formServiceImpl = new FormServiceImpl(); // 希望被代理的目标业务类 PerformanceHandler handler = new PerformanceHandler(formServiceImpl); // 将性能监视代码编织到该业务类中 // 利用newInstance()静态方法为handler船检一个符合FormService接口的代理实例 FormService proxy = (FormService) Proxy.newProxyInstance(formServiceImpl.getClass().getClassLoader(), formServiceImpl.getClass().getInterfaces(), handler); proxy.removeTopic(100); proxy.removeMessage(1000); } }由上述实现可以看出,JDK代理只能为接口创建代理实例,因为newProxyInstance()的第二个参数就是业务类的接口。
2、基于CGLib动态代理
对于没有通过接口定义业务方法的类,JDK无法搞定,因此CGLib就是一个替代者。CGLib采用非常底层的字节码技术,为一个类创建子类,并在自类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。它实现了 MethodInterceptor类:
package com.zkp.base; 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(); private Monitor monitor; public Object getProxy( Class superClass ){ enhancer.setSuperclass(superClass); // 设置需要创建子类的类 enhancer.setCallback(this); return enhancer.create(); // 通过字节码技术动态创建子类实例 } @Override public Object intercept(Object arg0, Method method, Object[] arg2, MethodProxy proxy) throws Throwable { monitor.start(); Object obj = proxy.invokeSuper(arg0, arg2); // 通过代理类调用父类中方法 monitor.end(); return obj; } ... }在上面的代码中,arg0为目标类实例,method为目标类方法的反射对象,arg2为动态入参,proxy为生成的代理类实例。注意到由于CGLib底层利用到了ASM,在引入cglib包的时候,我们也需要引入asm.jar包。
接下来利用CGLib为FormServiceImpl类创建代理对象,并测试代理对象方法,代码如下:
package com.zkp.test; import com.zkp.base.CGLibProxy; import com.zkp.service.impl.FormServiceImpl; public class TestCGLibProxy { public static void main(String[] args){ CGLibProxy proxy = new CGLibProxy(); FormServiceImpl formServiceImpl = (FormServiceImpl) proxy.getProxy(FormServiceImpl.class); formServiceImpl.removeMessage(10000); formServiceImpl.removeTopic(3000); } }
不过以上还有以下问题:
1、目标类所有方法都被织入了横切代码,而有时我们只需要对目标类的部分方法进行动态织入;
2、通过硬编码的方式指定了横切代码的织入点,即在一个方法的开始和结束织入横切逻辑;
3、手工编写创建代理的过程,对每个接口或者实现类都需要各自编写代码,没有做到通用。
四、JDK和CGLib比较
除了JDK只能代理接口而CGLib可以代理具体实现类的差别外,CGLib创建的代理对象在性能上会比JDK创建的高不少,但是CGLib在创建代理对象的时候花费的时间却比JDK多很多。因此,对于singleton的代理对象或者是具有实现池时,我们一般选用CGLib。而当需要频繁地创建对象时,我们使用JDK动态代理技术。此外,由于CGLib采用动态创建子类的方式生成代理对象,因此没法对目标类的final、private方法进行代理
标签:
原文地址:http://blog.csdn.net/zkp0601/article/details/51345750