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

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

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

静态代理

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

静态代理UML

上面的静态代理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)   // 构建AOP的advice, 这里需要传入业务类的实例
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, "./");

动态代理原理:

 1     @CallerSensitive
 2     public static Object newProxyInstance(ClassLoader loader,
 3                                           Class<?>[] interfaces,
 4                                           InvocationHandler h)
 5         throws IllegalArgumentException
 6     {
 7         Objects.requireNonNull(h);
 8 
 9         final Class<?>[] intfs = interfaces.clone();
10         final SecurityManager sm = System.getSecurityManager();
11         if (sm != null) {
12             checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
13         }
14 
15         /*
16          * 获取代理类
17          */
18         Class<?> cl = getProxyClass0(loader, intfs);
19 
20         /*
21          * Invoke its constructor with the designated invocation handler.
22          */
23         try {
24             if (sm != null) {
25                 checkNewProxyPermission(Reflection.getCallerClass(), cl);
26             }
27 
28             final Constructor<?> cons = cl.getConstructor(constructorParams);  // 获取带有InvocationHandler参数的构造方法
29             final InvocationHandler ih = h;
30             if (!Modifier.isPublic(cl.getModifiers())) {
31                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
32                     public Void run() {
33                         cons.setAccessible(true);
34                         return null;
35                     }
36                 });
37             }
38             return cons.newInstance(new Object[]{h});  // 把handler传入构造方法生成实例
39         } catch (IllegalAccessException|InstantiationException e) {
40             throw new InternalError(e.toString(), e);
41         } catch (InvocationTargetException e) {
42             Throwable t = e.getCause();
43             if (t instanceof RuntimeException) {
44                 throw (RuntimeException) t;
45             } else {
46                 throw new InternalError(t.toString(), t);
47             }
48         } catch (NoSuchMethodException e) {
49             throw new InternalError(e.toString(), e);
50         }
51     }

其中 getProxyClass(loader, interfaces) 方法用于获取代理类,它主要做了三件事情:在当前类加载器的缓存里搜索是否有代理类,没有则生成代理类并缓存在本地JVM里。

 1     /**
 2      * 生成代理类。在调用此方法之前, 必须调用checkProxyAccess方法执行权限检查。
 3      */
 4     private static Class<?> getProxyClass0(ClassLoader loader,
 5                                            Class<?>... interfaces) {
 6         if (interfaces.length > 65535) {
 7             throw new IllegalArgumentException("interface limit exceeded");
 8         }
 9 
10         // 如果由实现了给定接口的给定加载器定义的代理类存在, 则将简单地返回缓存的副本;
11         // 否则, 它将通过ProxyClassFactory创建代理类
12         return proxyClassCache.get(loader, interfaces);
13     }

其中,代理类的生成主要是以下这两行代码。

1   byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
2                 proxyName, interfaces, accessFlags);  // 生成代理类的字节码文件并保存
3 
4   defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);  // 使用类加载器将字节码加载到内存中

动态代理在运行期通过接口动态生成代理类,这为其带来了一定的灵活性,但这个灵活性却带来了两个问题,第一代理类必须实现一个接口,如果没实现接口会抛出一个异常。第二性能影响,因为动态代理使用反射的机制实现的,首先反射肯定比直接调用要慢。其次,使用反射大量生成类文件可能引起 Full GC 造成性能影响,因为字节码文件加载后会存放在 JVM 运行时区的方法区(或者叫持久代)中,当方法区满的时候,会引起 Full GC,所以当你大量使用动态代理时,可以将持久代设置大一些,减少 Full GC 次数。

CGLIB动态代理

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

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);  // 执行原有逻辑,注意这里是invokeSuper
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动态代理使用继承来实现代理。

参考参考一参考二

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