从‘’动态代理‘’到‘’拦截器‘’的一系列学习

java动态代理机制中有两个重要的类和接口InvocationHandler(接口)和Proxy(类),这一个类Proxy和接口InvocationHandler是我们实现动态代理的核心

1. InvocationHandler接口

InvocationHandler中包含invoke()方法

我们通过动态代理对象调用方法时,这个方法的调用就会被转发到实现了InvocationHandler接口类的invoke()方法来调用

下面是在评论区一个大佬举得例子,我觉得很形象

public class DynamicProxy {


    public static void main(String[] args) {
        // 小韭菜学生类
        Student ordinaryStudents = new OrdinaryStudents();
        ordinaryStudents.eat();
        ordinaryStudents.write();

        // 现在有一位特殊的学生,他是区长的儿子,我们自然要对他额外照顾,要给他加一下功能。
        // 一种思路是定义一个类:区长的儿子类,他继承自学生类,但世上儿子千千万,有区长的儿子,也有市长的儿子,更有省长的儿子,不能把他们挨个定义出来,
        // 现在就可以使用动态代理机制,动态的给区长的儿子加上功能,以后碰到市长、省长的儿子也同样处理。
//--------------------------------------------------------------------------------
        //下面的思路很重要
        
        // InvocationHandler作用就是,当代理对象的原本方法被调用的时候,会重定向到一个方法,
        // 这个方法就是InvocationHandler里面定义的内容,同时会替代原本方法的结果返回。
        // InvocationHandler接收三个参数:
        //			proxy,代理后的实例对象
        //			method,对象被调用方法
        //			args,调用时的参数

        InvocationHandler handler = (proxy, method, handlerArgs) -> {
            // 定义eat方法
            if ("eat".equals(method.getName())) {
                System.out.println("我可以吃香喝辣!");
                return null;
            }
            // 定义write方法
            if ("write".equals(method.getName())) {
                System.out.println("我的作文题目是《我的区长父亲》。");
                //调用普通学生类的write方法,流程还是要走的,还是要交一篇作文上去,不能太明目张胆
                method.invoke(ordinaryStudents, handlerArgs);
                System.out.println("我的作文拿了区作文竞赛一等奖!so easy!");
                return null;
            }
            return null;
        };
        
        // 对这个实例对象代理生成一个代理对象。
        // 被代理后生成的对象,是通过People接口的字节码增强方式创建的类而构造出来的。它是一个临时构造的实现类的对象。
        // loder和interfaces基本就是决定了这个类到底是个怎么样的类。而h是InvocationHandler,决定了这个代理类到底是多了什么功能.
        // 通过这些接口和类加载器,拿到这个代理类class。然后通过反射的技术复制拿到代理类的构造函数,
        // 最后通过这个构造函数new个一对象出来,同时用InvocationHandler绑定这个对象。
        // 最终实现可以在运行的时候才切入改变类的方法,而不需要预先定义它。
        Student sonOfDistrict = (Student) Proxy.newProxyInstance(ordinaryStudents.getClass().getClassLoader(), ordinaryStudents.getClass().getInterfaces(), handler);
        
        //这里的Proxy.newProxyInstance在下文有介绍
        
        sonOfDistrict.eat();
        sonOfDistrict.write();

    }
}

/*最终输出:
我在吃饭!
我在写作文!
我可以吃香喝辣!
我的作文题目是《我的区长父亲》。
我在写作文!
我的作文拿了区作文竞赛一等奖!so easy! */

getInterfaces()方法和Java的反射机制有关。它能够获得这个对象所实现的接口

2. 用 Java 实现拦截器 Interceptor 的拦截功能

https://blog.csdn.net/qq_35246620/article/details/68484407

理解拦截器的实现

3. JDK动态代理类Proxy.newProxyInstance()

动态代理是在运行时生成代理对象的技术,允许你拦截对目标对象方法的调用,并在调用前后执行自定义的逻辑

Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法

newProxyInstance() 方法的签名如下:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

这个方法的作用就是创建一个代理类对象,它接收三个参数,我们来看下几个参数的含义:

loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用

在上面的例子:

Student sonOfDistrict = (Student) Proxy.newProxyInstance(ordinaryStudents.getClass().getClassLoader(), ordinaryStudents.getClass().getInterfaces(), handler);

4. Spring MVC 拦截器

原理:拦截器Interceptor的拦截功能是基于 Java 的动态代理来实现的,参考第3点

实现:通过两种途径,第一种是实现HandlerInterceptor接口,第二种是实现WebRequestInterceptor接口

4.1 HandlerInterceptor 接口

HandlerInterceptor接口中,定义了 3 个方法,分别为preHandle()postHandle()afterCompletion(),我们就是通过复写这 3 个方法来对用户的请求进行拦截处理的

preHandle:处理器执行之前执行,如果返回 false 将跳过处理器、拦截器 postHandle 方法、视图渲染等,直接执行拦截器 afterCompletion 方法。
postHandle:处理器执行后,视图渲染前执行,如果处理器抛出异常,将跳过该方法直接执行拦截器 afterCompletion 方法。
afterCompletion:视图渲染后执行,不管处理器是否抛出异常,该方法都将执行,清理资源

public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("请求来了");
        return true;
    }
}

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("已登录");
        return true;
    }
}

API配置

@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor());
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

参考

  1. https://blog.csdn.net/yaomingyang/article/details/80981004
  2. https://blog.csdn.net/qq_35246620/article/details/68484407