一、前言
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
来源:掘金
网友评论