代理作用即应用

动态代理在java中有着广泛的应用,比如spring aop、hibernate数据查询、测试框架后端的mock、rpc远程调用、java注解对象获取、日志、用户鉴权、全局异常处理、性能监控、事务处理。

静态代理

案例代码

  1. -- 接口
  2. public interface UserInterface {
  3. public void queryUser();
  4. }
  5. --实现类
  6. public class UserInterfaceImpl implements UserInterface {
  7. @Override
  8. public void queryUser() {
  9. System.out.println("=============查询用户信息==============");
  10. }
  11. }
  12. --代理类
  13. public class UserProxy {
  14. private UserInterface userInterface;
  15. public UserProxy(UserInterface userInterface){
  16. this.userInterface = userInterface;
  17. }
  18. public void log(){
  19. System.out.println("==========输出日志start============");
  20. userInterface.queryUser();
  21. System.out.println("==========输出日志end============");
  22. }
  23. }
  24. --测试类
  25. public class Client {
  26. public static void main(String[] args){
  27. UserInterface userInterface = new UserInterfaceImpl();
  28. UserProxy proxy = new UserProxy(userInterface);
  29. proxy.log();
  30. }
  31. }
  32. --输出结果
  33. ==========输出日志start============
  34. =============查询用户信息==============
  35. ==========输出日志end============

静态代理缺点

优点

简单、不侵入源代码

缺点

  • 当接口需要增加、删除、修改方法的时候,目标对象与代理类都需要同时修改,不易维护
  • 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式

维护一个代理类,由于一个代理类实现多个接口,导致代理类过于庞大
新建多个代理类,每个目标对象都需要一个代理类,会产生过多的代理类

动态代理

针对静态代理的缺点,改进方式就是让代理类动态生成,这就是动态代理,这种没有实现类但是在运行期动态创建一个接口对象的方式,我们称为动态代码,jdk提供的动态创建接口对象的方式,就叫动态代理。

类加载

java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化,其中加载阶段需要三件事

  • 通过一个类的全限定名来获取此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的 java.lang.class 对象,作为方法去这个类的各种数据访问入口

由于虚拟机规范对这3点要求并不具体,所以实际实现很灵活,关于第一点获取类的二进制字节流(class字节码)就有很多途径

  • 从zip、war、jar、ear等包中
  • 网络中获取,applet
  • 运行时计算生成 - 这种场景使用最多的就是动态代理技术
  • 其他文件生成,由jsp生成class

类的加载过程详见:

动态代理实现方式

通过实现接口的方式 — jdk的动态代理
通过继承类的方式 — CGLIB动态代理

jdk动态代理

InvocationHandler和Proxy常用API

java.lang.reflect.InvocationHandler
Object invoke(Object proxy, Method method, Object[] args) 用于集中处理被代理对象需要执行的方法
java.lang.reflect.Proxy
static Object newProxyInstance(ClassLoader loader, Class<?>[]… interfaces, InvocationHandler h) 构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的invoke方法

  1. -- 接口
  2. public interface UserInterface {
  3. public void queryUser();
  4. }
  5. --实现类
  6. public class UserInterfaceImpl implements UserInterface {
  7. @Override
  8. public void queryUser() {
  9. System.out.println("=============查询用户信息==============");
  10. }
  11. }
  12. --代理类
  13. public class LogHandler implements InvocationHandler {
  14. //被代理的对象
  15. private Object target;
  16. public LogHandler(Object target){
  17. this.target = target;
  18. }
  19. @Override
  20. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  21. this.pre();
  22. //执行被代理对象的方法
  23. Object result = method.invoke(target, args);
  24. this.after();
  25. return result;
  26. }
  27. public void pre(){
  28. System.out.println("==========输出日志start============");
  29. }
  30. public void after(){
  31. System.out.println("==========输出日志end============");
  32. }
  33. }
  34. public class Client {
  35. public static void main(String[] args){
  36. UserInterfaceImpl userInterface = new UserInterfaceImpl();
  37. ClassLoader classLoader = userInterface.getClass().getClassLoader();
  38. InvocationHandler handler = new LogHandler(userInterface);
  39. Class[] interfaces = userInterface.getClass().getInterfaces();
  40. UserInterface proxyInstance = (UserInterface)Proxy.newProxyInstance(classLoader, interfaces, handler);
  41. proxyInstance.queryUser();
  42. }
  43. }
  44. ==========输出日志start============
  45. =============查询用户信息==============
  46. ==========输出日志end============

代理类调用过程

生成的代理类具体内容是什么?

  1. // 生成代理类的代码
  2. public class ProxyUtils {
  3. /**
  4. * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下
  5. * params: clazz 需要生成动态代理类的类
  6. * proxyName: 为动态生成的代理类的名称
  7. */
  8. public static void generateClassFile(Class clazz, String proxyName) {
  9. // 根据类信息和提供的代理类名称,生成字节码
  10. byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
  11. String paths = clazz.getResource(".").getPath();
  12. System.out.println(paths);
  13. FileOutputStream out = null;
  14. try {
  15. //保留到硬盘中
  16. out = new FileOutputStream(paths + proxyName + ".class");
  17. out.write(classFile);
  18. out.flush();
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. } finally {
  22. try {
  23. out.close();
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }
  30. // 生成的代理类
  31. public final class UserServiceProxy extends Proxy implements UserService {
  32. private static Method m1;
  33. private static Method m2;
  34. private static Method m4;
  35. private static Method m0;
  36. private static Method m3;
  37. public UserServiceProxy(InvocationHandler var1) throws {
  38. super(var1);
  39. }
  40. public final boolean equals(Object var1) throws {
  41. // 省略...
  42. }
  43. public final String toString() throws {
  44. // 省略...
  45. }
  46. public final void select() throws {
  47. try {
  48. super.h.invoke(this, m4, (Object[])null);
  49. } catch (RuntimeException | Error var2) {
  50. throw var2;
  51. } catch (Throwable var3) {
  52. throw new UndeclaredThrowableException(var3);
  53. }
  54. }
  55. public final int hashCode() throws {
  56. // 省略...
  57. }
  58. public final void update() throws {
  59. try {
  60. super.h.invoke(this, m3, (Object[])null);
  61. } catch (RuntimeException | Error var2) {
  62. throw var2;
  63. } catch (Throwable var3) {
  64. throw new UndeclaredThrowableException(var3);
  65. }
  66. }
  67. static {
  68. try {
  69. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
  70. m2 = Class.forName("java.lang.Object").getMethod("toString");
  71. m4 = Class.forName("proxy.UserService").getMethod("select");
  72. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  73. m3 = Class.forName("proxy.UserService").getMethod("update");
  74. } catch (NoSuchMethodException var2) {
  75. throw new NoSuchMethodError(var2.getMessage());
  76. } catch (ClassNotFoundException var3) {
  77. throw new NoClassDefFoundError(var3.getMessage());
  78. }
  79. }
  80. }
  • 生成的代理类UserServiceProxy继承了Proxy类,并实现了被代理的所有接口,以及equals、hashcode、toString方法
  • 由于UserServiceProxy继承了Proxy类,所以每个代理类都会关联一个InvocationHandler方法调用处理器
  • 类和所有方法都被public final修饰,所以代理类只能被使用,不可被继承

动态代理 - 图1

cglib代理

  1. -- maven引入cglib
  2. public class UserDao {
  3. public void select() {
  4. System.out.println("UserDao 查询 selectById");
  5. }
  6. }
  7. public class LogInterceptor implements MethodInterceptor {
  8. /**
  9. * Object -- 被代理的对象
  10. * Method -- 表示拦截的方法
  11. * Object[] -- 数组表示参数列表
  12. * MethodProxy -- 表示对方法的代理
  13. * invokeSuper方法表示对被代理对象方法的调用
  14. */
  15. @Override
  16. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  17. before();
  18. Object result = methodProxy.invokeSuper(o, objects);
  19. after();
  20. return result;
  21. }
  22. private void before() {
  23. System.out.println(String.format("log start time [%s] ", new Date()));
  24. }
  25. private void after() {
  26. System.out.println(String.format("log end time [%s] ", new Date()));
  27. }
  28. }
  29. public class CglibClient {
  30. public static void main(String[] args){
  31. Enhancer enhancer = new Enhancer();
  32. enhancer.setSuperclass(UserDao.class);
  33. enhancer.setCallback(new LogInterceptor());
  34. //enhancer.setCallbackFilter(new DaoFilter());
  35. UserDao proxy = (UserDao)enhancer.create();
  36. proxy.select();
  37. }
  38. }
  39. // 回调过滤器: 在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。
  40. public class DaoFilter implements CallbackFilter {
  41. @Override
  42. public int accept(Method method) {
  43. if ("select".equals(method.getName())) {
  44. return 0; // Callback 列表第1个拦截器
  45. }
  46. return 1; // Callback 列表第2个拦截器,return 2 则为第3个,以此类推
  47. }
  48. }

CGLIB创建动态代理类的模式是

  • 查找目标类上的所有非final的public类型的方法定义
  • 将这些方法的定义转换成字节码
  • 将组成的字节码转换成相应的代理的class对象
  • 实现MethodInterceptor接口,用来处理对代理类上所有方法的请求

    jdk动态代理与cglib多态代理的区别

  • JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生成代理对象。

  • cglib动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。
  • jdk proxy优势

最小化依赖关系,减少依赖意味着简化开发和维护,JDK 本身的支持,可能比 cglib 更加可靠。
平滑进行 JDK 版本升级,而字节码类库通常需要进行更新以保证在新版 Java 上能够使用。
代码实现简单

  • cglib优势

无需实现接口,达到代理类无侵入
只操作我们关心的类,而不必为其他相关类增加工作量。
高性能
ASM机制:asm是一个java字节码操控框架,可以直接生成二进制class文件,也可以再类被加载入java虚拟机之前动态改变类的行为,ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。