前言介绍
在Java中动态代理是非常重要也是非常有用的一个技术点,如果没有动态代理技术几乎也就不会有各种优秀框架的出现,包括Spring。
其实在动态代理的使用中,除了我们平时用的Spring还有很多中间件和服务都用了动态代理,例如;
- RPC通信框架Dubbo,在通信的时候由服务端提供一个接口描述信息的Jar,调用端进行引用,之后在调用端引用后生成了对应的代理类,当执行方法调用的时候,实际需要走到代理类向服务提供端发送请求信息,直至内容回传。
- 另外在使用Mybatis时候可以知道只需要定义一个接口,不需要实现具体方法就可以调用到Mapper中定义的数据库操作信息了。这样极大的简化了代码的开发,又增强了效率。
- 最后不知道你自己是否尝试过开发一些基于代理类的框架,以此来优化业务代码。也就是将业务代码中非业务逻辑又通用性的功能抽离出来,开发为独立的组件。推荐个案例,方便知道代理类的应用:手写RPC框架第三章《RPC中间件》
代理方式
动态代理可以使用Jdk方式也可以使用CGLB,他们的区别,如下;
类型 | 机制 | 回调方式 | 适用场景 | 效率 |
JDK | 委托机制,代理类和目标类都实现了同样的接口,InvocationHandler持有目标类,代理类委托InvocationHandler去调用目标类的原始方法 | 反射 | 目标类是接口类 | 效率瓶颈在反射调用稍慢 |
CGLIB | 继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑 | 通过FastClass方法索引调用 | 非接口类,非final类,非final方法 | 第一次调用因为要生成多个Class对象较JDK方式慢,多次调用因为有方法索引较反射方式快,如果方法过多switch case过多其效率还需测试 |
案例工程
1itstack-demo-test 2└── src 3 ├── main 4 │ └── java 5 │ └── org.itstack.demo 6 │ ├── proxy 7 │ │ └── cglib 8 │ │ └── CglibProxy.java 9 │ ├── jdk 10 │ │ ├── reflect 11 │ │ │ ├── JDKInvocationHandler.java 12 │ │ │ └── JDKProxy.java 13 │ │ └── util 14 │ │ └── ClassLoaderUtils.java 15 │ └── service 16 │ ├── IUserService.java 17 │ └── UserService.java 18 └── test 19 └── java 20 └── org.itstack.demo.test 21 └── ApiTest.java
基础接口和方法便于验证
service/IUserService.java
1public interface IUserService { 2 3 String queryUserNameById(String userId); 4 5}
service/UserService.java
1public class UserService implements IUserService { 2 3 public String queryUserNameById(String userId) { 4 return "hi user " + userId; 5 } 6 7}
JDK动态代理
reflect/JDKInvocationHandler.java & 代理类反射调用
- 实现InvocationHandler.invoke,用于方法增强{监控、执行其他业务逻辑、远程调用等}
- 如果有需要额外的参数可以提供构造方法
1public class JDKInvocationHandler implements InvocationHandler { 2 3 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 4 System.out.println(method.getName()); 5 return "我被JDKProxy代理了"; 6 } 7 8}
reflect/JDKProxy.java & 定义一个代理类获取的服务
- Proxy.newProxyInstance 来实际生成代理类,过程如下;
- Class cl = getProxyClass0(loader, intfs); 查找或生成指定的代理类
- proxyClassCache.get(loader, interfaces); 代理类的缓存中获取
- subKeyFactory.apply(key, parameter) 继续下一层
- byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); 生成代理类的字节码
1public class JDKProxy { 2 3 public static <T> T getProxy(Class<T> interfaceClass) throws Exception { 4 InvocationHandler handler = new JDKInvocationHandler(); 5 ClassLoader classLoader = ClassLoaderUtils.getCurrentClassLoader(); 6 T result = (T) Proxy.newProxyInstance(classLoader, new Class[]{interfaceClass}, handler); 7 return result; 8 } 9 10}
ApiTest.test_proxy_jdk() & 执行调用并输出反射类的字节码
- 代理后调用方法验证
- 通过使用ProxyGenerator.generateProxyClass获取实际的字节码,查看代理类的内容
1@Test 2public void test_proxy_jdk() throws Exception { 3 4 IUserService proxy = (IUserService) JDKProxy.getProxy(ClassLoaderUtils.forName("org.itstack.demo.service.IUserService")); 5 String userName = proxy.queryUserNameById("10001"); 6 System.out.println(userName); 7 8 String name = "ProxyUserService"; 9 byte[] data = ProxyGenerator.generateProxyClass(name, new Class[]{IUserService.class}); 10 11 // 输出类字节码 12 FileOutputStream out = null; 13 try { 14 out = new FileOutputStream(name + ".class"); 15 System.out.println((new File("")).getAbsolutePath()); 16 out.write(data); 17 } catch (FileNotFoundException e) { 18 e.printStackTrace(); 19 } catch (IOException e) { 20 e.printStackTrace(); 21 } finally { 22 if (null != out) try { 23 out.close(); 24 } catch (IOException e) { 25 e.printStackTrace(); 26 } 27 } 28 29}
输出结果
1queryUserNameById 2我被JDKProxy代理了
将生成的代理类进行反编译jd-gui
部分内容抽取,可以看到比较核心的方法,也就是我们在调用的时候走到了这里
1public final String queryUserNameById(String paramString) 2 throws 3{ 4try 5{ 6 return (String)this.h.invoke(this, m3, new Object[] { paramString }); 7} 8catch (Error|RuntimeException localError) 9{ 10 throw localError; 11} 12catch (Throwable localThrowable) 13{ 14 throw new UndeclaredThrowableException(localThrowable); 15} 16} 17 18 19static 20{ 21try 22{ 23 m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); 24 m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); 25 m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); 26 m3 = Class.forName("org.itstack.demo.service.IUserService").getMethod("queryUserNameById", new Class[] { Class.forName("java.lang.String") }); 27 return; 28} 29catch (NoSuchMethodException localNoSuchMethodException) 30{ 31 throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); 32} 33catch (ClassNotFoundException localClassNotFoundException) 34{ 35 throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); 36} 37}
CGLIB动态代理
cglib/CglibProxy.java
- 提供构造方法,生成CGLIB的代理类,回调this
- intercept可以进行方法的增强,处理相关业务逻辑
- CGLIB是通过ASM来操作字节码生成类
1public class CglibProxy implements MethodInterceptor { 2 3 public Object newInstall(Object object) { 4 return Enhancer.create(object.getClass(), this); 5 } 6 7 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 8 System.out.println("我被CglibProxy代理了"); 9 return methodProxy.invokeSuper(o, objects); 10 } 11 12}
ApiTest.test_proxy_cglib() & 调用代理类
1@Test 2public void test_proxy_cglib() { 3 CglibProxy cglibProxy = new CglibProxy(); 4 UserService userService = (UserService) cglibProxy.newInstall(new UserService()); 5 String userName = userService.queryUserNameById("10001"); 6 System.out.println(userName); 7}
输出结果
1我被CglibProxy代理了 2hi user 10001
综上总结
- 在我们实际使用中两种方式都用所有使用,也可以依照不同的诉求进行选择
- 往往动态代理会和注解共同使用,代理类拿到以后获取方法的注解,并做相应的业务操作
- 有时候你是否会遇到增加AOP不生效,因为有时候有些类是被代理操作的,并没有执行你的自定义注解也就是切面