代理模式(英语: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 }
可以看到,通过保留目标类对象然后调用目标类的方法来实现静态代理。
但在某些情况下它还有明显的缺点:
在JDK动态代理中友好地解决了以上问题。
动态代理类和静态代理类之间的主要区别在于,代理类的字节码不是在程序运行之前生成的,而是在程序运行时在虚拟机中自动创建的。
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动态代理类的实例还使用反射包中的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个参数:
使用的ClassLoader,通常就是接口类的ClassLoader;
需要实现的接口数组,至少需要传入一个接口进去;
用来处理接口方法调用的实例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 (Code Generation Library),一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP等。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
CGLIB动态代理的实现机制是生成目标类的子类 Enhancer#create,该目标类是通过调用父类(目标类)的方法来实现的,然后在调用父类的方法时在代理中进行增强。
与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 }
使用 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