暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

一文了解拦截器与拦截器链的实现

CoderGeshu 2020-08-09
365

拦截器和拦截器链

本文中的拦截器是使用 JDK 动态代理来实现的
前文须知:JDK的动态代理☆★☆

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

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
先调用接口方法,然后再一层代理一层代理的往里执行,最后执行真实对象中的方法,其执行结果也就如上所示。

👉JDK动态代理模式

👉简单了解一下Java反射技术?

👉逆向工程Mybatis Generator代码生成


分享、点赞、在看

文章转载自CoderGeshu,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论