MyBatis插件
MyBatis四大对象,每一个对象在创建的时候,都会经过以下语句包装
获取到所有的interceptors(拦截器),调用interceptor.plugin(target);返回target包装后的对象
插件机制:可以使用插件为目标对象创建一个代理对象,代理对象可以拦截到四大对象的每一个执行
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
插件的编写
-
创建Interceptor接口的实现类
public class MyFirstPlugin implements Interceptor { // 拦截目标对象方法的目标方法执行 @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("MyFirstPlugin===>intercept:" + invocation.getMethod()); //执行目标方法 Object proceed = invocation.proceed(); return proceed; } //包装目标对象,为目标对象生成一个代理对象 @Override public Object plugin(Object target) { System.out.println("MyFirstPlugin===>plugin:" + target); //调用Plugin.wrap来使用当前的interceptor包装目标对象 Object wrap = Plugin.wrap(target, this); //返回为当前target创建的动态代理 return wrap; } //将插件注册时 的property属性设置进来 @Override public void setProperties(Properties properties) { System.out.println("MyFirstPlugin===>setProperties:插件的配置信息:" + properties); } }
-
使用@Intercepts注解完成插件签名
@Intercepts的参数为@Signature的集合,Signature的第一个参数是要拦截的对象,第二个参数是拦截对象的方法,第三个参数为方法的形参
@Intercepts( { @Signature(type = StatementHandler.class,method = "parameterize",args = java.sql.Statement.class) } ) public class MyFirstPlugin implements Interceptor { }
-
将写好的插件注册到全局配置文件中
<!--配置mybatis插件--> <plugins> <plugin interceptor="com.yellowstar.plugins.MyFirstPlugin"> <!--可以注册属性--> <property name="username" value="root"/> <property name="password" value="123456"/> </plugin> </plugins>
单个插件的运行
先看一下配置了插件之后,执行查询语句的输出结果
MyFirstPlugin===>setProperties:插件的配置信息:{password=123456, username=root}
MyFirstPlugin===>plugin:org.apache.ibatis.executor.CachingExecutor@2781e022
MyFirstPlugin===>plugin:org.apache.ibatis.scripting.defaults.DefaultParameterHandler@42d8062c
MyFirstPlugin===>plugin:org.apache.ibatis.executor.resultset.DefaultResultSetHandler@12cdcf4
MyFirstPlugin===>plugin:org.apache.ibatis.executor.statement.RoutingStatementHandler@5f3a4b84
MyFirstPlugin===>intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
org.apache.ibatis.executor.statement.RoutingStatementHandler@5f3a4b84
断点调试,看以下源码,signatureMap是我们注册的目标对象(StatementHandler),type是当前运行到的对象(假设当前为Executor),那么判断条件不成立,会直接返回target,如果当前运行的对象跟目标对象相同,则为目标对象创建一个代理对象
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
创建完代理对象后,接着往下走会执行我们的目标方法,这里我们可以看出plugin方法是用来包装目标对象,intercept方法是执行目标对象的方法
多个插件的执行
假设场景:两个插件同时执行,为了确保目标对象和目标方法一致,复制一个插件,并在xml文件中进行配置
<plugins>
<plugin interceptor="com.yellowstar.plugins.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</plugin>
<plugin interceptor="com.yellowstar.plugins.MySecondPlugin"></plugin>
</plugins>
先来看一下运行结果,我们发现执行顺序是先由配置顺序决定的,当符合拦截条件时,会对目标对象进行包装,这里需要注意的是第二个插件对对象进行包装时,是对一个插件包装后的对象进行包装,所以在执行intercept方法时,会倒序执行
MyFirstPlugin===>setProperties:插件的配置信息:{password=123456, username=root}
MyFirstPlugin===>plugin:org.apache.ibatis.executor.CachingExecutor@4493d195
MySecondPlugin===>plugin:org.apache.ibatis.executor.CachingExecutor@4493d195
MyFirstPlugin===>plugin:org.apache.ibatis.scripting.defaults.DefaultParameterHandler@45820e51
MySecondPlugin===>plugin:org.apache.ibatis.scripting.defaults.DefaultParameterHandler@45820e51
MyFirstPlugin===>plugin:org.apache.ibatis.executor.resultset.DefaultResultSetHandler@25af5db5
MySecondPlugin===>plugin:org.apache.ibatis.executor.resultset.DefaultResultSetHandler@25af5db5
MyFirstPlugin===>plugin:org.apache.ibatis.executor.statement.RoutingStatementHandler@5bcea91b
MySecondPlugin===>plugin:org.apache.ibatis.executor.statement.RoutingStatementHandler@5bcea91b
MySecondPlugin===>intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
MyFirstPlugin===>intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
简单的插件制作
场景:根据员工id查询员工数据,无论传过来的id是多少,我们都通过插件将他设置为查询id为5的员工
我们知道,执行目标方法的方法是intercept(),设置sql语句的参数是通过ParameterHandler设置的,其中parameterObject用来设置参数值,所以需要渠道parameterObject对象进行修改
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin===>intercept:" + invocation.getMethod());
//原始的sql查询id为1的员工,现将他修改成查询id为5的员工
//获取当前拦截的对象
Object target = invocation.getTarget();
System.out.println(target);
//拿到:StatementHandler==>ParameterHandler===>parameterObject
//拿到target的元数据,相当于拿到StatementHandler
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("原始参数为:" + value);
metaObject.setValue("parameterHandler.parameterObject",5);
//执行目标方法
Object proceed = invocation.proceed();
return proceed;
}