Mybatis
plugin原理和实现思路
摘要:本文主要讲解3.6.4版本mybatis下plugin的原理和实现思路;
包目录说明
该包整体类比较少,但实现插件的思路指的学习和模仿,包下主要有Interceptor(拦截器顶级接口)、InterceptorChain(责任链)、Intercepts和Signature(两个注解配合,标定那些类和方法需要拦截)、Invocation(反射机制类)、Plugin(插件对象本身)、PluginException(异常类);
包的层级图
在开始说如何实现插件之前,需要了解一下,mybatis的执行概要图和mybatis的核心类:
执行概要图
核心类:
从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
Configuration初始化基础配置,比如MyBatis的别名等,一些重要的类型对象,如,插件,映射器,ObjectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中
SqlSessionFactory
SqlSession工厂
SqlSession作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
Executor
MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement MappedStatement维护了一条
SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql表示动态生成的SQL语句以及相应的参数信息
类的说明:
Interceptor
拦截器接口,所有的拦截器都需要实现;代码如下
public interface Interceptor {
//拦截的方法(里面需要执行invocation的proceed方法,用于执行代码对象的自身方法)
Object intercept(Invocation invocation) throws Throwable;
//获取代理对象
Object plugin(Object target);
//暂时没有看到一些用处,可以自己注入一下属性
void setProperties(Properties properties);
}
Invocation
执行类(反射类),代码如下
public 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;
}
//每次拦截之后,需要调用这个方法,用于执行本身的方法
public Object proceed() throws InvocationTargetException,IllegalAccessException {
return method.invoke(target, args);
}
}
Intercepts和Signature
两个注解配合使用,Intercepts标记类,Signature标记拦击的类、方法、和参数;
public @interface Signature {
Class type();
String method();
Class[] args();
}
Plugin
publicclass Plugin implements InvocationHandler {
//代理对象
private final Object target;
// 拦截对象
private final Interceptor interceptor;
// 存放的是类和方法(通过注解获取到的)
private final Map,Set> signatureMap;
private Plugin(Object target, Interceptorinterceptor, Map, Set> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
//封装插件对象
public static Object wrap(Object target,Interceptor interceptor) {
//可以看一下源码了,就是简单的反射机制代码了
Map,Set> signatureMap = getSignatureMap(interceptor);
Class type = target.getClass();
Class[] interfaces =getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor,signatureMap));
}
return target;
}
//执行方法,如果方法存在map中,就执行拦截(interceptor的intercept方法)
@Override
public Object invoke(Object proxy, Methodmethod, Object[] args) throws Throwable {
try {
Set methods =signatureMap.get(method.getDeclaringClass());
if (methods != null &&methods.contains(method)) {
return interceptor.intercept(newInvocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
InterceptorChain
责任链,初始化的时候,赋值了所有的拦截器,代码也很简单,如下
public class InterceptorChain {
private final List interceptors = newArrayList();
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 getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
值得注意的是,什么时候interceptors赋的值?这里就关联到了Configuration这个类,目前有两种初始化的方式:
[if !supportLists]1、 [endif]通过SqlSessionFactoryBean去构建Configuration添加拦截器并构建获取SqlSessionFactory;
[if !supportLists]2、 [endif]通过原始的XMLConfigBuilder 构建configuration添加拦截器;
源码就不黏贴了;
项目中思路的应用
Plugin的作用就是将项目中的执行对象某些方法暴露在外面,允许第三方在方法执行之前,执行自己的逻辑;相对来说将自己的逻辑暴露在外面还是有很大的风险,目前自己想到的有参数效验,自己原来封装了一个jar,里面有一些类似@MAX @MIN等等注解,这个时候,可以向外提供plugin,到达不修改jar的前提,又向外提供添加注解的能力;