Java动态代理的实现机制

简介:

一、概述

代理是一种设计模式,其目的是为其他对象提供一个代理以控制对某个对象的访问,代理类负责为委托类预处理消息,过滤消息并转发消息以及进行消息被委托类执行后的后续处理。为了保持行为的一致性,代理类和委托类通常会实现相同的接口。

按照代理的创建时期,代理类可分为两种:

  • 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,也就是说在程序运行前代理类的.class文件就已经存在。

  • 动态代理:在程序运行时运用反射机制动态创建生成。

下面在将动态代理的实现机制之前先简单介绍一下静态代理。

二、静态代理

上面说过,代理类和委托类一般都要实现相同的接口,下面先定义这个接口:


  
  
  1. public interface Service 
  2. {     
  3.     public void add(); 

委托类作为接口的一种实现,定义如下:


 
 
  1. public class ServiceImpl implements Service 
  2.     public void add() 
  3.     { 
  4.         System.out.println("添加用户!"); 
  5.          
  6.     } 

假如我们要对委托类加一些日志的操作,代理类可做如下定义:


 
 
  1. public class ServiceProxy implements Service 
  2.     private Service service; 
  3.     public ServiceProxy(Service service) 
  4.     { 
  5.         super(); 
  6.         this.service = service; 
  7.     } 
  8.     public void add() 
  9.     { 
  10.         System.out.println("服务开始"); 
  11.         service.add(); 
  12.         System.out.println("服务结束"); 
  13.     } 

编写测试类:


 
 
  1. public class TestMain 
  2.     public static void main(String[] args) 
  3.     { 
  4.         Service serviceImpl=new ServiceImpl(); 
  5.         Service proxy=new ServiceProxy(serviceImpl); 
  6.         proxy.add(); 
  7.     } 

运行测试程序,结果如下图:

从上面的代码可以看到,静态代理类只能为特定的接口服务,如果要服务多类型的对象,就要为每一种对象进行代理。我们就会想是否可以通过一个代理类完成全部的代理功能,于是引入的动态代理的概念。

三、动态代理

Java的动态代理主要涉及两个类,Proxy和InvocationHandler。

Proxy:提供了一组静态方法来为一组接口动态地生成代理类及其对象。


 
 
  1. // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器 
  2. static InvocationHandler getInvocationHandler(Object proxy)  
  3.  
  4. // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象 
  5. static Class getProxyClass(ClassLoader loader, Class[] interfaces)  
  6.  
  7. // 方法 3:该方法用于判断指定类对象是否是一个动态代理类 
  8. static boolean isProxyClass(Class cl)  
  9.  
  10. // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 
  11. static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h) 

InvocationHandler:它是调用处理器接口,自定义了一个invok方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问


  
  
  1. // 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行 
  2. Object invoke(Object proxy, Method method, Object[] args) 

实现Java的动态代理,具体有以下四个步骤:

  1. 通过实现InvocationHandler接口创建自己的调用处理器

  2. 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类

  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器类接口类型

  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入

下面根据上述的四个步骤来实现自己的动态代理的示例:

接口和接口的实现类(即委托类)跟上面静态代理的代码一样,这里我们来实现InvocationHandler接口创建自己的调用处理器


 
 
  1. public class ServiceHandle implements InvocationHandler 
  2.     private Object s; 
  3.      
  4.     public ServiceHandle(Object s) 
  5.     { 
  6.         this.s = s; 
  7.     } 
  8.     public Object invoke(Object proxy, Method method, Object[] args) 
  9.             throws Throwable 
  10.     { 
  11.         System.out.println("服务开始"); 
  12.         //invoke表示对带有指定参数的指定对象调用由此 Method 对象表示的底层方法 
  13.         Object result=method.invoke(s, args); 
  14.         System.out.println("服务结束"); 
  15.         return result; 
  16.     } 

编写测试类:


 
 
  1. public class TestMain 
  2.     public static void main(String[] args) 
  3.     { 
  4.         Service service=new ServiceImpl(); 
  5.         InvocationHandler handler=new ServiceHandle(service); 
  6.         Service s=(Service) Proxy.newProxyInstance(service.getClass().getClassLoader(), service.getClass().getInterfaces(), handler); 
  7.         s.add(); 
  8.     } 

运行测试程序,结果同静态代理。我们可以看到上述代码并没有我们之前说的步骤2和3,这是因为Prox的静态方法newProxyInstance已经为我们封装了这两个步骤。具体的内部实现如下:


 
 
  1. // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象 
  2. Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });  
  3.  
  4. // 通过反射从生成的类对象获得构造函数对象 
  5. Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });  
  6.  
  7. // 通过构造函数对象创建动态代理类实例 
  8. Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler }); 

newProxyInstance函数的内部实现为:


 
 
  1. public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException 
  2.              //检查h不为空,否则抛异常 
  3.             Objects.requireNonNull(h); 
  4.             //获得与制定类装载器和一组接口相关的代理类类型对象 
  5.             final Class<?>[] intfs = interfaces.clone(); 
  6.              
  7.             //检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的 
  8.             final SecurityManager sm = System.getSecurityManager();     
  9.             if (sm != null)  
  10.             { 
  11.                 checkProxyAccess(Reflection.getCallerClass(), loader, intfs); 
  12.             } 
  13.             //获得与制定类装载器和一组接口相关的代理类类型对象 
  14.             Class<?> cl = getProxyClass0(loader, intfs); 
  15.             try 
  16.             { 
  17.                 if (sm != null)  
  18.                 { 
  19.                     checkNewProxyPermission(Reflection.getCallerClass(), cl); 
  20.                 } 
  21.                 // 通过反射获取构造函数对象并生成代理类实例 
  22.                 final Constructor<?> cons = cl.getConstructor(constructorParams); 
  23.                 final InvocationHandler ih = h; 
  24.                 if (!Modifier.isPublic(cl.getModifiers()))  
  25.                 { 
  26.                     AccessController.doPrivileged(new PrivilegedAction<Void>()  
  27.                     { 
  28.                         public Void run()  
  29.                         { 
  30.                         cons.setAccessible(true); 
  31.                         return null
  32.                         } 
  33.                     }); 
  34.                 } 
  35.                 return cons.newInstance(new Object[]{h}); 
  36.             }  
  37.             catch (IllegalAccessException|InstantiationException e) 
  38.             { 
  39.                 throw new InternalError(e.toString(), e); 
  40.             } 
  41.             catch (InvocationTargetException e)  
  42.             { 
  43.                 Throwable t = e.getCause(); 
  44.                 if (t instanceof RuntimeException)  
  45.                 { 
  46.                     throw (RuntimeException) t; 
  47.                 } 
  48.                 else  
  49.                 { 
  50.                     throw new InternalError(t.toString(), t); 
  51.                 } 
  52.             }  
  53.             catch (NoSuchMethodException e)  
  54.             { 
  55.                 throw new InternalError(e.toString(), e); 
  56.            } 
  57.  } 

四、模拟实现Proxy类

根据上面的原理介绍,我们可以自己模拟实现Proxy类:


 
 
  1. public class Proxy 
  2.     public static Object newProxyInstance(Class inface,InvocationHandle h) throws Exception 
  3.     { 
  4.         String rt="\r\n"
  5.         String methodStr=""
  6.         Method[] methods=inface.getMethods(); 
  7.         for(Method m:methods) 
  8.         { 
  9.             methodStr+="@Override"+rt+ 
  10.                      "public void "+m.getName()+"()"+rt+"{" + rt + 
  11.                     "   try {"+rt+ 
  12.                     "  Method md="+inface.getName()+".class.getMethod(\""+m.getName()+"\");"+rt+ 
  13.                         "h.invoke(this,md);"+rt+ 
  14.                     "   } catch(Exception e){e.printStackTrace();}"+rt+ 
  15.                      
  16.                     "}"
  17.         } 
  18.         String src="package test;"+rt+ 
  19.                 "import java.lang.reflect.Method;"+rt+ 
  20.                 "public class ServiceImpl2 implements "+inface.getName()+ rt+ 
  21.                 "{"+rt+ 
  22.                     "public ServiceImpl2(InvocationHandle h)"+rt+ 
  23.                     "{"+rt+ 
  24.                         "this.h = h;"+rt+ 
  25.                     "}"+rt+ 
  26.                     " test.InvocationHandle h;"+rt+ 
  27.                     methodStr+ 
  28.                 "}"
  29.         String fileName="d:/src/test/ServiceImpl2.java"
  30.         //compile 
  31.         compile(src, fileName); 
  32.         //load into memory and create instance 
  33.         Object m = loadMemory(h); 
  34.          
  35.         return m; 
  36.     } 
  37.     private static void compile(String src, String fileName) throws IOException 
  38.     { 
  39.         File f=new File(fileName); 
  40.         FileWriter fileWriter=new FileWriter(f); 
  41.         fileWriter.write(src); 
  42.         fileWriter.flush(); 
  43.         fileWriter.close(); 
  44.         //获取此平台提供的Java编译器 
  45.         JavaCompiler compiler=ToolProvider.getSystemJavaCompiler(); 
  46.         //获取一个标准文件管理器实现的新实例 
  47.         StandardJavaFileManager fileManager=compiler.getStandardFileManager(null,nullnull); 
  48.         //获取表示给定文件的文件对象 
  49.         Iterable units=fileManager.getJavaFileObjects(fileName); 
  50.         //使用给定组件和参数创建编译任务的 future 
  51.         CompilationTask t=compiler.getTask(null, fileManager, nullnullnull, units); 
  52.         //执行此编译任务 
  53.         t.call();     
  54.         fileManager.close(); 
  55.     } 
  56.     private static Object loadMemory(InvocationHandle h) 
  57.             throws MalformedURLException, ClassNotFoundException, 
  58.             NoSuchMethodException, InstantiationException, 
  59.             IllegalAccessException, InvocationTargetException 
  60.     { 
  61.         URL[] urls=new URL[] {new URL("file:/"+"d:/src/")}; 
  62.         //从路径d:/src/加载类和资源 
  63.         URLClassLoader ul=new URLClassLoader(urls); 
  64.         Class c=ul.loadClass("test.ServiceImpl2"); 
  65.         //返回Class对象所表示的类的指定公共构造方法。     
  66.         Constructor ctr=c.getConstructor(InvocationHandle.class); 
  67.         //使用此 Constructor对象ctr表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例 
  68.         Object m = ctr.newInstance(h); 
  69.         return m; 
  70.     } 

五、总结

1、所谓的动态代理就是这样一种class,它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后改 class就宣称它实现了这些interface,但是其实它不会替你作实质性的工作,而是根据你在生成实例时提供的参数handler(即 InvocationHandler接口的实现类),由这个Handler来接管实际的工作。

2、Proxy的设计使得它只能支持interface的代理,Java的继承机制注定了动态代理类无法实现对class的动态代理,因为多继承在Java中本质上就行不通。


作者:温布利往事

来源:51CTO

相关文章
|
7月前
|
设计模式 人工智能 安全
AQS:Java 中悲观锁的底层实现机制
AQS(AbstractQueuedSynchronizer)是Java并发包中实现同步组件的基础工具,支持锁(如ReentrantLock、ReadWriteLock)和线程同步工具类(如CountDownLatch、Semaphore)等。Doug Lea设计AQS旨在抽象基础同步操作,简化同步组件构建。 使用AQS需实现`tryAcquire(int arg)`和`tryRelease(int arg)`方法以获取和释放资源,共享模式还需实现`tryAcquireShared(int arg)`和`tryReleaseShared(int arg)`。
395 32
AQS:Java 中悲观锁的底层实现机制
|
7月前
|
人工智能 Java 关系型数据库
Java——SPI机制详解
SPI(Service Provider Interface)是JDK内置的服务提供发现机制,主要用于框架扩展和组件替换。通过在`META-INF/services/`目录下定义接口实现类文件,Java程序可利用`ServiceLoader`动态加载服务实现。SPI核心思想是解耦,允许不同厂商为同一接口提供多种实现,如`java.sql.Driver`的MySQL与PostgreSQL实现。然而,SPI存在缺陷:需遍历所有实现并实例化,可能造成资源浪费;获取实现类方式不够灵活;多线程使用时存在安全问题。尽管如此,SPI仍是Java生态系统中实现插件化和模块化设计的重要工具。
259 0
|
5月前
|
人工智能 前端开发 安全
Java开发不可不知的秘密:类加载器实现机制
类加载器是Java中负责动态加载类到JVM的组件,理解其工作原理对开发复杂应用至关重要。本文详解类加载过程、双亲委派模型及常见类加载器,并介绍自定义类加载器的实现与应用场景。
257 4
|
7月前
|
Java 区块链 网络架构
酷阿鲸森林农场:Java 区块链系统中的 P2P 区块同步与节点自动加入机制
本文介绍了基于 Java 的去中心化区块链电商系统设计与实现,重点探讨了 P2P 网络在酷阿鲸森林农场项目中的应用。通过节点自动发现、区块广播同步及链校验功能,系统实现了无需中心服务器的点对点网络架构。文章详细解析了核心代码逻辑,包括 P2P 服务端监听、客户端广播新区块及节点列表自动获取等环节,并提出了消息签名验证、WebSocket 替代 Socket 等优化方向。该系统不仅适用于农业电商,还可扩展至教育、物流等领域,构建可信数据链条。
|
9月前
|
缓存 Dubbo Java
理解的Java中SPI机制
本文深入解析了JDK提供的Java SPI(Service Provider Interface)机制,这是一种基于接口编程、策略模式与配置文件组合实现的动态加载机制,核心在于解耦。文章通过具体示例介绍了SPI的使用方法,包括定义接口、创建配置文件及加载实现类的过程,并分析了其原理与优缺点。SPI适用于框架扩展或替换场景,如JDBC驱动加载、SLF4J日志实现等,但存在加载效率低和线程安全问题。
428 7
理解的Java中SPI机制
|
7月前
|
人工智能 JavaScript Java
Java反射机制及原理
本文介绍了Java反射机制的基本概念、使用方法及其原理。反射在实际项目中比代理更常用,掌握它可以提升编程能力并理解框架设计原理。文章详细讲解了获取Class对象的四种方式:对象.getClass()、类.class、Class.forName()和类加载器.loadClass(),并分析了Class.forName()与ClassLoader的区别。此外,还探讨了通过Class对象进行实例化、获取方法和字段等操作的具体实现。最后从JVM类加载机制角度解析了Class对象的本质及其与类和实例的关系,帮助读者深入理解Java反射的工作原理。
191 0
|
8月前
|
存储 Java 编译器
Java 中 .length 的使用方法:深入理解 Java 数据结构中的长度获取机制
本文深入解析了 Java 中 `.length` 的使用方法及其在不同数据结构中的应用。对于数组,通过 `.length` 属性获取元素数量;字符串则使用 `.length()` 方法计算字符数;集合类如 `ArrayList` 采用 `.size()` 方法统计元素个数。此外,基本数据类型和包装类不支持长度属性。掌握这些区别,有助于开发者避免常见错误,提升代码质量。
800 1
|
8月前
|
缓存 监控 Java
java动态代理
本文介绍了Java中的动态代理及其优势,通过增强原有方法或拦截调用实现无侵入式代码扩展,如添加日志、缓存等。文章先讲解了静态代理的基本概念和实现方式,随后引出动态代理解决静态代理在多方法、多类场景下的局限性。通过JDK提供的InvocationHandler接口和Proxy类,展示了如何动态生成代理对象。最后,文章还探讨了代理Hook技术,包括寻找Hook点、选择代理方式以及替换原始对象的具体步骤。
230 0
|
9月前
|
缓存 运维 Java
Java静态代码块深度剖析:机制、特性与最佳实践
在Java中,静态代码块(或称静态初始化块)是指类中定义的一个或多个`static { ... }`结构。其主要功能在于初始化类级别的数据,例如静态变量的初始化或执行仅需运行一次的初始化逻辑。
298 4
|
10月前
|
Dubbo Java 应用服务中间件
Java的动态代理
Java动态代理是一种强大的机制,允许在运行时创建接口的代理实例,并拦截方法调用。其核心组件包括`java.lang.reflect.Proxy`和`java.lang.reflect.InvocationHandler`。通过自定义接口、实现接口、编写`InvocationHandler`处理器并生成代理对象,可以灵活地增强方法功能,如日志记录、事务管理等。典型应用场景包括AOP、RPC、延迟加载和Mock测试。与CGLIB相比,JDK动态代理基于接口,性能稍慢但无需第三方库,适用于需要无侵入式增强的场合。
253 2