Mybatis 拦截器 说明和使用 (一)

时间:2022-10-18 08:00:46

一.拦截器简介

本节不介绍使用,先说原理

Mybatis 定义了四个处理器,用于做 SQL 执行的默认处理;如果我们不添加拦截器,则 Mybatis 会按默认操作进行处理。
如果添加了拦截器,则其会先执行拦截器内的增强处理,基于JDK动态代理实现,再进行默认操作
PageHelper 分页插件就是基于拦截器实现的
其次,自定义拦截器要注意顺序问题,先注册的后生效,后注册的先生效,为什么这样下面会提到

二.拦截器实现说明

参考 Mybatis 实现写了一个演示 Demo,Mybatis 的应用下节介绍

1.拦截器链

/**
 * 拦截器链
 */
static class InterceptorChain{
    private final List<Interceptor> interceptors = new ArrayList<>();

    /**
     * 获取SqlSession时生成拦截器链代理类
     * @param target
     * @return
     */
    public Object pluginAll(Object target) {
        //第一次添加的拦截器对象,生成代理对象,原对象指向新生成的代理对象
        //第二次新的代理对象作为原对象,生成代理对象...
        //循环,直到所有注册的拦截器都遍历一遍
        for (Interceptor interceptor : interceptors) {
            target = interceptor.plugin(target);
        }
        return target;
    }

    /**
     * 依次添加拦截器实现
     * @param interceptor
     */
    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

}

2.拦截器接口

用于实现自定义拦截器的接口,可以在实现类内添加自定义的操作

/**
 * 拦截器接口
 * 拦截方法参数类,封装了 被代理类、方法对象类、参数 属性
 */
public interface Interceptor {

    /**
     * 拦截方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    Object intercept(Invocation invocation) throws Throwable;

    /**
     * 插件包裹:将目标类封装逐层包裹
     * @param target
     * @return
     */
    default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
}

3.执行器类

封装了被代理对象、方法对象、参数集合三个属性,还有一个 proceed 方法,proceed 内基于反射实现了被代理类方法的调用;此类即为拦截器方法的参数

/**
 * 执行器
 */
static class Invocation {

    private final Object target;
    private final Method method;
    private final Object[] args;

    public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }

    public Object getTarget() {
        return target;
    }

    public Method getMethod() {
        return method;
    }

    public Object[] getArgs() {
        return args;
    }

    /**
     * Proceed 实际就是反射中方法对象的Invoke
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }

}

4.拦截器注解定义

1.拦截器注解

用于标识,哪个类是 Mybatis 拦截类,同时如果在SpringBoot框架下可结合 @Component 注解直接将自定义拦截器注册到上面的链内,但是要注意顺序问题,多个自定义拦截器最好手动注册,当前类取了下面的签名类集合作为值

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
    /**
     * 签名注解
     * @return
     */
    Signature[] value();
}

2.方法签名注解

签名注解,表名拦截的是哪个处理器的哪个方法,即三个属性:方法名、类、方法参数类型;此处取值原理仍与反射机制相关,不清楚反射的可以参考下面Demo理解一下

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature{

    /**
     * 方法名,拦截类,方法参数类型
     * @return
     */
    String method();
    Class<?> type();
    Class<?>[] args();
}

3.反射Demo

package entity;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author 
 * @date 2022-10-16 21:37
 * @since 1.8
 */
public class ReflexTest {


    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        Reflex reflex = new Reflex();

        Method method = Reflex.class.getMethod("print", Integer.class);

        method.invoke(reflex,0);

        method = Reflex.class.getMethod("print", Integer.class,String.class);

        method.invoke(reflex,1,"model");

    }

    static class Reflex{

        /**
         * 打印测试
         * @param type
         */
        public void print(Integer type){
            System.out.println("单一参数:" + type);
        }

        /**
         * 打印测试
         * @param type
         * @param model
         */
        public void print(Integer type,String model){
            System.out.println("多个参数:" + type + ":" + model);
        }
    }
}

Mybatis 拦截器 说明和使用 (一)

5.默认处理器类

定义了两个默认处理器接口,用于演示,Mybatis 实际存在四个处理器类,用于实现参数处理、SQL处理、结果处理等操作;自定义拦截器是实现拦截器接口,不是实现处理器接口,处理器接口用于注册方法签名时要处理哪个处理器类的哪个方法

1.处理器一

接口

/**
 * 处理器接口
 */
public interface FirstHandler{
    String query(String type,Integer param);
}

实现

//默认实现
static class FirstHandlerImpl implements FirstHandler{

    @Override
    public String query(String type,Integer param) {
       switch (param){
           case 0:
               return "零";
           case 1:
               return "一";
           case 2:
               return "二";
           default:
               return "default";
       }
    }
}

2.处理器二

接口

/**
 * 处理器接口
 */
public interface SecondHandler{
    String select(Integer param);
}

实现

//默认实现
static class SecondHandlerImpl implements SecondHandler{

    @Override
    public String select(Integer param) {
        switch (param){
            case 0:
                return "SecondHandler:零";
            case 1:
                return "SecondHandler:一";
            case 2:
                return "SecondHandler:二";
            default:
                return "SecondHandler:default";
        }
    }
}

6.插件类(核心操作)

此类定义了一个 wrap 方法,用于包裹被代理类,会从默认处理器开始按注册顺序逐层包裹,最后全部包裹完成,返回代理对象,通过代理对象调用时会从外向内逐层调用,Plugin 完全取自Mybatis,没有修改,同学可以根据注释自己理解一下,这也是上面提到的先注册后执行,后注册先执行的原理,同时可能因此产生覆盖效果(后处理的覆盖先处理的)

/**
 * 插件类生成动态代理,实现InvocationHandler
 */
static class Plugin implements InvocationHandler {

    private final Object target;
    private final Interceptor interceptor;
    private final Map<Class<?>, Set<Method>> signatureMap;
    private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }

    /**
     * 包裹拦截器
     * @param target
     * @param interceptor
     * @return
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        //取注册类和方法
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        //取被代理(目标)类类型
        Class<?> type = target.getClass();
        //取被代理(目标)类所实现了的接口类中被注册了的部分
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        //如果实现了注册类则生成代理对象
        if (interfaces.length > 0) {
            //基于JDK动态代理方式(接口反射)生成代理类
            return Proxy.newProxyInstance(
                    type.getClassLoader(),
                    //接口类
                    interfaces,
                    //封装了每层循环的原始被代理对象,拦截器类 和 注册的实现方法
                    new Plugin(target, interceptor, signatureMap));
        }
        //否则返回被代理对象(原对象)
        return target;
    }

    /**
     * 调用(增强实现)
     *
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Set<Method> methods = signatureMap.get(method.getDeclaringClass());
            if (methods != null && methods.contains(method)) {
                //代理调用
                return interceptor.intercept(new Invocation(target, method, args));
            }
            //未注册拦截器时直接调用原对象
            return method.invoke(target, args);
        } catch (Exception e) {
            //TODO 抛出异常
            System.out.println(e);
            return null;
        }
    }

    /**
     * 根据签名注解取出要处理的类和方法
     * @param interceptor
     * @return
     */
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        //获取自定义拦截器上的拦截器类注解
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        //TODO 非空校验,如果没有指定的注解,则无需向下解析了
        //获取值,签名集合
        Signature[] sigs = interceptsAnnotation.value();
        //定义一个 HashMap 缓存签名类和方法集合
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
        for (Signature sig : sigs) {
            //按操作类取方法集合,存在则取出,否则新建
            Set<Method> methods = computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
            try {
                //根据方法名和参数类型,取签名类的方法对象,并添加到集合
                Method method = sig.type().getMethod(sig.method(), sig.args());
                methods.add(method);
            } catch (NoSuchMethodException e) {
                //TODO 抛出异常
            }
        }
        //添加完成后返回签名缓存
        return signatureMap;
    }

    /**
     * 递归取出所有实现的接口类集合
     * @param type
     * @param signatureMap
     * @return
     */
    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        //定义接口类集合
        Set<Class<?>> interfaces = new HashSet<>();
        //类不为NULL
        while (type != null) {
            //遍历目标类所实现的接口类
            for (Class<?> c : type.getInterfaces()) {
                //如果是被注册的方法类,就添加到集合
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
            //取父类(单继承多实现,如果没有显示继承某个父类,则取出结果为Object类,再取则为NULL,循环结束)
            type = type.getSuperclass();
        }
        //返回注册的接口类的数组
        return interfaces.toArray(new Class<?>[0]);
    }

    /**
     * 对 MAP 官方方法做了一层封装,不存在则创建一个值对象并返回
     * @param map
     * @param key
     * @param mappingFunction
     * @return
     * @param <K>
     * @param <V>
     */
    public static <K, V> V computeIfAbsent(Map<K, V> map, K key, Function<K, V> mappingFunction) {
        V value = map.get(key);
        if (value != null) {
            return value;
        }
        return map.computeIfAbsent(key, mappingFunction);
    }
}

7.自定义拦截器(测试)

上面的代码已经实现了拦截器框架,下面定义两个测试类验证一下

1.混合拦截

@Intercepts({
            @Signature(method = "query", type = FirstHandler.class,args = {String.class,Integer.class}),
            @Signature(method = "query", type = SecondHandler.class,args = {Integer.class}),
    })
static class MyInterceptor implements Interceptor{

     /**
      * 拦截器实现
      * @param invocation
      * @return
      * @throws Throwable
      */
     @Override
     public Object intercept(Invocation invocation) throws Throwable {

         //TODO 增强处理
         invocation.getArgs()[1] = 2;

         //反射对象Invocation,Proceed实际封装了方法调用:method.invoke(target, args) 此处即被代理类调用
         return invocation.proceed();
     }
 }

2.仅拦截第一类处理器

/**
 * 第二处理 拦截 1
 */
@Intercepts({
        @Signature(method = "select", type = SecondHandler.class,args = {Integer.class}),
})
static class SecondInterceptorOne implements Interceptor{

    /**
     * 拦截器实现
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        //TODO 增强处理
        invocation.getArgs()[0] = 1;

        //反射对象Invocation,Proceed实际封装了方法调用:method.invoke(target, args) 此处即被代理类调用
        return invocation.proceed();
    }
}

3.仅拦截第二类处理

/**
 * 第二处理 拦截 2
 */
@Intercepts({
        @Signature(method = "select", type = SecondHandler.class,args = {Integer.class}),
})
static class SecondInterceptorTwo implements Interceptor{

    /**
     * 拦截器实现
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        //TODO 增强处理
        invocation.getArgs()[0] = 2;

        //反射对象Invocation,Proceed实际封装了方法调用:method.invoke(target, args) 此处即被代理类调用
        return invocation.proceed();
    }
}

三.测试

将上面所有类和方法写到一个测试类,并通过主方法调用验证

package entity;

import java.lang.annotation.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.function.Function;

/**
 * @author 
 * @date 2022-10-12 17:43
 * @since 1.8
 */
public class TEST {

    /**
     * 测试类
     * 拦截器链是先注册拦截器实现类,再逐层生成动态代理,
     * 最先注册的是最原始被代理对象,最后注册的是最新代理对象,
     * 所以使用时是最后注册的拦截器类先生效,先注册的后生效,同时可能因此后生效的会覆盖先生效的处理
     *
     * Mybatis 拦截器在使用时,他会自己最先 New 一个默认的被代理类作为参数传给 pluginAll 然后才开始包裹拦截器链的实现类
     * @param args
     */
    public static void main(String[] args) {

        InterceptorChain chain = new InterceptorChain();

        chain.addInterceptor(new MyInterceptor());

        chain.addInterceptor(new SecondInterceptorOne());

        chain.addInterceptor(new SecondInterceptorTwo());

        FirstHandler firsthandler = (FirstHandler) chain.pluginAll(new FirstHandlerImpl());

        String result = firsthandler.query("",0);

        System.out.println(result);

        SecondHandler secondHandler = (SecondHandler) chain.pluginAll(new SecondHandlerImpl());

        System.out.println(secondHandler.select(0));

    }

    @Intercepts({
            @Signature(method = "query", type = FirstHandler.class,args = {String.class,Integer.class}),
            @Signature(method = "query", type = SecondHandler.class,args = {Integer.class}),
    })
    static class MyInterceptor implements Interceptor{

        /**
         * 拦截器实现
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {

            //TODO 增强处理
            invocation.getArgs()[1] = 2;

            //反射对象Invocation,Proceed实际封装了方法调用:method.invoke(target, args) 此处即被代理类调用
            return invocation.proceed();
        }
    }

    /**
     * 第二处理 拦截 1
     */
    @Intercepts({
            @Signature(method = "select", type = SecondHandler.class,args = {Integer.class}),
    })
    static class SecondInterceptorOne implements Interceptor{

        /**
         * 拦截器实现
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {

            //TODO 增强处理
            invocation.getArgs()[0] = 1;

            //反射对象Invocation,Proceed实际封装了方法调用:method.invoke(target, args) 此处即被代理类调用
            return invocation.proceed();
        }
    }

    /**
     * 第二处理 拦截 2
     */
    @Intercepts({
            @Signature(method = "select", type = SecondHandler.class,args = {Integer.class}),
    })
    static class SecondInterceptorTwo implements Interceptor{

        /**
         * 拦截器实现
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {

            //TODO 增强处理
            invocation.getArgs()[0] = 2;

            //反射对象Invocation,Proceed实际封装了方法调用:method.invoke(target, args) 此处即被代理类调用
            return invocation.proceed();
        }
    }


    /**
     * 拦截器链
     */
    static class InterceptorChain{
        private final List<Interceptor> interceptors = new ArrayList<>();

        /**
         * 获取SqlSession时生成拦截器链代理类
         * @param target
         * @return
         */
        public Object pluginAll(Object target) {
            //第一次添加的拦截器对象,生成代理对象,原对象指向新生成的代理对象
            //第二次新的代理对象作为原对象,生成代理对象...
            //循环,直到所有注册的拦截器都遍历一遍
            for (Interceptor interceptor : interceptors) {
                target = interceptor.plugin(target);
            }
            return target;
        }

        /**
         * 依次添加拦截器实现
         * @param interceptor
         */
        public void addInterceptor(Interceptor interceptor) {
            interceptors.add(interceptor);
        }

    }

    /**
     * 拦截器接口
     * 拦截方法参数类,封装了 被代理类、方法对象类、参数 属性
     */
    public interface Interceptor {

        /**
         * 拦截方法
         * @param invocation
         * @return
         * @throws Throwable
         */
        Object intercept(Invocation invocation) throws Throwable;

        /**
         * 插件包裹:将目标类封装逐层包裹
         * @param target
         * @return
         */
        default Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    }

    /**
     * 处理器接口
     */
    public interface FirstHandler{
        String query(String type,Integer param);
    }

    //默认实现
    static class FirstHandlerImpl implements FirstHandler{

        @Override
        public String query(String type,Integer param) {
           switch (param){
               case 0:
                   return "零";
               case 1:
                   return "一";
               case 2:
                   return "二";
               default:
                   return "default";
           }
        }
    }

    /**
     * 处理器接口
     */
    public interface SecondHandler{
        String select(Integer param);
    }

    //默认实现
    static class SecondHandlerImpl implements SecondHandler{

        @Override
        public String select(Integer param) {
            switch (param){
                case 0:
                    return "SecondHandler:零";
                case 1:
                    return "SecondHandler:一";
                case 2:
                    return "SecondHandler:二";
                default:
                    return "SecondHandler:default";
            }
        }
    }

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Intercepts {
        /**
         * 签名注解
         * @return
         */
        Signature[] value();
    }

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Signature{

        /**
         * 方法名,拦截类,方法参数类型
         * @return
         */
        String method();
        Class<?> type();
        Class<?>[] args();
    }

    /**
     * 执行器
     */
    static class Invocation {

        private final Object target;
        private final Method method;
        private final Object[] args;

        public Invocation(Object target, Method method, Object[] args) {
            this.target = target;
            this.method = method;
            this.args = args;
        }

        public Object getTarget() {
            return target;
        }

        public Method getMethod() {
            return method;
        }

        public Object[] getArgs() {
            return args;
        }

        /**
         * Proceed 实际就是反射中方法对象的Invoke
         * @return
         * @throws InvocationTargetException
         * @throws IllegalAccessException
         */
        public Object proceed() throws InvocationTargetException, IllegalAccessException {
            return method.invoke(target, args);
        }

    }

    /**
     * 插件类生成动态代理,实现InvocationHandler
     */
    static class Plugin implements InvocationHandler {

        private final Object target;
        private final Interceptor interceptor;
        private final Map<Class<?>, Set<Method>> signatureMap;
        private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
            this.target = target;
            this.interceptor = interceptor;
            this.signatureMap = signatureMap;
        }

        /**
         * 包裹拦截器
         * @param target
         * @param interceptor
         * @return
         */
        public static Object wrap(Object target, Interceptor interceptor) {
            //取注册类和方法
            Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
            //取被代理(目标)类类型
            Class<?> type = target.getClass();
            //取被代理(目标)类所实现了的接口类中被注册了的部分
            Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
            //如果实现了注册类则生成代理对象
            if (interfaces.length > 0) {
                //基于JDK动态代理方式(接口反射)生成代理类
                return Proxy.newProxyInstance(
                        type.getClassLoader(),
                        //接口类
                        interfaces,
                        //封装了每层循环的原始被代理对象,拦截器类 和 注册的实现方法
                        new Plugin(target, interceptor, signatureMap));
            }
            //否则返回被代理对象(原对象)
            return target;
        }

        /**
         * 调用(增强实现)
         *
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                Set<Method> methods = signatureMap.get(method.getDeclaringClass());
                if (methods != null && methods.contains(method)) {
                    //代理调用
                    return interceptor.intercept(new Invocation(target, method, args));
                }
                //未注册拦截器时直接调用原对象
                return method.invoke(target, args);
            } catch (Exception e) {
                //TODO 抛出异常
                System.out.println(e);
                return null;
            }
        }

        /**
         * 根据签名注解取出要处理的类和方法
         * @param interceptor
         * @return
         */
        private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
            //获取自定义拦截器上的拦截器类注解
            Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
            //TODO 非空校验,如果没有指定的注解,则无需向下解析了
            //获取值,签名集合
            Signature[] sigs = interceptsAnnotation.value();
            //定义一个 HashMap 缓存签名类和方法集合
            Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
            for (Signature sig : sigs) {
                //按操作类取方法集合,存在则取出,否则新建
                Set<Method> methods = computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
                try {
                    //根据方法名和参数类型,取签名类的方法对象,并添加到集合
                    Method method = sig.type().getMethod(sig.method(), sig.args());
                    methods.add(method);
                } catch (NoSuchMethodException e) {
                    //TODO 抛出异常
                }
            }
            //添加完成后返回签名缓存
            return signatureMap;
        }

        /**
         * 递归取出所有实现的接口类集合
         * @param type
         * @param signatureMap
         * @return
         */
        private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
            //定义接口类集合
            Set<Class<?>> interfaces = new HashSet<>();
            //类不为NULL
            while (type != null) {
                //遍历目标类所实现的接口类
                for (Class<?> c : type.getInterfaces()) {
                    //如果是被注册的方法类,就添加到集合
                    if (signatureMap.containsKey(c)) {
                        interfaces.add(c);
                    }
                }
                //取父类(单继承多实现,如果没有显示继承某个父类,则取出结果为Object类,再取则为NULL,循环结束)
                type = type.getSuperclass();
            }
            //返回注册的接口类的数组
            return interfaces.toArray(new Class<?>[0]);
        }

        /**
         * 对 MAP 官方方法做了一层封装,不存在则创建一个值对象并返回
         * @param map
         * @param key
         * @param mappingFunction
         * @return
         * @param <K>
         * @param <V>
         */
        public static <K, V> V computeIfAbsent(Map<K, V> map, K key, Function<K, V> mappingFunction) {
            V value = map.get(key);
            if (value != null) {
                return value;
            }
            return map.computeIfAbsent(key, mappingFunction);
        }
    }
}

结果

拦截 操作
MyInterceptor FirstHandler 的 query(String type,Integer param) 将第 2 个参数改为 2
MyInterceptor SecondHandler 的 query(Integer param) 方法不存在,所以不生效
SecondInterceptorOne SecondHandler 的 select(Integer param) 将参数改为 1
SecondInterceptorTwo SecondHandler 的 select(Integer param) 将参数改为 2

拦截到处理器的调用,并修改其参数

Mybatis 拦截器 说明和使用 (一)

最里面包裹的是默认实现

Mybatis 拦截器 说明和使用 (一)
整体输出效果(部分异常捕获未处理,Mybatis 实际会抛出),SecondHandler 原始为 0 ,先修改为 2 ,后又修改为 1 ,修改被覆盖,最终返回了 “一”

Mybatis 拦截器 说明和使用 (一)