目录
1、使用篇:Mybatis插件的用法? --实现 + 注册
2、原理篇:当我们不能修改mybatis代码的时候如何去实现增强和修改功能的死路和方法? --代理和装饰器模式
3、应用场景:mybatis插件能应用在那些场景呢?
【思考】:结合实例: PageInterceptor
1、mybatis插件编写
【重要思考点】动态代理实现增强与修改需要解决的问题?
①插件使用范围:有哪些对象允许被代理?有哪些方法可以被拦截? 四大天王可以被拦截:
Executor(update,query,flushStatements,commit,rollback,getTransaction,close,isClosed)
StatementHandler(getParameterObject, setParameters)
ParameterHandler( handleResultSets, handleOutputParamters )
ResultSetHandler ( prepare, parameterize, batch, update, query)
②怎么创建代理?
第一步,实现mybatis预留的Interceptor接口
@Intercepts( { //定义要拦截对象+方法:拦截对象Executor#query(MappedStatement, Object, RowBounds, ResultHandler)
@Signature(method = "query", type = Executor.class, args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class }),
@Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class MyInterceptor implements Interceptor {
//实现要增强的功能
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
System.out.println("Invocation.proceed()");
return result;
}
//包装目标类,生成代理对象(在创建代理对象的时候遍历实现Interceptor的类调用该方法进行包装)
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
//获取插件自定义参数
public void setProperties(Properties properties) {
String prop1 = properties.getProperty("prop1");
}
}
第二步,在mybatis-config.xml中注册插件
插件的加载:启动的时候被存放在Configuration按照数组的方式存放;
/**1、加载时:插件注册到配置对象*/
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).newInstance();
interceptorInstance.setProperties(properties);
// 核心:解析的插件设置进来
// 代理在创建的时候按照配置的顺序层层包装,先注册的包装在最里面;在代理对象被调用的时候则按照顺序逆序开始执行,顺序结束执行;
configuration.addInterceptor(interceptorInstance);
}
}
}
//被调用:将插件添加给 Configuration内部属性 interceptorChain
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
interceptorChain类解析:
public class InterceptorChain {
private final List interceptors = new ArrayList();
//给目标代理对象添加插件
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
//调用实现类的plugin方法;里面是进行包装的核心逻辑:Plugin.wrap(target, this);
target = interceptor.plugin(target);
}
return target;
}
//添加插件
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
//获取XX插件
public List getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
③创建的代理对象什么时候使用?启动或者创建会话或者执行SQL?
/**2、在会话调用中对四大对象进行插件包装*/
parameterHandler设置插件
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
resultSetHandler设置插件
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
statementHandler设置插件
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
Executor设置插件
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
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, autoCommit);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
④被代理后,调用的是什么方法?怎么调用到原被代理对象的方法?
第一步,在被代理的时候,做了调用: target = interceptor.plugin(target);
第二步,interceptor.plugin() 在实现类中最终都会调用: Plugin.wrap(target, this);
第三步,而在 Plugin.wrap(target, this)中,实现了动态代理创建new Plugin(target, interceptor, signatureMap))
第四步,因此,我们想知道动态代理在真实调用的时候如何执行那就需要看其invoke方法;
第五步,在invoke方法我们看到其最终调用的是Interceptor接口的interceptor方法;
第六步,最终调用 invocation.proceed() 实现反射调用被代理对象的方法;
第一步,在被代理的时候,做了调用: target = interceptor.plugin(target),interceptor.plugin() 在实现类中最终都会调用: Plugin.wrap(target, this); ; 前文代码有备注,不再重复;
第二步,而在 Plugin.wrap(target, this)中,实现了动态代理创建new Plugin(target, interceptor, signatureMap))
public static Object wrap(Object target, Interceptor interceptor) {
//获取插件类的 @Interceptors注解及注解属性@Signature集合(中的class类型和method和method的入参数args);结果保存key-目标代理class类型,value-Set
Map, Set> signatureMap = getSignatureMap(interceptor);
Class> type = target.getClass();
//根据调用的target对象实现的接口决定返回代理对象【-插件】数组
Class>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//Interceptor 是对被代理对象的的封装。
//Proxy.newProxyInstance() 是JDK方式创建动态代理。
//new Plugin(target, interceptor, signatureMap) 这是代理对象,必须实现InvocationHandler接口, 并实现invoke()方法,invoke方法是其代理执行逻辑;
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
第三步,因此,我们想知道动态代理在真实调用的时候如何执行那就需要看其invoke方法;在invoke方法我们看到其最终调用的是Interceptor接口的interceptor方法;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
//存在插件则调用插件的 intercept 方法并且创石化当前对象的类,方法,参数为 Invocation对象;
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
第四步,interceptor最终调用 invocation.proceed() 实现反射调用被代理对象的方法;
public class Invocation {
private Object target;
private Method method;
private Object[] args;
//....get/set/构造
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
2、插件的拦截链怎么形成?如何做到层层拦截? 责任链模式实现;InterceptorChain, 上面代码已经说明;
1,水平分表,注解实现
2,权限控制,根据用户不同对不同用户过滤不同的数据
3,关键数据脱敏