静态代理和动态代理
静态代理和动态代理
一、什么是代理模式?
代理模式(Proxy Pattern)给某一个对象提供一个代理,并由代理对象控制原对象的引用。代理对象在客户端和目标对象之间起到中介作用。
代理模式可以分为静态代理和动态代理两种类型,而动态代理中又分为JDK动态代理和CGLIB代理两种。
如下图:
二、静态代理
在静态代理中,代理类和真实类的关系在编译阶段就已经确定。在编写代理类的时候,需要明确代理哪个具体类,代理类和被代理类之间的关系在编译时就已经确定,因此称为静态代理。
代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是实际对象。
1)代码示例
定义一个学生管理的接口:
1 public interface IStudentService { 2 3 void insertStudent(); 4 5 void deleteStudent(); 6 7 }
实现学生管理接口的类:
1 public class StudentService implements IStudentService { 2 public void insertStudent(){ 3 //添加学生 4 System.out.println("添加学生"); 5 } 6 7 public void deleteStudent(){ 8 //删除学生 9 System.out.println("删除学生"); 10 } 11 }
定义一个静态代理类:
1 public class StudentServiceProxy implements IStudentService { 2 3 IStudentService studentService; 4 5 public StudentServiceProxy(IStudentService studentService) { 6 this.studentService = studentService; 7 } 8 9 @Override 10 public void insertStudent() { 11 System.out.println("准备添加学生"); 12 studentService.insertStudent(); 13 System.out.println("添加学生成功"); 14 } 15 16 @Override 17 public void deleteStudent() { 18 System.out.println("准备删除学生"); 19 studentService.deleteStudent(); 20 System.out.println("删除学生成功"); 21 } 22 }
测试:
1 public class Client { 2 public static void main(String[] args) { 3 IStudentService studentServiceProxy = new StudentServiceProxy(new StudentService()); 4 studentServiceProxy.insertStudent(); 5 studentServiceProxy.deleteStudent(); 6 } 7 }
测试结果:
1 准备添加学生 2 添加学生 3 添加学生成功 4 准备删除学生 5 删除学生 6 删除学生成功
2)优缺点
- 优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
- 缺点:不同的接口要有不同的代理类实现,会很冗余。
三、动态代理
静态代理会为每一个业务增强都提供一个代理类, 由代理类来创建代理对象, 而动态代理并不存在代理类, 代理对象直接由代理生成工具动态生成。
在动态代理中,代理类是在运行时动态生成的。所谓动态代理,就是在程序运行时,动态地为被代理对象生成代理类,这就需要借助编程语言当中的"反射"特性。
1. JDK动态代理
1)实现原理
JDK的动态代理是基于反射实现。JDK通过反射,生成一个代理类,这个代理类实现了原来那个类的全部接口,并对接口中定义的所有方法进行了代理。当我们通过代理对象执行原来那个类的方法时,代理类底层会通过反射机制,回调我们实现的InvocationHandler接口的invoke方法。并且这个代理类是Proxy类的子类。
在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。JDK动态代理是使用 java.lang.reflect 包下的代理类来实现。
2)代码示例
学生管理的接口和实现学生管理接口的类均沿用上面静态代理示例中的代码。
除此之外,定义一个调用逻辑处理器:
在 MyInvocationHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke 方法中编写方法调用的逻辑处理
1 public class MyInvocationHandler implements InvocationHandler { 2 private Object obj; 3 4 public MyInvocationHandler(Object object){ 5 this.obj = object; 6 } 7 8 @Override 9 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 10 System.out.println(method.getName() + "方法调用前"); 11 Object result = method.invoke(obj, args); 12 System.out.println(method.getName() + "方法调用后"); 13 return result; 14 } 15 }
测试:
1 public class Client { 2 public static void main(String[] args) { 3 IStudentService studentService = new StudentService(); 4 5 // 创建 InvocationHandler 实现 6 InvocationHandler myInvocationHandler = new MyInvocationHandler(studentService); 7 8 // 通过 Proxy.newProxyInstance 动态生成代理对象 9 IStudentService studentServiceProxy = (IStudentService) Proxy.newProxyInstance( 10 studentService.getClass().getClassLoader(), 11 studentService.getClass().getInterfaces(), 12 myInvocationHandler); 13 studentServiceProxy.insertStudent(); 14 studentServiceProxy.deleteStudent(); 15 } 16 17 }
运行结果:
1 insertStudent方法调用前 2 添加学生 3 insertStudent方法调用后 4 deleteStudent方法调用前 5 删除学生 6 deleteStudent方法调用后
4) 代理类的调用过程
生成的代理类到底长什么样子呢?借助下面的工具类,把代理类保存下来再探个究竟.
工具类如下:
1 public class ProxyUtils { 2 /** 3 * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下 4 * params: clazz 需要生成动态代理类的类 5 * proxyName: 为动态生成的代理类的名称 6 */ 7 public static void generateClassFile(Class clazz, String proxyName) { 8 // 根据类信息和提供的代理类名称,生成字节码 9 byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces()); 10 String paths = clazz.getResource(".").getPath(); 11 System.out.println(paths); 12 FileOutputStream out = null; 13 try { 14 //保留到硬盘中 15 out = new FileOutputStream(paths + proxyName + ".class"); 16 out.write(classFile); 17 out.flush(); 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } finally { 21 try { 22 out.close(); 23 } catch (IOException e) { 24 e.printStackTrace(); 25 } 26 } 27 } 28 }
然后在测试类的main方法最后面加入一行代码:
// 保存JDK动态代理生成的代理类,类名保存为 StudentServiceProxy ProxyUtils.generateClassFile(studentService.getClass(), "StudentServiceProxy");
运行测试方法,在target类路径下找到StudentServiceProxy.class,代码如下:
1 import java.lang.reflect.InvocationHandler; 2 import java.lang.reflect.Method; 3 import java.lang.reflect.Proxy; 4 import java.lang.reflect.UndeclaredThrowableException; 5 import org.example.proxy.jdkproxy.IStudentService; 6 7 public final class StudentServiceProxy extends Proxy implements IStudentService { 8 private static Method m1; 9 private static Method m2; 10 private static Method m3; 11 private static Method m4; 12 private static Method m0; 13 14 public StudentServiceProxy(InvocationHandler var1) throws { 15 super(var1); 16 } 17 18 public final boolean equals(Object var1) throws { 19 //... 20 } 21 22 public final int hashCode() throws { 23 //... 24 } 25 26 public final String toString() throws { 27 //... 28 } 29 30 public final void insertStudent() throws { 31 try { 32 super.h.invoke(this, m3, (Object[])null); 33 } catch (RuntimeException | Error var2) { 34 throw var2; 35 } catch (Throwable var3) { 36 throw new UndeclaredThrowableException(var3); 37 } 38 } 39 40 public final void deleteStudent() throws { 41 try { 42 super.h.invoke(this, m4, (Object[])null); 43 } catch (RuntimeException | Error var2) { 44 throw var2; 45 } catch (Throwable var3) { 46 throw new UndeclaredThrowableException(var3); 47 } 48 } 49 50 51 static { 52 try { 53 m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); 54 m2 = Class.forName("java.lang.Object").getMethod("toString"); 55 m3 = Class.forName("org.example.proxy.jdkproxy.IStudentService").getMethod("insertStudent"); 56 m4 = Class.forName("org.example.proxy.jdkproxy.IStudentService").getMethod("deleteStudent"); 57 m0 = Class.forName("java.lang.Object").getMethod("hashCode"); 58 } catch (NoSuchMethodException var2) { 59 throw new NoSuchMethodError(var2.getMessage()); 60 } catch (ClassNotFoundException var3) { 61 throw new NoClassDefFoundError(var3.getMessage()); 62 } 63 } 64 }
从 StudentServiceProxy 的代码中我们可以发现:
1)StudentServiceProxy 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法。
2)由于 StudentServiceProxy 继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器。
3)类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承。
4)每个方法都有一个 Method 对象来描述,Method 对象在 static 静态代码块中创建,以 m + 数字 的格式命名。
调用方法的时候通过 super.h.invoke(this, m1, (Object[])null); 调用,其中的 super.h实际上是在创建代理的时候传递给 Proxy.newProxyInstance 的 MyInvocationHandler 对象,它继承 InvocationHandler 类,通过执行invoke方法,负责实际的调用处理逻辑。
而 MyInvocationHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法。
1 @Override 2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 3 System.out.println(method.getName() + "方法调用前"); 4 Object result = method.invoke(obj, args); 5 System.out.println(method.getName() + "方法调用后"); 6 return result; 7 }
我们看下 InvocationHandler 类:
1 public interface InvocationHandler { 2 public Object invoke(Object proxy, Method method, Object[] args) 3 throws Throwable; 4 }
Proxy类:
1 public class Proxy implements java.io.Serializable { 2 3 private static final long serialVersionUID = -2222568056686623797L; 4 5 //... 6 7 protected InvocationHandler h; 8 9 private Proxy() { 10 11 } 12 13 protected Proxy(InvocationHandler h) { 14 Objects.requireNonNull(h); 15 this.h = h; 16 } 17 18 //... 19 }
5)JDK动态代理步骤
JDK动态代理分为以下几步:
- 拿到被代理对象的引用,并且通过反射获取到它的所有的接口。
- 通过JDK Proxy类重新生成一个新的类,同时新的类要实现被代理类所实现的所有的接口。
- 动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用。
- 编译新生成的 Java 代码.class。
- 将新生成的Class文件重新加载到 JVM 中运行
6) 优缺点:
- 优点:解决了静态代理中冗余的代理实现类问题。
- 缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。
2. CGLIB动态代理
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
1)实现原理
JDK动态代理是通过重写被代理对象实现的接口中的方法来实现,而CGLIB是通过继承被代理对象来实现,和JDK动态代理需要实现指定接口一样,CGLIB也要求代理对象必须要实现MethodInterceptor接口,并重写其唯一的方法intercept。
CGLIB采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。(利用ASM开源包,将被代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)
注意:
- 因为CGLIB是通过继承目标类来重写其方法来实现的,故而如果是final和private、static方法则无法被重写,也就是无法被代理。
- 不管有没有接口都可以使用CGLIB动态代理, 而不是只有在无接口的情况下才能使用。
2) 代码示例
不同于 JDK 动态代理不需要额外的依赖。CGLIB(Code Generation Library) 实际是属于一个开源项目,如果要使用它的话,需要手动添加相关依赖。
1 <dependency> 2 <groupId>cglib</groupId> 3 <artifactId>cglib</artifactId> 4 <version>3.3.0</version> 5 </dependency>
实现一个学生管理类:
1 public class StudentService { 2 public void insertStudent() { 3 //添加学生 4 System.out.println("添加学生"); 5 } 6 7 public void deleteStudent() { 8 //删除学生 9 System.out.println("删除学生"); 10 } 11 }
自定义 MethodInterceptor(方法拦截器):
1 public class MyMethodInterceptor implements MethodInterceptor { 2 @Override 3 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 4 System.out.println(method.getName() + "方法调用前"); 5 Object object = methodProxy.invokeSuper(o, objects); 6 System.out.println(method.getName() + "方法调用后"); 7 return object; 8 } 9 }
测试:
1 public class Client { 2 public static void main(String[] args) { 3 Enhancer enhancer = new Enhancer(); 4 enhancer.setSuperclass(StudentService.class); 5 enhancer.setCallback(new MyMethodInterceptor()); 6 StudentService studentService = (StudentService) enhancer.create(); 7 studentService.insertStudent(); 8 studentService.deleteStudent(); 9 } 10 }
测试结果:
insertStudent方法调用前
添加学生
insertStudent方法调用后
deleteStudent方法调用前
删除学生
deleteStudent方法调用后
3) CGLIB 动态代理使用步骤
- 定义一个需要被代理的类;
- 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
- 通过 Enhancer 类的 create()创建代理类;
4)CGLIB动态代理分析
CGLIB动态代理采用了FastClass机制,其分别为代理类和被代理类各生成一个FastClass,这个FastClass类会为代理类或被代理类的方法分配一个 index(int类型)。这个index当做一个入参,FastClass 就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比 JDK 动态代理通过反射调用更高。
JDK动态代理只生成一个文件,而CGLIB生成了三个文件,所以生成代理对象的过程会更复杂。
5)优缺点
-
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
-
缺点:技术实现相对难理解些。
四、JDK动态代理和CGLib动态代理对比
1. JDK 动态代理只能代理实现了接口的类,而 CGLIB 可以代理未实现任何接口的类。 另外,CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
五、静态代理和动态代理的本质区别
1. 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。
2. 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
3. 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
参考链接:
https://nilzzzz.github.io
https://juejin.cn/post/7011357346018361375