摘要:根据之前的介绍,我们知道动态代理机制是一种在运行时创建代理对象的机制,而不是在编译的时候就生成固定的代理类。这种机制可以在不修改目标类的情况下,动态的对目标类添加一些复杂的功能,例如日志、权限、监控等等。被广泛用在Java面向切面编程AOP操作中,例如在Spr
根据之前的介绍,我们知道动态代理机制是一种在运行时创建代理对象的机制,而不是在编译的时候就生成固定的代理类。这种机制可以在不修改目标类的情况下,动态的对目标类添加一些复杂的功能,例如日志、权限、监控等等。被广泛用在Java面向切面编程AOP操作中,例如在Spring框架中被大量使用。
在实际开发过程中,实现动态代理的方式有两种分别如下所示。
基于接口的动态代理:在Java中,我们可以通过 java.lang.reflect.Proxy 类来动态地创建一个实现了目标接口的代理对象。这里需要注意被代理对象和代理对象都必须实现相同的接口,才可以使用动态代理,这个也是代理设计模式的基础概念。基于类的动态代理:由于JDK默认不支持通过继承目标类的方式来创建代理对象。所以我们需要某些第三方库,如 CGLIB,来支持基于类的动态代理实现。这里我们先来介绍一下基于接口的动态代理,在后面的分享中我们来介绍基于类的动态代理实现。
基于接口的动态代理实现是由Java提供的一套动态代理机制,允许在运行时来动态的创建一个代理对象,但是需要注意的是,这个对象必须要实现指定的接口并且将方法调用传递到一个指定的处理程序InvocationHandler,通过这种方式,可以在不修改原始类的情况下,对需要代理的真实对象进行功能性的增强。例如比较常见的就是记录操作日志、增加权限控制或者是在数据库操作中增加事务管理机制。
在这种所实现方式中比较核心的几个概念就是如下所示的几个概念。
接口:需要代理对象和被代理对象都必须要实现相同的接口。InvocationHandler:制定程序处理器,其中有一个必须要实现的方法invoke,这方法在代理对象调用目标方法时会被调用,所以我们可以在这个方法中增加一些自定义的预处理逻辑,例如提到的日志、事务、监控等操作。Proxy代理:通过java.lang.reflect.Proxy 类来动态的创建代理对象,在这个类中提供了一个newProxyInstance方法,就是专门在运行时来生成一个实现了指定接口的代理对象,并且将其转发到指定的Invocationhandler中进行处理。这里为什么是基于接口实现,我们可以仔细想想Java的面向对象的三大特性。这里就不做过多的说明,如果还没有对面向对象的三大特性熟悉的话,说明还没有真正理解面向对象编程。
其实从上面的介绍中我们也可以简单知道基于接口实现动态代理的工作原理,就是我们之前提到的最基础的哪个代理设计模式的动态版本流程。
首先就是要代理对象和被代理的对象都要实现相同的接口,这样才能保证代理对象可以执行与真实对象一样的操作,其次,就是要将这个操作转发到InvocationHandler接口上,然后通过invoke方法来定义增强的逻辑。最终就是通过真正的动态代理机制的Proxy.newProxyInstance方法来最终生成代理对象,这个代理对象户将方法交给了InvocationHandler 的 invoke 方法处理。
其实从原理上来看,实现动态代理的过程有点类似于工厂模式或者是创建者模式,更具需要来调用实现了InvocationHandler接口的不同的处理器来生成不同的扩展
更具上面的描述我们实现一个简单的基于接口的动态代理实现来看看是不是跟我们上面提到的原理类似。
假设我们有一个 Subject 接口,以及它的实现类 RealSubject,我们希望在调用 request 方法时,增加日志记录功能。
定义接口
public interface Subject {void request;}实现接口
public class RealSubject implements Subject {@Overridepublic void request {System.out.println("RealSubject: Handling request...");}}实现 InvocationHandler
import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class LoggingInvocationHandler implements InvocationHandler {private Object target;// 构造方法接收目标对象public LoggingInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object args) throws Throwable {// 在方法调用前记录日志System.out.println("Logging: Before method " + method.getName);// 调用目标对象的实际方法Object result = method.invoke(target, args);// 在方法调用后记录日志System.out.println("Logging: After method " + method.getName);return result;}}创建代理对象
import java.lang.reflect.Proxy;public class ProxyExample {public static void main(String args) {// 创建目标对象Subject realSubject = new RealSubject;// 创建 InvocationHandlerLoggingInvocationHandler handler = new LoggingInvocationHandler(realSubject);// 创建代理对象Subject proxy = (Subject) Proxy.newProxyInstance(realSubject.getClass.getClassLoader, // 类加载器new Class { Subject.class }, // 代理的接口handler // 代理的处理器);// 通过代理对象调用方法proxy.request;}}输出结果
Logging: Before method requestRealSubject: Handling request...Logging: After method request通过上面的整个实现流程,我们可以清楚的看到,在Proxy.newProxyInstance方法的调用中通过realSubject.getClass.getClassLoader调用来获取到了目标类的类加载器,这里有兴趣的读者可以了解一下Java的类加载机制,就可以知道为什么这里需要用到类加载器,然后通过new Class { Subject.class }来指定了代理对象要实现的接口数组,这里为什么是数组,是因为Java中接口可以是多继承,所以我们的目标类可能会继承多个接口,所以要用到数组。
上面的操作完成之后,接来就是了handler参数了,这个参数是InvocationHandler的实现类,也就是说在运行的时候,就会调用handler的invoke方法,而这个方法在我们实现LoggingInvocationHandler的时候就进行了重写,并且增加了自定义的日志记录逻辑。然后就可以通过proxy代理来调用request方法,这个时候,request方法就会被转发到LoggingInvocationHandler.invoke 方法中,然后执行我们自定义的逻辑,输出相应的结果实现。
总结通过上面的介绍我们也可以看出来,动态代理模式确实可以在不修改目标类代码的情况下对代理类增加额外的功能,增强了代码实现的灵活性。但是这种方式只能代理实现了某些固定接口的目标类,如果该目标类没有实现指定的接口,那么就无法实现动态代理操作了。
另外,从动态代理Proxy.newProxyInstance的调用逻辑来看,需要加载很多的内容,这是因为需要通过反射机制来进行扩展,虽然反射可以带来一定的灵活性,但是在程序性能方面会有所损耗,如果代码逻辑太复杂的话反而增加了代码的复杂度,让程序变的难以维护。本身的解耦也就变成了耦合。
以上就是基于接口的动态代理实现的全部的介绍,基于类的动态代理我们会在下一篇分享中来介绍,大家多多关注,敬请期待吧!
来源:从程序员到架构师