代理模式分为 3 种角色
- Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法
- RealSubject(真实主题角色):真正实现业务逻辑的类
- Proxy(代理主题角色):用来代理和封装真实主题
根据字节码的创建时机来分类,可以分为静态代理和动态代理:
- 所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
- 而动态代理的源码是在程序运行期间由JVM根据反射等机制动态地生成,所以在运行前并不存在代理类的字节码文件
2. 静态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class UserServiceProxy implements UserService { private UserService target;
public UserServiceProxy(UserService target) { this.target = target; } public void select() { before(); target.select(); after(); } private void before() { ... } private void after() { ... } }
|
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
- 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
- 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
- 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
- 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护
3. 动态代理
为什么类可以动态地生成?
JVM的类加载过程分为 5 个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下三件事情:
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的
java.lang.Class
对象,作为方法区这个类的各种数据访问入口
由于《Java虚拟机规范》对这三点要求并不具体,因此实现非常灵活。例如第一点获取类的二进制字节流就有很多途径:
- 从ZIP包获取,这是JAR、EAR、WAR等格式的基础
- 从网络中获取,典型的应用是 Applet
- 运行时计算生成,这种场景使用最多的是动态代理技术,在
java.lang.reflect.Proxy
类中,就是用了 ProxyGenerator.generateProxyClass()
来为特定接口生成形式为 *$Proxy
的代理类的二进制字节流
- 由其它文件生成,典型应用是JSP,由JSP文件生成对应的Class文件
- 从数据库中读取
- 从加密文件中获取等等
为了让生成的代理类与目标对象(真实主题角色)保持一致性,从现在开始将介绍以下两种最常见的方式:
- 通过代理类和目标类实现同一个接口的方式 -> JDK动态代理
- 通过代理类继承目标类的方式 -> CGLIB动态代理
3.1 JDK动态代理
JDK动态代理主要涉及两个类:java.lang.reflect.Proxy
和 java.lang.reflect.InvocationHandler
。通过案例来说明
编写一个调用逻辑处理器 LogHandler
类,提供日志增强功能,并实现 InvocationHandler
接口;在 LogHandler
中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke
方法中编写方法调用的逻辑处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class LogHandler implements InvocationHandler { Object target; public LogHandler(Object target) { this.target = target; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.printf("log start time [%s]", new Date()); Object result = method.invoke(target, args); System.out.printf("log end time [%s]", new Date()); return result; } }
|
编写测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Test public void JDKProxyTest() { System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); UserService userService = new UserServiceImpl(); ClassLoader classLoader = userService.getClass().getClassLoader(); Class<?>[] interfaces = userService.getClass().getInterfaces(); InvocationHandler logHandler = new LogHandler(userService);
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler); proxy.select(); proxy.update(); ProxyUtils.generateClassFile(userService.getClass(), "UserServiceProxy"); }
|
运行结果
因为 InvocationHandler
是一个函数接口,可以使用匿名内部类或Lambda表达式简化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Test public void JDKProxyByLambdaTest() { UserService userService = new UserServiceImpl(); UserService proxy = (UserService) Proxy .newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), ((proxy1, method, args) -> { System.out.printf("log start time [%s]\n", new Date()); Object result = method.invoke(userService, args); System.out.printf("log end time [%s]\n", new Date()); return result; })); proxy.select(); proxy.update(); }
|
3.2 代理类源码分析
通过上面测试类的配置,可以在target的类路径下找到代理类的class文件,反编译得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public final class UserServiceProxy extends Proxy implements UserService { private static Method m1; private static Method m2; private static Method m4; private static Method m0; private static Method m3;
public UserServiceProxy(InvocationHandler var1) throws { super(var1); }
public final boolean equals(Object var1) throws { }
public final String toString() throws { }
public final void select() throws { try { super.h.invoke(this, m4, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
public final int hashCode() throws { }
public final void update() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } }
static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m4 = Class.forName("top.whale.proxy.UserService").getMethod("select"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m3 = Class.forName("top.whale.proxy.UserService").getMethod("update"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
|
从 UserServiceProxy
的代码中可以发现:
- UserServiceProxy 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法
- 由于 UserServiceProxy 继承了 Proxy 类,所以每个代理类都会关联一个 InvocationHandler 方法调用处理器
- 类和所有方法都被
public final
修饰,所以代理类只可被使用,不可以再被继承
- 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以
m + 数字
的格式命名
- 调用方法的时候通过
super.h.invoke(this, m3, (Object[])null);
调用,其中的 super.h.invoke
实际上是在创建代理的时候传递给 Proxy.newProxyInstance
的 LogHandler
对象,它继承 InvocationHandler
类,负责实际的调用处理逻辑
而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法
3.3 CGLIB动态代理
CGLIB动态代理是代理类去继承目标类,然后重写目标类的方法,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑
JDK动态代理中提供了一个Proxy
类来创建代理类,而在CGLIB动态代理中也提供了一个类似的类Enhancer
编写 LogInterceptor
,实现 MethodInterceptor
,用于方法的拦截回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class LogInterceptor implements MethodInterceptor {
@Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.printf("log start time [%s]\n", new Date()); Object result = methodProxy.invokeSuper(o, objects); System.out.printf("log end time [%s]\n", new Date()); return result; } }
|
编写测试类
1 2 3 4 5 6 7 8 9
| @Test public void cglibTest() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(UserDAO.class); enhancer.setCallback(new LogInterceptor()); UserDAO proxyDao = (UserDAO) enhancer.create(); proxyDao.select(); proxyDao.update(); }
|
同样可以使用匿名内部类或Lambda表达式简化