关于JDK动态代理和CGLIB动态代理

1. 代理模式 

一句话总结:为其他对象提供一种代理以控制对这个对象的访问。
千篇一律的介绍:代理模式是常用的java设计模式,他的特征是代理类与委托类(或目标类)有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 
按照代理的创建时期,代理类可以分为两种。 
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 
动态代理:在程序运行时,运用反射机制动态创建而成。 

2. 动态代理: JDK动态代理和CGLIB动态代理

2.1 JDK动态代理:

a. JDK动态代理是面向接口的,必须提供一个委托类和代理类都要实现的接口

b. JDK动态代理的实现主要使用java.lang.reflect包里的Proxy类和InvocationHandler接口。

实现:

 接口:

1 public interface ProxyInterface {
2     public void proxyMethod(String name);
3 }

 

目标类:

 

1 public class TargetObject implements ProxyInterface {
2     @Override
3     public void proxyMethod(String name) {
4         System.out.println(this.getClass().getName() + "  --->:" + name);
5     }
6 }

 

 

代理的最终操作类: 为通过反射机制实现的代理类提供方法的实现。实现InvocationHandler接口的invoke方法做代理类要做的事情

 1 public class JDKInvocationHandler implements InvocationHandler {
 2     private Object target;
 3 
 4     public JDKInvocationHandler(Object target) {
 5         this.target = target;
 6     }
 7 
 8     @Override
 9     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  //参数说明, proxy是生成的代理对象, method是代理的方法, args是方法接收的参数, 此代理类会代理目标类的所有方法
10         System.out.println("begin invoke...." + proxy.getClass().getName());  //目标类方法调用之前预处理
11 
12         method.invoke(target, args);
13 
14         System.out.println("end invoke...");  //目标类方法调用完成之后的后续处理
15         return null;
16     }
17 }

 

测试:

 1 public class Main {
 2     public static void main(String[] args){
 3         TargetObject target = new TargetObject();
 4 
 5         JDKInvocationHandler handler = new JDKInvocationHandler(target);  //建立代理类和目标类之间的关联关系
 6 
 7         ProxyInterface proxy = (ProxyInterface) Proxy.newProxyInstance( //通过反射机制创建和目标类实现相同接口的代理类
 8                 target.getClass().getClassLoader(),
 9                 target.getClass().getInterfaces(),
10                 handler
11         );
12 
13         proxy.proxyMethod("this reflect method use jdk origin method!!!"); //调用代理类的代理方法
14     }
15 }

 

输出:

begin invoke....com.sun.proxy.$Proxy0   //目标类方法调用之前预处理的输出
com.mrlu.proxy.jdk.TargetObject  --->:this reflect method use jdk origin method!!!  //目标类提供的实际服务的输出
end invoke...    //目标类方法调用之后处理的输出

 

 

总结: 通过输出可以看到, 实际调用的是com.sun.proxy.$Proxy0代理类的方法,  在代理类内部又调用了com.mrlu.proxy.jdk.TargetObject目标类的方法

注意:

  1. JDKInvocationHandler类的invoke方法,是最终通过放射生成的代理类的所有方法的实现,所以调用反射类的所有方法都会有预处理和调用之后处理的逻辑

  2. method.invoke(target, args); 是通过反射机制调用目标类的相同方法, 此处注意第一个参数代表要调用的方法所在的对象,一般都是目标对象。此处感觉和JS中的call和apply有点类似

     若此处写成了代理对象:method.invoke(proxy, args); //proxy是代理对象

    在执行的时候会无限递归下去, 因为通过调用代理对象的方法,会执行额外的处理逻辑,然后method.invoke(proxy, args);又调用代理对象的方法,又继续执行额外的处理逻辑, 然后不断重复执行,方法永远不能返回,无限的递归下去

2.2 CGLIB动态代理

a. 使用CGLIB动态代理不要求必须有接口,生成的代理对象是目标对象的子类对象,所以需要代理的方法不能是private或者final或者static的

b. 使用CGLIB动态代理需要有对cglib的jar包依赖

实现:

目标类:

 

 1 public class TargetObject {
 2     public void proxyMethod(String name) {
 3         System.out.println(this.getClass().getName() + "  --->:" + name);
 4     }
 5     private void privateMethod(){
 6         System.out.println("this is private method");
 7     }
 8     public final void finalMethod(){
 9         System.out.println("this is final method");
10     }
11     public static void staticMethod(){
12         System.out.println("this is static method");
13     }
14 }

 

 

 

代理的最终操作类:

 

 1 public class InvokeHandler implements MethodInterceptor{
 2     private Enhancer enhancer = new Enhancer();
 3 
 4     public Object getProxy(Class clazz){
 5         enhancer.setSuperclass(clazz);  //这两个set是必须的
 6         enhancer.setCallback(this);
 7         return enhancer.create();
 8     }
 9 
10     @Override
11     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
12         System.out.println("begin invoke... target object:" + o.getClass().getName());
13         System.out.println("begin invoke... methodProxy object:" + methodProxy);
14         System.out.println("method info:" + method.getName());
15 
16 //        method.invoke(o, objects);
17         methodProxy.invokeSuper(o, objects);
18 
19         System.out.println("end invoke...");
20         return null;
21     }
22 }

 

 

 

测试:

 

 1 public class Main {
 2     public static void main(String[] args){
 3         InvokeHandler invokeHandler = new InvokeHandler();
 4 
 5         TargetObject targetObject = (TargetObject) invokeHandler.getProxy(TargetObject.class);
 6 
 7         targetObject.proxyMethod("this reflect method use cglib method!!!");
 8         targetObject.finalMethod();
 9         targetObject.staticMethod();
10 }

 

 

 

输出:

 

begin invoke... target object:com.mrlu.proxy.cglib.TargetObject$$EnhancerByCGLIB$$afe67711
begin invoke... methodProxy object:org.springframework.cglib.proxy.MethodProxy@58e50f2c
method info:proxyMethod
com.mrlu.proxy.cglib.TargetObject$$EnhancerByCGLIB$$afe67711  --->:this reflect method use cglib method!!!
end invoke...
this is final method
this is static method

 

 

 

总结:

  1. 通过输出可以看出,最终调用的是com.mrlu.proxy.cglib.TargetObject的子类(也是代理类)com.mrlu.proxy.cglib.TargetObject$$EnhancerByCGLIB$$afe67711的方法

  2. private,final和static修饰的方法不能被代理

注意:

  1. CGLIB是通过实现目标类的子类来实现代理,不需要定义接口

  2. 生成代理对象使用最多的是通过Enhancer和继承了Callback接口的MethodInterceptor接口来生成代理对象,设置callback对象的作用是当调用代理对象方法的时候会交给callback对象的来处理

  3. 创建子类对象是通过使用Enhancer类的对象,通过设置enhancer.setSuperClass(Class class)和enhancer.setCallback(Callback callback)来创建代理对象

解释MethodInterceptor接口的intercept方法:

Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;

参数说明:  Object var1代表的是子类代理对象,  Method var2代表的是要调用的方法反射对象, 第三个参数是传递给调用方法的参数,前三个参数和JDK的InvocationHandler接口的invoke方法中参数含义是一样的

       第四个参数MethodProxy对象是cglib生成的用来代替method对象的,使用此对象会比jdk的method对象的效率要高

      如果使用method对象来调用目标对象的方法: method.invoke(var1, var3),则会陷入无限递归循环中, 因为此时的目标对象是目标类的子代理类对象

      MethodProxy类提供了两个invoke方法:

        public Object invokeSuper(Object obj, Object[] args) throws Throwable;

        public Object invoke(Object obj, Object[] args) throws Throwable;

           注意此时应该使用invokeSuper()方法,顾名思义调用的是父类的方法,若使用invoke方法,则需要提供一个目标类对象,但我们只有目标类子类代理对象,所以会陷入无限递归循环中。

CGLIB所创建的动态代理对象的性能比JDK所创建的动态代理对象的性能高很多,但创建动态代理对象时比JDK创建动态代理对象要花费更长的时间。

后记: 补充一些反射知识

1. 要想获取类的私有属性或者私有方法,则需要使用TargetObject.class.getDeclaredFields()和TargetObject.class.getDeclaredMethods(), 然后调用field和method的setAccessible(true)方法;

2. 使用TargetObject.class.getFields()和TargetObject.class.getMethods();方法是拿不到private,protected,和deafault修饰的属性和方法的

3. 通过子类获取父类的私有方法,需使用SubObject.class.getSuperclass().getDeclaredMethods();  直接使用SubObject.class.getDeclaredMethods();只能拿到子类的所有方法(属性类似)

 

posted @ 2015-12-19 16:25  桦沐  阅读(774)  评论(0编辑  收藏  举报