• Java代理(静态代理、JDK动态代理、CGLIB动态代理)


    Java中代理有静态代理动态代理。静态代理的代理关系在编译时就确定了,而动态代理的代理关系是在运行期确定的。静态代理实现简单,适合于代理类较少且确定的情况,而动态代理则给我们提供了更大的灵活性。

    Java中动态代理有JDK原生动态代理CGLIB动态代理两种。前者本质上是根据定好的接口动态生成静态代理类(该接口的实现类);后者则不需要事先定好接口而是可以直接根据类进行动态代理,其本质是根据指定的类动态生成静态代理类(指定的类的子类)。动态代理中,被代理的对象的所有方法都会被代理,除非在代理逻辑中进行方法筛选。

    本质

    JDK原生动态代理:生成被代理对象所实现的接口的实现类;实现类中每个方法调用被代理对象的相应方法,只不过在调用前后加上了额外逻辑;要求被代理对象实现接口

    CGLIB动态代理:生成被代理对象的子类;子类中的每个方法调用被代理对象(父类对象)中的相应方法,只不过在调用前后加上了额外处理;要求被代理对象不能被final修饰

    动态代理是IOC、AOP等技术的基础。

    静态代理和JDK动态代理

    Dynamic proxies allow one single class with one single method to service multiple method calls to arbitrary classes with an arbitrary number of methods. A dynamic proxy can be thought of as a kind of Facade, but one that can pretend to be an implementation of any interface. Under the cover, it routes all method invocations to a single handler – the invoke() method.   https://www.baeldung.com/java-dynamic-proxies

    示例:

    1 package com.marchon.proxytest;
    2 
    3 public interface IUserService {
    4     public String getUserName();
    5     public Integer getAge(String userName);
    6 }
    IUserService
     1 package com.marchon.proxytest;
     2 
     3 /**
     4  * 被代理对象
     5  * 
     6  * @author zsm
     7  *
     8  */
     9 public class UserServiceImpl implements IUserService {
    10 
    11     @Override
    12     public String getUserName() {
    13         String res = this.getClass() + ":hello";
    14         System.out.println(res);
    15         return res;
    16     }
    17 
    18     @Override
    19     public Integer getAge(String userName) {
    20         Integer age = 20;
    21         System.out.println(age);
    22         return age;
    23     }
    24 
    25 }
    UserServiceImpl
     1 package com.marchon.proxytest;
     2 
     3 /**
     4  * 代理对象(静态代理)<br>
     5  * 缺点:<br>
     6  * 1、代理类和被代理类实现相同的接口,代码重复、得为每个接口都实现相应的实现从而维护成本高 2、代理对象只服务于被代理对象,即每个被代理对象都得实现相应的代理对象
     7  * 
     8  * @author zsm
     9  *
    10  */
    11 class UserServiceStaticProxy implements IUserService {
    12     private IUserService proxiedObj;
    13 
    14     public UserServiceStaticProxy(IUserService proxiedObj) {
    15         if (proxiedObj instanceof UserServiceStaticProxy) {
    16             throw new RuntimeException("illegal proxiedObj proxied object");
    17         }
    18 
    19         this.proxiedObj = proxiedObj;
    20     }
    21 
    22     @Override
    23     public String getUserName() {
    24         System.out.println("before");
    25         String res = proxiedObj.getUserName();
    26         System.out.println("after");
    27         return res;
    28 
    29     }
    30 
    31     @Override
    32     public Integer getAge(String userName) {
    33         System.out.println("before");
    34         Integer age = proxiedObj.getAge(userName);
    35         System.out.println("after");
    36         return age;
    37     }
    38 
    39 }
    40 
    41 public class Main_StaticProxy {
    42     public static void main(String[] args) {
    43         IUserService proxiedObj = new UserServiceImpl();
    44 
    45         // UserServiceStaticProxy proxy = new UserServiceStaticProxy(proxiedObj);
    46         IUserService proxyObject = new UserServiceStaticProxy(proxiedObj);
    47         proxyObject.getUserName();
    48         proxyObject.getAge("zhangsan");
    49     }
    50 }
    StaticProxy 
      1 package com.marchon.proxytest;
      2 
      3 import java.io.FileOutputStream;
      4 import java.io.IOException;
      5 import java.lang.reflect.InvocationHandler;
      6 import java.lang.reflect.Method;
      7 import java.lang.reflect.Proxy;
      8 
      9 import sun.misc.ProxyGenerator;
     10 
     11 /**
     12  * 代理对象(动态代理)<br>
     13  * 动态代理:在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能。动态代理主要分为JDK动态代理和cglib动态代理两大类<br>
     14  * 这里介绍jdk动态代理,其本质上是在运行时动态产生一个实现指定接口的静态代理类,指定接口的所有非final方法(包括继承的非final方法如toString)均会被代理。
     15  * 
     16  * @author zsm
     17  *
     18  */
     19 
     20 class JdkDynamicProxyTemplate implements InvocationHandler {
     21     private Object proxiedObj;
     22 
     23     public JdkDynamicProxyTemplate(Object proxiedObj) {
     24         this.proxiedObj = proxiedObj;
     25     }
     26 
     27     @Override
     28     public Object invoke(Object proxyObj, Method method, Object[] args) throws Throwable {
     29         // if (method.getName().equals("getUserName")) {//为避免所有非final方法都被代理,可作此判断
     30         //
     31         // }
     32 
     33         // System.out.println(proxyObj);//stack overflow, why?
     34         // System.out.println(method);// public abstract java.lang.String com.marchon.proxytest.IUserService.getUserName()
     35 
     36         System.out.println("before");
     37         Object res = method.invoke(proxiedObj, args);
     38         System.out.println("after");
     39 
     40         return res;
     41     }
     42 
     43 }
     44 
     45 public class Main_JdkDynamicProxy {// 参阅:https://www.jianshu.com/p/269afd0a52e6
     46     public static void main(String[] args) {
     47         IUserService proxiedObj = new UserServiceImpl();
     48 
     49         JdkDynamicProxyTemplate proxyTemplate = new JdkDynamicProxyTemplate(proxiedObj);
     50 
     51         // 第一个参数是指定代理类的类加载器(我们传入当前测试类的类加载器)
     52         // 第二个参数是代理类需要实现的接口(我们传入被代理类实现的接口,这样生成的代理类和被代理类就实现了相同的接口)
     53         // 第三个参数是invocation handler,用来处理方法的调用。这里传入我们自己实现的handler
     54         IUserService proxyObject = (IUserService) Proxy.newProxyInstance(proxiedObj.getClass().getClassLoader(),
     55                 proxiedObj.getClass().getInterfaces(), proxyTemplate);// 创建包含被代理对象各方法的代理对象,显然可知:该代理对象实现了所传接口中(这里为IUserService)定义的各方法、代理对象的各方法实现为直接调用proxyTemplate相应的各方法实现。可以使用sum.misc下的ProxyGenerator生成动态代理类的字节码文件,再反编译出动态代理类源码
     56 
     57         proxyObject.getUserName();
     58         proxyObject.getAge("zhangsan");
     59         proxyObject.toString();// 所有非final方法都会被代理,包括从Object继承的等
     60 
     61         {
     62             // 获取代理类字节码文件
     63             String path = "$Proxy0.class";
     64             byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", proxiedObj.getClass().getInterfaces());
     65             FileOutputStream out = null;
     66 
     67             try {
     68                 out = new FileOutputStream(path);
     69                 out.write(classFile);
     70                 out.flush();
     71             } catch (Exception e) {
     72                 e.printStackTrace();
     73             } finally {
     74                 try {
     75                     out.close();
     76                 } catch (IOException e) {
     77                     e.printStackTrace();
     78                 }
     79             }
     80         }
     81 
     82         // jdk动态代理动态产生的代理类类似于如下静态代理
     83         System.out.println();
     84         System.out.println("equivalent static proxy test:");
     85         new TmpEquivalentStaticProxy(proxyTemplate).getUserName();
     86 
     87     }
     88 }
     89 
     90 /**
     91  * 
     92  * 上述生成的动态代理生成的代理类实际是类似于本类
     93  * 
     94  * @author zsm
     95  *
     96  */
     97 class TmpEquivalentStaticProxy implements IUserService {// 真正由jdk动态代理生成的代理类还 extends Proxy
     98     private InvocationHandler invokeHandler;
     99 
    100     public TmpEquivalentStaticProxy(InvocationHandler invokeHandler) {
    101         this.invokeHandler = invokeHandler;
    102     }
    103 
    104     @Override
    105     public String getUserName() {
    106         Object[] args = null;
    107         Method getUserNameMethod = IUserService.class.getMethods()[0];// public abstract java.lang.String
    108                                                                         // com.marchon.proxytest.IUserService.getUserName()
    109         try {
    110             return (String) invokeHandler.invoke(this, getUserNameMethod, args);
    111         } catch (Throwable e) {
    112             e.printStackTrace();
    113             return null;
    114         }
    115 
    116     }
    117 
    118     @Override
    119     public Integer getAge(String userName) {
    120         Object[] args = new Object[] { userName };
    121         try {
    122             Method getAgeMethod = IUserService.class.getMethods()[1];// public abstract java.lang.Integer
    123                                                                         // com.marchon.proxytest.IUserService.getAge(java.lang.String)
    124             return (Integer) invokeHandler.invoke(this, getAgeMethod, args);
    125         } catch (Throwable e) {
    126             e.printStackTrace();
    127             return null;
    128         }
    129 
    130     }
    131 
    132 }
    JdkDynamic

    springframework中的一个jdk动态代理示例:

    由上可见,静态代理的各个方法具有共性:每个方法内的前后都做额外其他处理、中间调用被代理对象的相应方法。既然有共性,那就可以抽取共性以减少重复,也即将静态代理的方法抽象出一个”模板“,这样就不需要在代理对象中针对被代理对象的每个方法写额外逻辑,这其实就变成了动态代理。

    JDK动态代理实际上是自动生成一个静态代理类并创建相应实例。代理类默认继承Porxy类,因为Java中只支持单继承,所以JDK动态代理只能去实现接口;代理类的方法都会去调用InvocationHandler的invoke()方法,故此需重写InvocationHandler的invoke()方法。

    JDK动态代理为我们提供了非常灵活的代理机制,但也有不足:

    被代理对象的所有非final方法(如从Object继承的toString、equals等)都会被代理(即都会在方法前后做与InvocationHandler的invoke()方法前后同样的处理,当然我们可以通过在invoke里对method name加以判断避免此情况),然而有时候我们并不希望这些方法被代理。

    JDK动态代理是基于接口的(生成的动态代理类实际上extends Proxy implements IUserService)。如果要被代理的对象没有实现接口,该如何实现代理呢?可用下面要介绍的CGLIB动态代理。

    从上面讨论可得知,静态代理和动态代理在运行时是一样的(都是静态代理了),其差异在运行时之前(即编译期)才存在,差异体现在开发者是否需要分别在每个与被代理方法对应的代理方法的前后写额外的处理逻辑:静态代理需要而动态代理不需要(后者不需要是因为该任务从由开发者负责转交由JVM来负责了)。因此,动态代理相比于静态代理的主要优势是为开发者提供便利,不用像静态代理那写重复的代理方法逻辑。

    CGLIB动态代理(springframework)

    与JDK动态代理不同,CGLIB(Code Generation Library)动态代理不需要事先定义接口,而是可以直接对类进行动态代理。CGLIB动态代理中被代理对象的所有非final方法默认也会被代理。

    示例:

     1 package com.marchon.proxytest;
     2 
     3 import java.lang.reflect.Method;
     4 
     5 import org.springframework.cglib.proxy.Enhancer;
     6 import org.springframework.cglib.proxy.MethodInterceptor;
     7 import org.springframework.cglib.proxy.MethodProxy;
     8 
     9 /**
    10  * 代理对象(动态代理)<br>
    11  * 动态代理:在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能。动态代理主要分为JDK动态代理和cglib动态代理两大类<br>
    12  * 这里介绍cglib动态代理,其本质上是在运行时动态产生一个实现继承指定类的的静态代理类,指定类的所有非final方法(包括继承的非final方法如toString)均会被代理。
    13  * 
    14  * @author zsm
    15  *
    16  */
    17 
    18 class CglibDynamicProxyTemplate implements MethodInterceptor {
    19 
    20     @Override
    21     public Object intercept(Object proxyObj, Method proxiedMethod, Object[] args, MethodProxy proxyMethod) throws Throwable {
    22         // if (method.getName().equals("sayHello")) {//为避免所有非final方法都被代理,可作此判断
    23         //
    24         // }
    25 
    26         // System.out.println(proxyObj);//stack overflow, why?
    27         // System.out.println(proxiedMethod);// public java.lang.String
    28         // com.marchon.proxytest.HelloConcrete.sayHello(java.lang.String)
    29         // System.out.println(proxyMethod);// org.springframework.cglib.proxy.MethodProxy@108c4c35
    30 
    31         System.out.println("before");
    32         Object res = proxyMethod.invokeSuper(proxyObj, args);
    33         System.out.println("after");
    34         return res;
    35     }
    36 
    37 }
    38 
    39 class HelloConcrete {
    40     public String sayHello(String username) {
    41         return "hello " + username;
    42     }
    43 
    44     public final String getAddress() {
    45         return "beijing";
    46     }
    47 }
    48 
    49 public class Main_CglibDynamicProxy {
    50     public static void main(String[] args) {
    51         CglibDynamicProxyTemplate proxyTemplate = new CglibDynamicProxyTemplate();
    52 
    53         Enhancer enhancer = new Enhancer();
    54         enhancer.setSuperclass(HelloConcrete.class);
    55         enhancer.setCallback(proxyTemplate);
    56 
    57         HelloConcrete proxyObject = (HelloConcrete) enhancer.create();
    58         System.out.println(proxyObject.sayHello("zhangsan"));// 会创建包含被代理对象所有非final方法的代理类,实际上代理类是被代理类的子类,故不会代理final方法;代理对象的各方法实现为直接调用proxyTemplate相应的各方法实现
    59         proxyObject.hashCode();// 所有非final方法都会被代理,包括从Object继承的等
    60         proxyObject.getAddress();// final方法不会被代理
    61 
    62         // jdk动态代理动态产生的代理类类似于如下静态代理
    63         System.out.println();
    64         System.out.println("equivalent static proxy test:");
    65         new TmpEquivalentStaticProxyOfCglib(proxyTemplate).sayHello("zhangsan");
    66     }
    67 }
    68 
    69 class TmpEquivalentStaticProxyOfCglib extends HelloConcrete {// 真正由cglib动态代理生成的代理类还 implements Factory
    70     private MethodInterceptor methodInterceptor;
    71 
    72     public TmpEquivalentStaticProxyOfCglib(MethodInterceptor methodInterceptor) {
    73         this.methodInterceptor = methodInterceptor;
    74     }
    75 
    76     @Override
    77     public String sayHello(String username) {
    78         Object[] args = null;
    79         Method proxiedMethod = super.getClass().getMethods()[0];
    80         MethodProxy proxyMethod = null;// this.getClass().getMethods()[0];//不造如何获取MethodProxy对象,故此方法实际上跑不了
    81 
    82         try {
    83             return (String) methodInterceptor.intercept(this, proxiedMethod, args, proxyMethod);
    84         } catch (Throwable e) {
    85             e.printStackTrace();
    86             return null;
    87         }
    88 
    89     }
    90 
    91     // 无法override getAddress方法
    92 }
    CglibDynamicProxy

    由上可见,此动态代理本质上是在运行时动态根据指定的类继承实现一个子类(故指定的类不能是final的),在子类中重写方法,方法的实现为调用MethodInterceptor中的intercept方法。

    参考资料

    https://www.jianshu.com/p/269afd0a52e6

    https://www.cnblogs.com/liuyun1995/p/8144628.html

    https://www.cnblogs.com/CarpenterLee/p/8241042.html JDK动态代理和CGLIB动态代理

    https://blog.csdn.net/difffate/article/details/70552056 CGLIB动态代理 

  • 相关阅读:
    电力三维基础信息平台
    基于三维GIS技术的输电线路地理信息系统的设计与实现
    linux学习笔记(1)
    linux学习笔记(4)
    linux学习笔记(8)
    linux学习笔记(6)
    linux学习笔记(3)
    linux 学习笔记(2)
    linux学习笔记(7)
    ASCII码表完整版
  • 原文地址:https://www.cnblogs.com/z-sm/p/10757225.html
Copyright © 2020-2023  润新知