深入浅出Mybatis的插件原理
插件的作用:
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
mybatis 的plugin只能对上述类中的方法进行拦截
插件的应用场景
- 记录执行的sql语句.以及参数
- 改造sql语句,如分页.根据指定的时间参数查询不同的数据表
- 对sql语句进行检测,如sql中不允许使用like ‘%%’;
- 分库分表查询
如何使用插件
配置xml文件
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
复制代码
创建拦截器
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
// 拦截到具体方法执行
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
// 自定义属性
public void setProperties(Properties properties) {
this.properties = properties;
}
}
复制代码
插件的原理
1插件解析过程
全局Configuration对象创建来自于SqlSessionFactory对象的创建;跟踪源码入口SqlSessionFactoryBuilder.build()
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
复制代码
- 获取全局配置文件定义的插件,实例化.设置自定义属性
- 加入到Configuration的拦截器链中
拦截器链
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
// 生成代理对象的流程
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
// 添加拦截器
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
// 返回不可修改的责任链 (工作中可以借鉴)
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
复制代码
2 如何将插件植入到具体对象中
- 装饰器
- 代理
mybatis的插件基于jdk的动态代理来实现;
Executor 执行器
- 执行器的创建
Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 默认开启
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 插件直入过程
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
复制代码
-
执行器类型存在三种类型SIMPLE, REUSE, BATCH
-
默认执行器类型为Simple
我们可以看到CachingExecutor创建后经过PluginAll方法返回新的执行器对象
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } 复制代码
进入Interceptor.plugin方法中
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; // 默认方法 default Object plugin(Object target) { return Plugin.wrap(target, this); } default void setProperties(Properties properties) { // NOP } } 复制代码
然后进入到MyBatis给我们提供的Plugin工具类的实现 wrap方法中。
public 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; } public static Object wrap(Object target, Interceptor interceptor) { // 获取执行器上的配置的Intercepts注解信息,以及其拦截的方法 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); // 获取实现的接口 需要是signatureMap的key Class<?>[] interfaces = getAllInterfaces(type, signatureMap); //如果实现了接口 则创建代理对象,否则返回原生对象 if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } // 解析Intercepts注解 private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // issue #251 不存在注解抛出异常 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } Signature[] sigs = interceptsAnnotation.value(); // 保存类 与其方法映射 Map<Class<?>, Set<Method>> signatureMap = new HashMap<>(); for (Signature sig : sigs) { Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>()); try { // 获取Executor .StatementHandler.ParamterHandler.ResultSetHandler中的所有public方法 Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; } // 获取所有接口 private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { Set<Class<?>> interfaces = new HashSet<>(); while (type != null) { for (Class<?> c : type.getInterfaces()) { // if (signatureMap.containsKey(c)) { interfaces.add(c); } } type = type.getSuperclass(); } return interfaces.toArray(new Class<?>[0]); } } 复制代码
- 解析拦截器上signature注解,并检查是否符合规范
- 创建代理对象$Proxy
插件顺序为Plugin1->Plugin2->Plugin3->Plugin4,但是执行顺序为Plugin4->Plugin3->Plugin2->Plugin1->Executor
插件的执行过程
- 执行器执行具体方法 如Executor.query();基于创建的代理对象进入Plugin的invoker方法中
Plugin
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 获取拦截器interceptor类上标注的Signature注解中需要拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 若存在则执行intercept 方法
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
复制代码
以Executor的query方法为例
class | 作用 | |
---|---|---|
Plugin | 实现了InvocationHandler,执行代理 | |
Interceptor | 拦截器,执行指定方法拦截 | |
Invocation | 对被代理类进行包装,可以调用proceed()调用到被拦截的方法 |
最后;插件的原理虽然简单,但真正要用好它,需要对mybatis的各个类之间的关系要有足够的了解,否则你的插件不仅不会生效,还会影响到mybatis的执行流程;
自定义拦截器实现数据脱敏
因为我们需要对返回的数据进行脱敏,所以我们拦截的对象为ResultSetHandler;
辅助注解
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitization {
/**
* 从0起始
*
* @return
*/
int startOffset();
/**
* 结束
*
* @return
*/
int endOffset();
/**
* 占位符
*
* @return
*/
char replaceChar() default '*';
}
复制代码
编写拦截器
@Intercepts({@Signature(
type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class})})
public class DesensitizationInterceptor implements Interceptor {
public static final Map<Class, Map<Field, Desensitization>> fieldMap = new ConcurrentHashMap<>(64);
@Override
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need List
Object returnObject = invocation.proceed();
List c = (List) returnObject;
for (Object o : c) {
desensitization(o);
}
// implement post processing if need
return returnObject;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
public void desensitization(Object returnObject) {
if (returnObject != null && returnObject instanceof Account) {
Account account = (Account) returnObject;
// 转换参数
Class<? extends Account> aClass = account.getClass();
Map<Field, Desensitization> fieldDesensitizationMap = fieldMap.get(aClass);
if (fieldDesensitizationMap == null) {
Map<Field, Desensitization> fieldDesensitizationNew = new ConcurrentHashMap<>();
fieldDesensitizationMap = fieldMap.computeIfAbsent(aClass, e -> {
return fieldDesensitizationNew;
});
}
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field : declaredFields) {
// 仅对字符串进行处理,其他属性不做处理
if (field.getType() != String.class) {
continue;
}
Desensitization annotation = null;
if (!fieldDesensitizationMap.containsKey(field)) {
annotation = field.getAnnotation(Desensitization.class);
fieldDesensitizationMap.putIfAbsent(field, annotation);
} else {
annotation = fieldDesensitizationMap.get(field);
}
if (annotation == null) {
continue;
}
if (!field.isAccessible()) {
field.setAccessible(true);
}
try {
String s = (String) field.get(returnObject);
if (StringUtils.isEmpty(s)) {
continue;
}
// 替换参数
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (i >= annotation.startOffset() && i <= annotation.endOffset()) {
chars[i] = annotation.replaceChar();
}
}
// 重置属性值
field.set(returnObject,new String(chars));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
复制代码
以上代码仅供参考;
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END