首页 > 编程语言 > 详细

Java代理:静态代理、JDK动态代理和CGLIB动态代理

时间:2020-06-15 21:50:18      阅读:43      评论:0      收藏:0      [点我收藏+]

代理模式(英语:Proxy Pattern)是程序设计中的一种设计模式。所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。

代理模式分为静态代理动态代理(JDK、CGLIB)

静态代理

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

技术分享图片

上面的静态代理UML图的一个例子,代理类和目标类都实现相同的接口。

创建一个Animal接口。

1 package com.proxy.example;
2 
3 /**
4  *  an animation interface
5  */
6 public interface Animal {
7     void call();
8     void hobby();
9 }

创建目标类Cat并实现Animal接口。

 1 package com.proxy.example;
 2 
 3 public class Cat implements Animal {
 4     @Override
 5     public void call() {
 6         System.out.println("meow meow meow ~");
 7     }
 8 
 9     @Override
10     public void hobby(){
11         System.out.println("eating fish ~");
12     }
13 }

所以我们需要在目标对象Cat调用方法之前指出它已经饿了,该方法是使用静态代理来实现Cat的叫声,然后再进行调用。

 1 package com.proxy.example.staticproxy;
 2 
 3 import com.proxy.example.Animal;
 4 
 5 /**
 6  * 静态代理类, 实现了Animal接口
 7  */
 8 public class StaticProxyAnimal implements Animal {
 9     private Animal impl;    // 在代理对象中保存目标对象, 使代理类中持有目标对象
10 
11     /**
12      * 传入目标对象
13      *
14      * @param impl 目标对象
15      */
16     public StaticProxyAnimal(Animal impl) {
17         this.impl = impl;
18     }
19 
20     /**
21      * 调用代理对象中Cat#call的实现
22      */
23     @Override
24     public void call() {
25         System.out.println("Cat is hungry");
26         impl.call();
27     }
28 
29     @Override
30     public void hobby() {
31         // 接口每添加新方法,旧的在这里实现这个方法,灵活性不够,在大型的项目不易于维护
32     }
33 }

猫的饥饿和喵喵叫的行为是通过调用静态代理来实现的。

 1 package com.proxy.example.staticproxy;
 2 
 3 import com.proxy.example.Animal;
 4 import com.proxy.example.Cat;
 5 import org.junit.Test;
 6 
 7 public class Main {
 8     @Test
 9     public void staticProxy(){
10         Animal staticProxy = new StaticProxyAnimal(new Cat());
11         staticProxy.call(); // 猫的饥饿和喵喵叫行为是通过调用静态代理来实现的。
12     }
13 }

可以看到,通过保留目标类对象然后调用目标类的方法来实现静态代理。

但在某些情况下它还有明显的缺点:

  • 当我们将新方法添加到Animal接口时,不仅需要添加此方法的实现来实现Cat类,而且因为代理类实现了Animal接口,所以代理类还必须实现Animal的新方法,这在项目规模较大时不利于维护。
  • Animal调用的代理类实现是为Cat目标类的对象设置的。如果需要添加Dog目标类的代理,则必须为Dog类实现一个相应的代理类,这会使代理类的重用性变得不友好,并且太多的代理类难以维护。

在JDK动态代理中友好地解决了以上问题。

动态代理

JDK动态代理

动态代理类和静态代理类之间的主要区别在于,代理类的字节码不是在程序运行之前生成的,而是在程序运行时在虚拟机中自动创建的。

实现InvocationHandler接口

JDK动态代理类必须在反射包中实现java.lang.reflect.InvocationHandler接口,它负责实现接口的方法调用。

 1 package com.proxy.example.jdkdynamicproxy;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 
 6 public class TargetInvoker implements InvocationHandler {
 7     // 代理中的目标对象
 8     private Object target;
 9 
10     public TargetInvoker(Object target) {
11         this.target = target;
12     }
13 
14     /**
15      *
16      * @param proxy 代理目标对象的代理对象,它是真实的代理对象。
17      * @param method 方法执行目标类的方法
18      * @param args 执行目标类的方法的args参数
19      * @return
20      * @throws Throwable
21      */
22     @Override
23     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
24         System.out.println("jdk agent before invocation");
25         Object result = method.invoke(target, args);
26         System.out.println("jdk agent after invocation");
27         return result;
28     }
29 }

创建JDK动态代理类

创建JDK动态代理类的实例还使用反射包中的java.lang.reflect.Proxy类。通过调用代理Proxy#newproxyinstance静态方法创建。

 1 package com.proxy.example.jdkdynamicproxy;
 2 
 3 import java.lang.reflect.Proxy;
 4 
 5 public class DynamicProxyAnimal {
 6     public static Object getProxy(Object target) throws Exception {
 7         Object proxy = Proxy.newProxyInstance(
 8                 target.getClass().getClassLoader(), // 目标类的类加载器
 9                 target.getClass().getInterfaces(),  // 代理程序需要实现的接口,可以指定多个,这是一个数组
10                 new TargetInvoker(target)   // 交由InvocationHandler接口实现类里的invoke()方法去处理
11         );
12         return proxy;
13     }
14 }

通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:

  1. 使用的ClassLoader,通常就是接口类的ClassLoader

  2. 需要实现的接口数组,至少需要传入一个接口进去;

  3. 用来处理接口方法调用的实例InvocationHandler 。

最后,实现动态代理。

 1 package com.proxy.example.jdkdynamicproxy;
 2 
 3 import com.proxy.example.Animal;
 4 import com.proxy.example.Cat;
 5 import org.junit.Test;
 6 
 7 public class Main {
 8     @Test
 9     public void jdkDynamicProxy() throws Exception {
10         Cat cat = new Cat();
11         Animal proxy = (Animal) DynamicProxyAnimal.getProxy(cat);   // 将返回的Object强制转型为接口
12         proxy.call();
13     }
14 }

JDK在运行期动态创建class字节码并加载的过程。动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给 InvocationHandler 完成的。保存动态生成的代理的方法:

1 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");

CGLIB动态代理

CGLIB (Code Generation Library),一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP等。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。

CGLIB动态代理的实现机制是生成目标类的子类 Enhancer#create,该目标类是通过调用父类(目标类)的方法来实现的,然后在调用父类的方法时在代理中进行增强。

实现MethodInterceptor接口

与JDK动态代理的实现相比,CGLIB动态代理不需要实现与目标类相同的接口,而是通过方法拦截来实现代理。

 1 package com.proxy.example.cglibproxy;
 2 
 3 import org.springframework.cglib.proxy.MethodInterceptor;
 4 import org.springframework.cglib.proxy.MethodProxy;
 5 
 6 import java.lang.reflect.Method;
 7 
 8 /**
 9  * 方法拦截器——通过方法拦截接口调用目标类的方法, 然后增强被拦截的方法. 方法拦截器接口的拦截方法中有四个参数
10  */
11 public class TargetInterceptor implements MethodInterceptor {
12 
13     /**
14      * 拦截方法
15      *
16      * @param obj obj代理类对象
17      * @param method 代理当前拦截的方法
18      * @param args 拦截方法参数
19      * @param methodProxy 与目标类相对应的代理类的代理方法
20      * @return result
21      * @throws Throwable 抛出的异常
22      */
23     @Override
24     public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
25         System.out.println("CGLIB Before Calling");
26         Object result = methodProxy.invokeSuper(obj, args);
27         System.out.println("CGLIB After Calling");
28         return result;
29     }
30 }

创建CGLIB动态代理类

使用 Enhancer 类创建CGLIB动态代理类。它是CGLIB动态代理中的核心类。

 1 package com.proxy.example.cglibproxy;
 2 
 3 import org.springframework.cglib.proxy.Enhancer;
 4 
 5 /**
 6  * 使用Enhancer类创建CGLIB动态代理类. 它是CGLIB动态代理中的核心类.
 7  */
 8 public class CglibProxy {
 9     public static Object getProxy(Class<?> clazz) {
10         Enhancer enhancer = new Enhancer();
11         enhancer.setClassLoader(clazz.getClassLoader());
12         enhancer.setSuperclass(clazz);
13         enhancer.setCallback(new TargetInterceptor());
14         return enhancer.create();
15     }
16 }

可以通过设置代理类的信息以及代理类所拦截的方法的回调执行逻辑来实现代理类。

CGLIB动态代理调用:

 1 package com.proxy.example.cglibproxy;
 2 
 3 import com.proxy.example.Animal;
 4 import com.proxy.example.Cat;
 5 import org.junit.Test;
 6 import org.springframework.cglib.core.DebuggingClassWriter;
 7 
 8 public class Main {
 9     @Test
10     public void cglibDynamicProxy() throws Exception {
11         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
12         Animal cat = (Animal) CglibProxy.getProxy(Cat.class);
13         cat.call();
14     }
15 }

上面是在简单应用程序中实现CGLIB动态代理的方式。但是, Enhancer是常用的,并且在回调过滤器的使用中具有特色。当它拦截目标对象的方法时,它可以有选择地执行方法拦截,即选择代理方法的增强处理。现在再添加一个方法拦截实现:

 1 package com.proxy.example.cglibproxy;
 2 
 3 import org.springframework.cglib.proxy.MethodInterceptor;
 4 import org.springframework.cglib.proxy.MethodProxy;
 5 
 6 import java.lang.reflect.Method;
 7 
 8 /**
 9  * 方法拦截器2
10  */
11 public class TargetInterceptor2 implements MethodInterceptor {
12     @Override
13     public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
14         System.out.println("CGLIB Before calling TargetInterceptor2");
15         Object result = methodProxy.invokeSuper(obj, args);
16         System.out.println("CGLIB After calling TargetInterceptor2");
17         return result;
18     }
19 }

实现回调过滤器 CallbackFilter。

 1 package com.proxy.example.cglibproxy;
 2 
 3 import org.springframework.cglib.proxy.CallbackFilter;
 4 
 5 import java.lang.reflect.Method;
 6 
 7 /**
 8  * 回调过滤器
 9  */
10 public class TargetCallbackFilter implements CallbackFilter {
11     /**
12      * 使用Enhancer#setcallbacks设置多个方法拦截器的数组中的方法拦截器
13      *
14      * @param method 方法
15      * @return 返回的数字是数组的索引
16      */
17     @Override
18     public int accept(Method method) {
19         if ("hobby".equals(method.getName())) {
20             return 1;
21         } else {
22             return 0;
23         }
24     }
25 }

要演示如何调用不同的方法拦截器,需要在Enhancer设置中,使用 Enhancer#setcallbacks 设置多个方法拦截器。该参数是一个数组。 Targetcallbackfilter#accept返回的数字是数组的索引,它确定要回调哪个方法拦截器。

 1 package com.proxy.example.cglibproxy;
 2 
 3 import org.springframework.cglib.proxy.Callback;
 4 import org.springframework.cglib.proxy.Enhancer;
 5 
 6 public class CglibProxy2 {
 7     public static Object getProxy(Class<?> clazz) {
 8         Enhancer enhancer = new Enhancer();
 9         enhancer.setClassLoader(clazz.getClassLoader());
10         enhancer.setSuperclass(clazz);
11         enhancer.setCallbacks(new Callback[]{new TargetInterceptor(), new TargetInterceptor2()});   // 设置回调数组
12         enhancer.setCallbackFilter(new TargetCallbackFilter()); // 设置回调过滤器
13         return enhancer.create();
14     }
15 }

根据代码实现逻辑,调用喵喵加的方法将调用 TargetInterceptor 类,而兴趣方法将调用 TargetInterceptor2 类。

1     @Test
2     public void dynamicProxy() throws Exception {
3         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./");
4         Animal cat = (Animal) CglibProxy2.getProxy(Cat.class);
5         cat.call();
6         cat.hobby();
7     }

执行结果如下:

技术分享图片

 

 

总结:JDK动态代理实现目标类的接口,然后在构造动态代理时将目标类作为参数传递,因此代理对象保存目标对象,然后通过代理对象的 InvocationHandler 实现动态代理的操作。

CGLIB动态代理将通过配置目标类信息并使用ASM字节码框架来生成目标类的子类。调用代理方法时,代理的操作是通过拦截该方法来实现的。

通常,JDK动态代理使用接口来实现代理,而CGLIB动态代理使用继承来实现代理。

 

参考参考一参考二

Java代理:静态代理、JDK动态代理和CGLIB动态代理

原文:https://www.cnblogs.com/magic-sea/p/13137780.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!