美文网首页Java
废话不多少,终于弄懂了mybatis plugin

废话不多少,终于弄懂了mybatis plugin

作者: 程序花生 | 来源:发表于2021-01-21 22:13 被阅读0次

    一、前言

    1月份已经过了一半多,天气回暖了许多,今天就来学习一下mybatis插件相关内容,可能mybatis插件使用得很少,但是对于某一些业务场景,plugin可以帮助解决一些问题,就比如脱敏问题,我们在实际中,我们需要导出Excel,但是并不希望用户信息完整的展示出来,所以我们可以脱敏,姓名只显示杨楠、 151*1234等等,所以plugin可以结合相应的业务场景进行开发

    二、mybatis plugin介绍

    2.1 四大核心对象

    ParameterHandler:用来处理传入SQL的参数,我们可以重写参数的处理规则。
    getParameterObject, setParameters
    ResultSetHandler:用于处理结果集,我们可以重写结果集的组装规则。 handleResultSets, handleOutputParameters 复制代码
    StatementHandler:用来处理SQL的执行过程,我们可以在这里重写SQL非常常用。
    prepare, parameterize, batch, update, query
    Executor:是SQL执行器,包含了组装参数,组装结果集到返回值以及执行SQL的过程,粒度比较粗。
    update, query, flushStatements, commit, rollback,getTransaction, close, isClosed

    2.2 Interceptor

    想要开发mybatis插件,我们只需要实现org.apache.ibatis.plugin.Interceptor接口,以下为Interceptor接口的结构:

    public interface Interceptor {
    
      //代理对象每次调用的方法,就是要进行拦截的时候要执行的方法。在这个方法里面做我们自定义的逻辑处理
      Object intercept(Invocation invocation) throws Throwable;
        
      /**
       *生成target的代理对象,通过调用Plugin.wrap(target,this)来生成
       */
      Object plugin(Object target);
    
      //用于在Mybatis配置文件中指定一些属性的,注册当前拦截器的时候可以设置一些属性
      void setProperties(Properties properties);
    
    }
    昨天我们刚刚学习了JDK代理,下面我们一起来看看Invocation对象
    
    public class Invocation {
    
      //需要拦截的对象
      private final Object target;
      //拦截target中的具体方法,也就是说Mybatis插件的粒度是精确到方法级别的。
      private final Method method;
      //拦截到的参数。
      private final Object[] args;
      ……getter/setter……
        
       //proceed 执行被拦截到的方法,你可以在执行的前后做一些事情。
       public Object proceed() throws InvocationTargetException, IllegalAccessException {
                //执行目标类的目标方法
                return method.invoke(target, args);
      }
        
    }
    

    以上的proceed方法(method.invoke(target, args))是否似曾相识,因为昨天在写代理的时候,JDK代理用到了该方法,执行被代理类的方法,表示指定目标类的目标方法

    2.3 拦截签名

    因为我们在Interceptor中提到了Invocation,主要用于获取拦截对象的信息,并且执行相应的方法,但是我们应该如何获取Invocation对象?

    @Intercepts(@Signature(type = ResultSetHandler.class, //拦截返回值
            method = "handleResultSets", //拦截的方法
            args = {Statement.class}))//参数
    这样其实就创建了一个Invocation对象
    
    @Signature参数
    
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Signature {
      
      //定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个
      Class<?> type();
    
      //在定义拦截类的基础之上,在定义拦截的方法,主要是看type里面取哪一个拦截的类,取该类当中的方法
      String method();
    
      //在定义拦截方法的基础之上在定义拦截的方法对应的参数,
      Class<?>[] args();
    }
    

    举个例子:

    比如,我们需要拦截Executor类的update方法,思路如下:

    定义Interceptor的实现类

    public class MybatisPlugin implements Interceptor{}
    

    增加@Intercepts注解,标识需要拦截的类、方法、方法参数

    /**
     * 插件签名,告诉当前的插件用来拦截哪个对象的哪种方法
     */
    @Intercepts(
            @Signature(
                    type = Executor.class, 
                    method = "update",
                    args = {MappedStatement.class,Object.class}
                    )
    )
    @Slf4j
    public class MybatisPlugin implements Interceptor {}
    
    
    @Signature注解:
      1.type:  表示所需要拦截的类为Executor
      2.method:为Executor类中的update方法
      3.args:  这个对应update的的参数,如下:
                        int update(MappedStatement ms, Object parameter) 
    

    实现intercept、plugin、setProperties方法

    /**
     * 拦截目标对象的目标方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("拦截目标对象:{}",invocation.getTarget());
        Object proceed = invocation.proceed();
        log.info("intercept拦截到的返回值:{}",proceed);
        return proceed;
    }
    
    /**
     * 包装目标对象 为目标对象创建代理对象
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        log.info("将要包装的目标对象:{}",target);
        //创建代理对象
        return Plugin.wrap(target,this);
    }
    
    /**
     * 获取配置文件的属性
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        log.info("获取配置文件参数");
    }
    

    注入plugin

    @Configuration
    public class MybatisPluginBean {
    
        @Bean
        public MybatisPlugin mybaticPlugin(){
            return new MybatisPlugin();
        }
    }
    

    2.4 MetaObject

    Mybatis提供了一个工具类:

    org.apache.ibatis.reflection.MetaObject
    

    它通过反射来读取和修改一些重要对象的属性。我们可以利用它来处理四大对象的一些属性,这是Mybatis插件开发的一个常用工具类。

    Object getValue(String name) 根据名称获取对象的属性值,支持OGNL表达式。
    void setValue(String name, Object value) 设置某个属性的值。
    Class<?> getSetterType(String name) 获取setter方法的入参类型。
    Class<?> getGetterType(String name) 获取getter方法的返回值类型。
    通常我们使用SystemMetaObject.forObject(Object object)来实例化MetaObject对象。
    

    三、mybatis脱敏插件

    1、首先,定义函数接口,用于存储脱敏策略
    2、定义注解,用于标识需要脱敏的属性
    3、实现Interceptor接口,用于处理脱敏操作
    4、注册插件

    3.1 定义函数接口

    JDK8开始,加入了函数式编程接口,之前我们给对象传递的都是值,但是现在我们可以传递表达式,我们只需要继承Function<String,String>

    /**
     * 函数式接口
     */
    public interface Declassified extends Function<String,String> {
    
    }
    

    3.2 定义脱敏策略

    脱敏策略,主要是约定名字应该如何脱敏,将杨羽茉转换为杨*茉

    /**
     * 脱敏的策略
     */
    public enum SensitiveStrategy{
      
        //定义名称脱敏处理表达式
        NAME(s -> s.replaceAll("(\\S)\\S(\\S*)","$1*$2"));
    
        private Declassified declassified;
    
        //注入脱敏函数式接口
         SensitiveStrategy(Declassified declassified){
            this.declassified = declassified;
        }
    
        //获取脱敏函数式接口
        public Declassified getDeclassified() {
            return declassified;
        }
    }
    

    写到这里,我们进行拓展,学习s.replaceAll()的使用方式:

    replaceAll(): 给定的参数 replacement 替换字符串所有匹配给定的正则表达式的子字符串

    public String replaceAll(String regex, String replacement)
    
    regex -- 匹配此字符串的正则表达式。
    
    replacement -- 用来替换每个匹配项的字符串。
    
        @Test
        void contextLoads() {
            this.strProcess("杨羽茉");
        }
    
    public void strProcess(String name){
       //意思:将name转换为$1*$2方式,也就是将中间的字符替换为a*b
       String s = name.replaceAll("(\\S)\\S*(\\S)", "$1*$2");
       System.out.println("转换的字符串:"+s);
    }
    
    
    输出结果:
                转换的字符串:杨*茉
    

    再来一个例子,脱敏手机号码:

    public void strProcess(String phone){
            //将手机号码转换为131***2172的方式
        //(\\d{3})代表$1显示前3位
        // \\d* 代表中间部分替换为***
        //(\\d{4}) 代表$2显示后三位
            String s = phone.replaceAll("(\\d{3})\\d*(\\d{4})", "$1***$2");
            System.out.println("转换的字符串:"+s);
      
        }
    

    3.2 定义脱敏注解

    /**
     * 脱敏注解:标识需要脱敏的字段,并且指定具体的脱敏策略
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Sensitive {
        //脱敏策略
        SensitiveStrategy strategy();
    }
    

    3.4 实现Interceptor接口

    /**
     * mybatis插件
     */
    @Intercepts(
            @Signature(
                    type = ResultSetHandler.class, //表示需要拦截的是返回值
                    method = "handleResultSets", //表示需要拦截的方法
                    args = {Statement.class} //表示需要拦截方法的参数
            )
    )
    
    public class MybatisPlugin implements Interceptor {
    
        private Logger log = LoggerFactory.getLogger(MybatisPlugin.class);
    
        /**
         * 拦截方法
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            //执行目标方法
            List<Object> records = (List<Object>) invocation.proceed();
            records.forEach(this::sensitive);
            return records;
        }
    
        /**
         * 过滤@Sensitive注解
         * @param source
         */
        private void sensitive(Object source){
            //获取返回值类型
            Class<?> sourceClass = source.getClass();
    
            //获取返回值的metaObject:通过反射来读取和修改一些重要对象的属性
            MetaObject metaObject = SystemMetaObject.forObject(source);
    
            //我们在拦截的时候,需要过滤没有@Sensitive注解的属性,如果有注解,在doSensitive中进行脱敏操作
            Stream.of(sourceClass.getDeclaredFields())
                    .filter(field -> field.isAnnotationPresent(Sensitive.class))
                    .forEach(field -> doSensitive(metaObject,field));
        }
    
        /**
         * 脱敏操作
         * @param metaObject
         * @param field
         */
        private void doSensitive(MetaObject metaObject, Field field){
            //获取属性名
            String name = field.getName();
            //获取属性值
            Object value = metaObject.getValue(name);
    
    
            //只有字符串才可以脱敏
            if(String.class == metaObject.getGetterType(name) && value != null){
                //获取自定义注解
                Sensitive annotation = field.getAnnotation(Sensitive.class);
                //获取自定义注解的参数
                SensitiveStrategy strategy = annotation.strategy();
                //脱敏操作
                String apply = strategy.getDeclassified().apply((String) value);
    
                //将脱敏后的数据放回返回值中
                metaObject.setValue(name,apply);
            }
        }
    
        /**
         * 生成代理对象
         * @param target
         * @return
         */
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target,this);
        }
    
        /**
         * 设置属性
         * @param properties
         */
        @Override
        public void setProperties(Properties properties) {
    
        }
    }
    

    3.5 注册插件

    @Configuration
    public class MybatisPluginConfig {
    
        @Bean
        public MybatisPlugin mybatisPlugin(){
            return new MybatisPlugin();
        }
    
    }
    

    使用注解

    @Sensitive(strategy = SensitiveStrategy.NAME)
    private String name;
    

    结果

    {
      userId=1,
      name=管*员
    }
    

    Mybatis plugin已经完成,明天开始要认真的开始系统化的学习netty相关内容,明天写一篇netty整合websocket,晚安!

    作者:Yangzinan
    链接:https://juejin.cn/post/6919846755995484168
    来源:掘金

    相关文章

      网友评论

        本文标题:废话不多少,终于弄懂了mybatis plugin

        本文链接:https://www.haomeiwen.com/subject/mghjzktx.html