Mybatis中的插件虽然名称叫插件,但实质上是通过动态代理实现的。和我们平时讲的插件概念不一样,但是本质上都是给外部提供接口进行扩展。
MyBatis 允许我们在映射语句执行过程中的某一点进行拦截调用。MyBatis允许我们使用插件来拦截的方法调用包括:
在Mybatis中自定义插件只需要以下几步:
自定义插件代码如下:
这里使用@Intercepts注解指定了拦截Executor对象的update方法和query方法,由于接口中存在重载方法,所以通过args指定方法的参数来确定是哪一个方法。
@Intercepts({
@Signature(type = Executor.class,method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class,method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class })})
public class MyFirstPlugin implements Interceptor {
/**
* 拦截目标对象的目标方法的执行
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("要拦截的方法: " + invocation.getMethod());
//得到当前对象
Object target = invocation.getTarget();
System.out.println("拦截的对象:" + target);
MetaObject metaObject = SystemMetaObject.forObject(target);
Object[] args = invocation.getArgs();
if (args[0] instanceof MappedStatement) {
MappedStatement statement = (MappedStatement) args[0];
System.out.println("=====================statement: " + statement);
System.out.println("=====================statement id: " + statement.getId());
System.out.println("=====================statement paraneterMap: " + statement.getParameterMap());
}
//执行原来的方法
Object prObject = invocation.proceed();
return prObject;
}
/**
* 包装目标对象的:包装:为目标对象创建一个代理对象
*
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
System.out.println("包装的对象: " + target.toString());
return Plugin.wrap(target, this);
}
/**
* 将插件注册时的property属性设置进来
*
* @param properties
*/
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
System.out.println("读取到配置文件的参数是:" + properties);
}
}
将插件注册到全局配置文件中
在plugin标签中可以使用property设置要传递的参数,这里设置的参数,可以在setProperties方法中获取到。
<plugins>
<plugin interceptor="com.ruoyi.web.test.MyFirstPlugin">
<property name="root" value="jjc"/>
<property name="sdsdfs" value="123456"/>
</plugin>
</plugins>
以上面的代码为例,在启动的时候,XMLConfigBuilder中的pluginElement方法会解全局配置文件中plugins节点。
configuration的addInterceptor方法最后会把拦截器加入到拦截器链中;而拦截的时候也会遍历拦截器链中的每一个拦截器,调用拦截器的plugin方法进行拦截。
resolveClass方法根据全限定类名解析出Class对象
Mybatis会尝试先从类型注册器中获取,如果没有则会使用类加载器根据全限定类型去加载(Resource.classForName中做了这个事情)。
到这里为止,Mybatis配置文件中的插件的解析就完成了。
现在我们来看看Mybatis是在什么时候拦截的。
下面截图中的方法是Configuration中的4个方法。这些方法在MyBatis的一个操作(新增,删除,修改,查询)中都会被执行到,执行的先后顺序是Executor、ParameterHandler、ResultSetHandler、StatementHandler;
其中ParameterHandler和ResultSetHandler的创建是在创建StatementHandler(3个可用的实现类CallableStatementHandler、PreparedStatementHandler、SimpleStatementHandler)的时候其构造函数调用的(这3个实现类的构造函数其实都调用了父类BaseStatementHandler的构造函数)。
这4个方法实例化了对应的对象之后,都会调用interceptorChain的pluginAll方法,InterceptorChain的pluginAll上面已经说过了,就是遍历所有的拦截器,然后调用各个拦截器的plugin方法。注意:拦截器的plugin方法的返回值会直接被赋值给原先的对象
接下来分析一下Plugin.wrap方法的执行。
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> 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;
}
getSignatureMap方法:首先会拿到拦截器这个类的@Interceptors注解,然后拿到这个注解的属性@Signature注解集合,然后遍历这个集合,遍历的时候拿出@Signature注解的type属性(Class类型),然后根据这个type得到带有method属性和args属性的Method。由于@Interceptors注解的@Signature属性是一个属性,所以最终会返回一个以type为key,value为Set的Map。
getAllInterfaces方法:根据目标实例target(这个target就是之前所说的MyBatis拦截器可以拦截的类,Executor,ParameterHandler,ResultSetHandler,StatementHandler)和它的父类们,返回signatureMap中含有target实现的接口数组。
所以Plugin类的作用就是根据 @Interceptors注解,得到这个注解的属性 @Signature数组,然后根据每个 @Signature注解的type,method,args属性使用反射找到对应的Method。最终根据调用的target对象实现的接口决定是否返回一个代理对象替代原先的target对象。
如果返回了代理类,那么代理类最终会执行Interceptor接口的interceptor方法。