前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >废话不多少,终于弄懂了mybatis plugin

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

作者头像
Java程序猿阿谷
发布2021-01-27 10:33:50
7010
发布2021-01-27 10:33:50
举报
文章被收录于专栏:Java快速进阶通道

一、前言

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

二、mybatis plugin介绍

2.1 四大核心对象

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

2.2 Interceptor

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

代码语言:javascript
复制
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对象

代码语言:javascript
复制
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对象?

代码语言:javascript
复制
@Intercepts(@Signature(type = ResultSetHandler.class, //拦截返回值
        method = "handleResultSets", //拦截的方法
        args = {Statement.class}))//参数

这样其实就创建了一个Invocation对象

@Signature参数

代码语言:javascript
复制
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {

  //定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个
  Class<?> type();

  //在定义拦截类的基础之上,在定义拦截的方法,主要是看type里面取哪一个拦截的类,取该类当中的方法
  String method();

  //在定义拦截方法的基础之上在定义拦截的方法对应的参数,
  Class<?>[] args();
}

举个例子:

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

  • 定义Interceptor的实现类
代码语言:javascript
复制
public class MybatisPlugin implements Interceptor{}
  • 增加@Intercepts注解,标识需要拦截的类、方法、方法参数
代码语言:javascript
复制
/**
 * 插件签名,告诉当前的插件用来拦截哪个对象的哪种方法
 */
@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方法
代码语言:javascript
复制
/**
 * 拦截目标对象的目标方法
 * @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
代码语言:javascript
复制
@Configuration
public class MybatisPluginBean {

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

2.4 MetaObject

Mybatis提供了一个工具类:

代码语言:javascript
复制
org.apache.ibatis.reflection.MetaObject

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

代码语言:javascript
复制
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>

代码语言:javascript
复制
/**
 * 函数式接口
 */
public interface Declassified extends Function<String,String> {

}

3.2 定义脱敏策略

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

代码语言:javascript
复制
/**
 * 脱敏的策略
 */
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 替换字符串所有匹配给定的正则表达式的子字符串

代码语言:javascript
复制
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);
}

输出结果:
            转换的字符串:杨*茉

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

代码语言:javascript
复制
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 定义脱敏注解

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

3.4 实现Interceptor接口

代码语言:javascript
复制
/**
 * 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 注册插件

代码语言:javascript
复制
@Configuration
public class MybatisPluginConfig {

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

}

使用注解

代码语言:javascript
复制
@Sensitive(strategy = SensitiveStrategy.NAME)
private String name;

结果

代码语言:javascript
复制
{
  userId=1,
  name=管*员
}

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、mybatis plugin介绍
  • 三、mybatis脱敏插件
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档