在四大对象创建的时候
1、每个创建出来的对象不是直接返回的,而是经过interceptorChain.pluginAll(四大对象)
2、获取到所有的Interceptor(拦截器,插件需要实现的接口),调用interceptor.plugin(target),返回target包装后的对象
3、插件机制,使用插件为目标对象来创建一个代理对象,与AOP原理相同
即插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行方法
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);
}
}
插件编写:
1、编写Interceptor的实现类
2、使用@Intercepts注解完成插件签名
3、将写好的插件注册到全局配置文件中
/**
* 通过注解设置是哪些对象可以被该插件拦截
* type指定拦截的目标对象
* method指定拦截的方法
* args指定拦截方法需要传入的参数类型,确定是哪个具体方法,因为方法可能存在重载
* @author Administrator
*
*/
@Intercepts(
value = {
@Signature(
args = { java.sql.Statement.class },
method = "parameterize",
type =org.apache.ibatis.executor.statement.StatementHandler.class )
}
)
public class MyInterceptor implements Interceptor {
/**
* intercept:拦截
* 拦截目标对象方法的执行,可以在这里编写目标方法执行前后需要做的操作
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// TODO Auto-generated method stub
System.out.println("*********目标方法执行前*****");
Object obj = invocation.proceed();//执行目标方法
System.out.println("*********目标方法执行后*****");
return obj;
}
/**
* 创建代理对象,Plugin.wrap方法会将四大对象一一与@Intercepts注解中的属性相匹配,如果匹配上就生成代理对象,匹配不上直接返回目标对象
*/
@Override
public Object plugin(Object target) {
// TODO Auto-generated method stub
System.out.println("*********plugin*****");
Object obj = Plugin.wrap(target, this);
return obj;
}
/**
* 获取插件中的property属性
*/
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
System.out.println(properties);
}
}
全局配置文件中注册该插件
<plugins>
<plugin interceptor="com.learncmzy.interceptor.MyInterceptor">
<property name="username" value="yangguiyong"/>
<property name="password" value="123456"/>
</plugin>
</plugins>
如果是编写了多个插件,并且多个插件拦截的是同一个目标对象,那么多个插件会产生多层代理。创建动态代理的时候,是按照插件在全局配置文件配置顺序创建层层代理对象,执行目标方法的时候,是按照逆向顺序执行。
使用插件实现参数查询时参数的修改,比如查询id 为1的学生,结果查询id为6的学生。查询时使用StatementHandler,但是是生成的RoutingStatementHandler,里面的PreparedStatementHandler执行parameterize方法,方法中使用ParameterHandler对参数进行赋值,此时DefaultParamenterHandler中包含了传入的参数parameterObject,修改parameterObject即可。
通过SystemMetaObject.forObject方式可以获取目标对象中的元数据。metaObject.getValue,metaObject.setValue获取和修改目标对象的元数据
public Object intercept(Invocation invocation) throws Throwable {
// TODO Auto-generated method stub
System.out.println("*********目标方法执行前*****");
//获取目标对象
Object target = invocation.getTarget();
//通过SystemMetaObject.forObject方式可以获取目标对象中的元数据,从而可以轻易的对目标对象中的元数据进行修改
MetaObject metaObject = SystemMetaObject.forObject(target);
//获取目标对象中的元数据
Object param = metaObject.getValue("parameterHandler.parameterObject");
System.out.println(param);
//将目标对象中的参数修改,从而实现偷梁换柱的现象
metaObject.setValue("parameterHandler.parameterObject", 6);
Object obj = invocation.proceed();
System.out.println("*********目标方法执行后*****");
return obj;
}
注意:插件的编写需要慎重,必须要弄清楚拦截的对象的具体作用,拦截对象里面的具体元数据,以及方法执行的流程,否则破坏Mybatis底层的逻辑
PageHelper分页插件:
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md