JAVA中的代理技术(静态代理和动态代理)
最近看书,有两个地方提到了动态代理,一是在Head First中的代理模式,二是Spring AOP中的AOP。所以有必要补充一下动态代理的相关知识。
Spring采用JDK动态代理和CGLib动态代理的技术在运行期实现AOP。要使用JDK动态代理,目标类必须实现相应的接口,而CGLib不对目标类做任何限制,它通过动态生成目标子类的方式提供代理。JDK在创建代理对象时性能高于CGLib,而生成的代理对象的性能却比CGLib低,如果是singleton的代理,推荐使用CGLib动态代理。-----《Spring3.0就是这么简单p73》
Java在java.lang.reflect包中有自己的代理支持,利用这个包你可以在运行时动态地创建一个代理类,实现一个或者多个接口,并将方法的调用转发到你所指定的类。-----《Head First设计模式p474》
在设计模式中,代理的目的有很多种,包括将请求转发到远端主题、给创建开销大的对象对象提供代表等等;在Spring AOP中提到代理类,基本上是指在普通业务类的基础上实现日志、性能统计、事务的增强。下面所描述的内容也以Spring AOP为基础。
一、静态代理
静态代理是指在编译的时候就生成代理类,因此也成为编译时增强。
实现静态代理常用的方法是通过AspectJ,AspectJ 是一个基于 Java 语言的 AOP 框架,提供了强大的 AOP 功能,主要包含两个部分,第一个部分定义了如何表达、定义 AOP 编程中的语法规范;另一个部分是工具部分,包括编译器、调试工具等。
使用AspectJ的流程是先定义业务类,然后用AspectJ提供的工具生成代理类,在此不详细介绍,如要详细了解,请参考下面的文档链接。
二、动态代理
无需使用任何特殊命令对 Java 源代码进行编译,它采用运行时动态地、在内存中临时生成“代理类”的方式来生成代理。有以下两种使用使用动态代理的方式:
(1)CGLib动态代理
CGLIB(Code Generation Library),简单来说,就是一个高效的代码生成类库,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强。在Spring AOP中,它在运行期间生成的代理对象是针对目标类扩展的子类。
(2)JDK动态代理
Spring AOP中建议使用JDK动态代理。此时其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
Spring到底选择那种代理模式?如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理,所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。上说的是默认情况下,也可以手动配置一些选项使Spring采用CGLIB代理。
org.springframework.transaction.interceptor.TransactionProxyFactoryBean是org.springframework.aop.framework. ProxyConfig的子类,所以可以参照ProxyConfig里的一些设置,将optimize和proxyTargetClass任意一个设置为true都可以强制Spring采用CGLIB代理。
下面重点介绍一下Java的动态代理技术:
java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器 static InvocationHandler getInvocationHandler(Object proxy) // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) // 方法 3:该方法用于判断指定类对象是否是一个动态代理类 static boolean isProxyClass(Class cl) // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象,第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行 Object invoke(Object proxy, Method method, Object[] args)
实现一个动态添加前后日志的全部逻辑:
package newjava; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //普通对象和代理对象都要实现的接口 interface PersonInterface { void meeting(); } //普通对象 class Person implements PersonInterface { @Override public void meeting() { System.out.println("meeting..."); } } //代理处理器 class PersonHander implements InvocationHandler { Person person; PersonHander(Person p) { person = p; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //进行前后织入 if (method.getName().equals("meeting")) { System.out.println("good morning before metting"); Object obj = method.invoke(person, args); //调用原始类的meeting方法 System.out.println("good afternoon after metting"); return obj; } return null; } } //测试类 public class ProxyTest { public static void main(String[] args) { //普通对象 Person p = new Person(); //创建代理对象,必须转换为实现的接口 PersonInterface personProxy = (PersonInterface) Proxy.newProxyInstance( p.getClass().getClassLoader(), p.getClass().getInterfaces(), new PersonHander(p)
); //调用被代理后的方法,其实就是invoke方法,参数:代理对象本身,meeting对应的Method对象、实际参数都会传递到invoke里面去 personProxy.meeting(); } }
参考:
http://www.ibm.com/developerworks/cn/java/j-lo-springaopcglib/
http://www.cnblogs.com/phoebus0501/archive/2011/03/11/1980569.html
http://www.flyne.org/article/255