一文了解动态代理和拦截器

微信关注公众号:三朝猿老,第一时间获取最新分享。

1. 了解代理模式

什么是代理模式?代理模式就是给某一个对象提供一个代理对象,并由代理对象控制对原对象的访问。

为什么使用代理模式?使用代理模式的好处有很多,比如它可以起到隔离的作用(代理对象是对真实对象的一种隔离),符合面向对象设计的开闭原则等等。

通俗的来理解代理模式,它就类似于我们生活中常见的中介或代销商,比如我们需要买某某明星的唱片,我们只需要去相应的唱片店购买就行了,我们并不需要了解这个唱片是从哪生产制造的,这些和我们目的无关的因素都可以屏蔽掉,我们只需找到我们需要的唱片付钱就可以了,至于唱片店选择哪里的厂商、是否更换厂商,都不用我们考虑。

代理模式可以分为两种:静态代理、动态代理。静态代理是由程序员创建或特定工具自动生成源代码,再对其编译,静态代理类在编译期就生成,也就是说,在程序运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。

代理模式结构图:

Subject 接口中,定义代理类和真实对象的共用接口:

public interface Subject {
    
    
    void request();
}

RealSubject 表示真实实体对象:

public class RealSubject implements Subject {
    
    
    @Override
    pulic void request(){
    
    
        System.out.println("真实对象中的方法");
    }
}

Proxy 类,它保存一个真实对象的引用,使得代理可以访问真实对象,并且它们实现了同一接口规范,所以可以用来代替真实对象。当使用代理调用 request() 方法时,代理对象实际调用的还是真实对象的方法,不过我们可以在调用真实对象方法之前增加一些处理逻辑。

public class Proxy implements Subject {
    
    
    private RealSubject realSubject;
    
    @Override
    pulic void request(){
    
    
        if (realSubject == null) {
    
    
            // 创建真实的对象实例
            realSubject = new RealSubject();
        }
        // 代理对象实际调用的为真实对象中的方法
        realSubject.request();
    }
}

2. JDK 内置的动态代理模式

在 Java 中由多种动态代理的技术,比如 JDK、CGLIB 等,在这里主要介绍一下 JDK 的动态代理模式。

首先我们需要一个接口,这个接口可以用来下挂其实现类的代理对象(即真实类和代理类都实现的接口)。例如,我们创建如下接口,SayHello.java

public interface SayHello {
    
    
    void sayHello(String name);
}

接着,我们创建上述接口的一个实现类,SayHelloImpl.java

public class SayHelloImpl implements SayHello {
    
    
    @Override
    public void sayHello(String name) {
    
    
        System.out.println("Hello " + name);
    }
}

现在我们希望在真正使用 SayHelloImpl 类之前,需要先经过代理类的逻辑方法进行处理,然后由代理对象再决定要不要真正使用 SayHelloImpl 中的方法。与我们自己实现的代理模式不同的是,JDK 中已经定义好了代理类 Proxy,我们只需要使用它就可以构建代理对象,无需再自定义代理类。

使用 JDK 实现代理模式时,一般需要经过两个主要步骤,即:

① 为真实对象绑定代理对象;

② 实现代理对象的逻辑处理方法。

下边进行分步讲解:

① 为真实对象绑定代理对象

使用 JDK 绑定一个代理对象其实很简单,JDK 已经提供了相应的类和方法,屏蔽了其中的细节。例如我们为一个 Object 对象绑定一个代理对象,使用的代码如下:

Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                       obj.getClass().getInterfaces(), this);

它使用到的是 java.lang.reflect.Proxy 类,然后调用其 newProxyInstance 方法,它返回一个 Object 类型的代理对象。在 JDK 文档中,对这个方法的描述为:

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

意思就是:返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

在这个方法中,需要提供三个参数:

ClassLoader loader, @NotNull Class<?>[] interfaces, @Notnull InvocationHandler h

第一个参数是指定使用哪个类加载器;第二个参数是生成的代理对象可以下挂在哪些接口之下(即代理对象实现了哪些接口);第三个参数是指定实现代理逻辑方法的类,此类需要实现 InvocationHandler 接口,请看下面内容。

② 实现代理逻辑方法

在 JDK 中,实现代理逻辑方法的类必须要实现 java.lang.reflect.InvocationHandler 接口,看到这个限定名我们也可以发现动态代理就是对反射机制的一种应用。在 InvocationHandler 接口中,只存在一个需要实现的方法:

public interface InvocationHandler {
    
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

当我们使用代理对象调用相应接口中的方法时,就会自动触发 invoke 方法,并执行此方法中的处理逻辑。

在这里,大家有没有一种熟悉的感觉,以前在学习 Java GUI 的时候,如果我们需要一个拥有监听器的类,那么就需要实现一个 java.awt.event.ActionListener 接口,同样的我们还要实现这个接口下的唯一方法 public void actionPerformed(ActionEvent e),然后当添加监听器并由事件触发后,会自动执行 actionPerformed 实现方法的处理逻辑。我们这里也是如此,相当于我们在使用代理对象调用方法时,会自动触发 invoke 方法,然后执行其处理逻辑。

invoke 方法中,包含三个参数,分别的含义如下:

  • Object proxy : 代理对象,也就是使用 Proxy.newProxyInstance() 返回的对象。
  • Method method : 当前调度的方法。由反射获取,当使用代理对象调用接口中的方法时,会自动反射获取相应的真实对象实现的方法。
  • Object[] args : 调度方法的参数。

现在,我们创建使用 JDK 实现的代理模式,如 JDKProxy.java 所示:

package com.eric.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKProxy implements InvocationHandler {
    
    
    Object target = null;

    // 为真实对象绑定一个代理对象
    public Object bind(Object target) {
    
    
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }
   
    // 实现代理逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        System.out.println("执行真实对象方法前执行代理逻辑");
        // 执行真实对象方法
        Object obj = method.invoke(target, args);
        System.out.println("执行真实对象方法后执行的逻辑");
        return obj;
    }
}

上述代码中,我们定义 bind 方为真实对象 target 绑定一个代理对象并返回,并且这个代理对象的构造是使用的 target 的类加载器,返回的代理类可以下挂载 target 实现的接口下,并制定了实现代理逻辑方法的类 this,也就是当前类,所以当前类需要实现 InvocationHandler 接口的 invoke 方法,这一点和 ActionListener 的使用很相似。

在代理逻辑方法中,我们在真实对象调用方法之前和之后分别打印语句以验证处理逻辑的执行结果是否正确。

我们用如下代码进行测试,JDKProxyTest.java

package com.eric.proxy;

public class JDKProxyTest {
    
    
    public static void main(String[] args) {
    
    
        JDKProxy jdkProxy = new JDKProxy();
        SayHello proxy = (SayHello) jdkProxy.bind(new SayHelloImpl());
        proxy.sayHello("Eric");
    }
}

首先使用 JDKProxy 类返回一个 SayHelloImpl 的代理类,并且这个代理类下挂在 SayHello 接口下。然后当我们使用代理对象执行接口中的方法时,就会自动触发此代理对象所指定的代理逻辑方法,即 JDKProxy 中实现的 invoke 方法,然后就会执行 invoke 中的逻辑。

结果如下:

真实对象方法执行前执行代理逻辑
Hello Eric
真实对象方法执行后执行的逻辑

由验证结果可以看出,在调用真实对象的方法之前,首先执行了代理逻辑,在调用真实对象方法之后,也同样执行了相应的代理逻辑。

当然,我们在调用真实对象方法之前,可以根据相应的条件,判断是否需要调用真实对象的方法,这样其功能就相当于一个拦截器,拦截住不满足的条件,使其不能调用真实对象方法。

3. CGLIB动态代理

CGLIB 动态代理和 JDK 动态代理的区别:

  1. CGLIB 需要引入外部的 jar 包,如:cglib-3.2.5.jar;JDK 不需要额外依赖包。
  2. CGLIB 只需要一个普通 Java 类即可,而 JDK 需要下挂的接口。
  3. CGLIB 使用 Class 类型进行构建代理,而 JDK 使用的 Object 类型构建代理,并且它们使用的类也不同,CGLIB 使用 Enhancer 类,JDK 是使用 Proxy 类。

CGLIB 动态代理的实现

一个普通类 SayHello.java

public class SayHello {
    
    
    public void sayHello(String name) {
    
    
        System.out.println("Hello " + name);
    }
}

实现对真实类对象代理的绑定以及代理处理逻辑的实现,需要继承 net.sf.cglib.proxy.MethodInterceptor 类,并实现其中的 intercept 方法:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
    
    

    /**
     * 生成CGLIB代理对象
     *
     * @param cls 需要代理的Class类
     * @return Class类的CGLIB代理对象
     */
    public Object getProxy(Class cls) {
    
    
        // CGLIB Enhancer 增强类对象
        Enhancer enhancer = new Enhancer();
        // 设置增强类型
        enhancer.setSuperclass(cls);
        // 定义代理对象,要求代理对象必须实现 MethodInterceptor 方法,这里定义为当前对象
        enhancer.setCallback(this);
        // 生成并返回代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
        System.out.println("调用真实方法前");
        // 利用CGLIB调用真实对象的方法
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("调用真实方法后");
        return result;
    }
}

测试类:

public static void testCGLIBProxy(){
    
    
    CglibProxy cp = new CglibProxy();
    SayHello obj = (SayHello) cp.getProxy(SayHello.class);
    obj.sayHello("Eric");
}

运行结果:

Hello Eric

4. 拦截器和拦截器链

使用的 JDK 的动态代理来实现。

4.1 拦截器

拦截器,在 AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前进行拦截,然后进行某些操作。拦截器可以说相当于是个过滤器,筛选出符合条件的继续往下执行,不符合条件的就直接终止在拦截器阶段,并不会执行真实对象中的方法调用。

我们把这些过滤条件的处理逻辑单独的抽取出来,然后设计一个类来统一处理,那么这个类就是一个拦截器,拦截器可以使用拦截器接口以及其实现类的方式实现。

设计好拦截器后,我们只需要在前文 JDK 实现的动态代理中,在 invoke 方法中真实对象调用 method.invoke() 之前或之后加上拦截器,这样就会起到拦截作用。

如下,我们先来定义一个拦截器的接口 Interceptor,我们需要把拦截的处理逻辑都放在这里边,为了简单起见,我们这里只有一个简单的打印信息的方法:

package com.eric.proxy;

public interface Interceptor {
    
    
    void printInfo();
}

接着我们来设计一个拦截器的实现类,InterceptorImpl.java

package com.eric.proxy;

public class InterceptorImpl implements Interceptor {
    
    
    @Override
    public void printInfo() {
    
    
        System.out.println("执行拦截器中的方法");
    }
}

设计好拦截器之后,我们要想办法把它添加到 JDK 动态代理的 invoke 方法中,由此我们需要稍微修改一下 JDKProxy.java 的内容。 我们可以把设置拦截器设计的更灵活一点,在使用动态代理时选择使用拦截器,也可以选择不使用拦截器,下面我们来看一下修改后的内容:

package com.eric.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKProxy implements InvocationHandler {
    
    
    private Object target;
    // 定义拦截器,使用全限定名
    private String interceptorClass;

    public JDKProxy(Object target, String interceptorClass) {
    
    
        this.target = target;
        this.interceptorClass = interceptorClass;
    }

    // 绑定带有拦截器的代理对象
    public static Object bind(Object target, String interceptorClass) {
    
    
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), new JDKProxy(target, interceptorClass));
    }

    // 绑定不带有拦截器的代理对象
    public static Object bind(Object target) {
    
    
        return JDKProxy.bind(target, null);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
        // 如果未设置拦截器,则直接返回原方法调用
        if (interceptorClass == null) {
    
    
            return method.invoke(target, args);
        }
        // 否则就是使用了拦截器
        Interceptor interceptor = (Interceptor) Class.forName(interceptorClass).newInstance();
        // 则先执行拦截器之中的处理逻辑
        interceptor.printInfo();
        // 然后执行原方法调用或者由拦截器决定不执行原方法的调用
        return method.invoke(target, args);
        // 原方法调用后也可以再次使用拦截器进行结果处理
    }
}

由上述代码我们可以发现,我们增加了一个私有成员变量 interceptorClass、构造方法、重载的 bind 方法,另外在 invoke 中也成功使用了拦截器。下面我们来一个一个分析一下:

  • 私有变量 interceptorClass:它是字符串类型,用来存储某个具体拦截器的全限定名称,这样就可以利用反射机制构建出其实例对象并使用。
  • 构造方法:为两个成员变量赋值。
  • bind 方法:这里设计了两个重载的 bind 方法:
    • bind(Object target, String interceptorClass) 方法是为真实对象绑定代理对象,同时使用拦截器,需要提供具体的拦截器全限定名;
    • bind(Object target) 方法则是为真实对象绑定代理对象,并不使用拦截器。
  • 修改后的 invoke 方法:在方法中新增了处理逻辑,首先需要判断是否使用了拦截器,如果没有使用,则直接返回真实对象的方法调用;如果使用了拦截器,则首先利用反射构建出拦截器实例,然后再处理拦截器中的处理逻辑,最后由拦截器再决定是否要返回真实对象中的方法调用,这样就实现了拦截器的过滤作用。

设计测试类如下:

package com.eric.proxy;

public class JDKProxyTest {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("设置拦截器的代理执行结果:");
        // 生成指定拦截器的代理对象
        SayHello proxy = (SayHello) JDKProxy.bind(new SayHelloImpl(), "com.eric.proxy.InterceptorImpl");
        proxy.sayHello("Eric");
        System.out.println("\n不设置拦截器的执行结果:");
        // 生成未设置拦截器的代理对象
        proxy = (SayHello) JDKProxy.bind(new SayHelloImpl());
        proxy.sayHello("Jack");
    }
}

执行结果:

设置拦截器的代理执行结果:
执行拦截器中的方法
Hello Eric

不设置拦截器的执行结果:
Hello Jack

4.2 拦截器链

有时候在一个拦截器中无法做到复杂的拦截处理逻辑,因此可以把这些处理逻辑按照一定的顺序分别进行设计,然后又多个拦截器组成拦截器链,一个一个进行过滤,只有全部通过之后才能顺利调用真实对象方法。

我们可以为上述的拦截器接口 Interceptor 创建多个实现类,如下:
InterceptorImpl2.java

package com.eric.proxy;

public class InterceptorImpl2 implements Interceptor {
    
    
    @Override
    public void printInfo() {
    
    
        System.out.println("执行拦截器2");
    }
}

InterceptorImpl3.java

package com.eric.proxy;

public class InterceptorImpl3 implements Interceptor {
    
    
    @Override
    public void printInfo() {
    
    
        System.out.println("执行拦截器3");
    }
}

其他的都不需要修改,只需要再我们使用代理对象的时候,多设计几个代理就可以了,因此我们的测试类可以改成这样:

package com.eric.proxy;

public class JDKProxyTest {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("设置拦截器链,执行结果:");
        // 设计具有拦截器链的代理,注意每个bind方法中的真实对象的改变
        SayHello proxy = (SayHello) JDKProxy.bind(new SayHelloImpl(),
                "com.eric.proxy.InterceptorImpl");
        SayHello proxy2 = (SayHello) JDKProxy.bind(proxy,
                "com.eric.proxy.InterceptorImpl2");
        SayHello proxy3 = (SayHello) JDKProxy.bind(proxy2,
                "com.eric.proxy.InterceptorImpl3");
        // 由最终的代理进行调用方法
        proxy3.sayHello("Eric");
    }
}

执行结果:

设置拦截器链,执行结果:
执行拦截器3
执行拦截器2
执行拦截器中的方法
Hello Eric

仔细观察拦截器链的顺序以及上述的执行结果,我们可以发现,首先我们为 new SayHelloImpl() 对象生成了一个使用 InterceptorImpl 拦截器的代理对象 proxy,然后我们又对此代理对象 proxy 继续使用 InterceptorImpl2 拦截器生成代理对象 proxy2,最终再生成 proxy2 的代理对象 proxy3,它使用的是 InterceptorImpl3 拦截器。执行的时候,需要由最外层的代理也就是 proxy3 先调用接口方法,然后再一层代理一层代理的往里执行,最后执行真实对象中的方法,其执行结果也就如上所示。


微信关注公众号:三朝猿老,第一时间获取最新分享。

猜你喜欢

转载自blog.csdn.net/weixin_43653599/article/details/107921937