Mybatis源码分析(三)Mybatis插件的使用和源码分析
先来看下使用:
打印sql语句和执行的时间
1:实现 Interceptor 接口
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class}) }) public class SqlInterceptor implements Interceptor { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = statementHandler.getBoundSql(); String sql = boundSql.getSql(); logger.info("mybatis intercept sql:{}", sql); long t1=System.currentTimeMillis(); try{
return invocation.proceed(); }catch (Exception e){ logger.error("执行失败"); return null; }finally { long t2=System.currentTimeMillis(); logger.info("执行时间:"+(t2-t1)); } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { String dialect = properties.getProperty("testProperty"); logger.info("mybatis在配置插件的时候传入的属性:{}", dialect); } }
2:在mybatis的全局配置文件中加上这个插件
<plugins> <plugin interceptor="org.apache.ibatis.demo.SqlInterceptor"> <property name="testProperty" value="我是测试的"/> </plugin> </plugins>
看效果:
原理:
先总结一下:
0: 可以拦截的有四个接口:Executor,ParameterHandler ,ResultSetHandler ,StatementHandler
1:实现 Interceptor 接口,重写 intercept方法,在注解上面定义拦截的是哪个接口,哪个方法,参数是什么。可以根据拦截的是哪个接口,在方法中getTarget得到哪个实例,然后进行处理。
2:在解析全局配置文件成Configuration对象的时候会加载plugin属性,把自定义插件添加到 InterceptorChain# interceptors集合中。
3:在生成上面四个接口实例的时候,会加载 InterceptorChain# interceptors集合遍历找到符合签名的插件,如果找到了是拦截自己的插件就生成一个代理对象返回出去。
我在定义了, Interceptor接口实现类,在全局配置文件中增加<plugin></plugin>节点,这个节点在mybatis解析全局配置文件成Configuration对象的时候,把接口实现类都添加到了 InterceptorChain 中,这个在上节源码中可以看到。
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).getDeclaredConstructor().newInstance(); // 设置属性 interceptorInstance.setProperties(properties); // 添加到InterceptorChain 中 configuration.addInterceptor(interceptorInstance); } } }
public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); }
存到 InterceptorChain# interceptors集合中
mybatis提供了可以在四个接口的实现类的方法执行前进行拦截。
Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
补充: StatementHandler: 在编译sql语句的时候会用到他们的实现类。 ParameterHandler处理sql中参数的填充。
具体拦截那个接口的那个方法,和上面使用的 @Intercepts 注解里面的方法签名有关,type指定拦截那个接口,method方法名,args是参数。
具体的效果,就是如果拦截了某个接口,在Configuration创建某个接口实现类的时候,会调用InterceptorChain,如果符合签名的条件,就返回对应实现类的一个动态代理。
public class Configuration { protected final InterceptorChain interceptorChain = new InterceptorChain(); /**对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) { return newExecutor(transaction, defaultExecutorType); } /**对Executor 进行拦截**/ public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 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); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; } public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); } }
创建的顺序 :1: newExecutor 2:newParameterHandler 3:newResultSetHandler 4:newStatementHandler
以例子中的拦截的 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); // InterceptorChain#pluginAll 传入的参数是原始的 RoutingStatementHandler 对象 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
InterceptorChain#pluginAll,遍历处理添加进去的拦截插件 ,目前只有一个自定义的插件。
然后走到 自定义的逻辑 SqlInterceptor#plugin中:
Plugin#wrap中
public static Object wrap(Object target, Interceptor interceptor) { // 得到注解中的签名信息 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); // 看签名信息和当前target 类型是否匹配 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); // 如果匹配的上,返回动态代理,代理逻辑用 new Plugin(target, interceptor, signatureMap) 来封装 if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; }
这个逻辑走完,回到newStatementHandler方法中,得到的 statementHandler 已经是个代理对象了。代理逻辑会执行Plugin中的invoke方法,注意代理逻辑中是new Plugin(target,interceptor,singmap)都传递进去了
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 拦截的签名方法匹配 走自定义的拦截逻辑 又传了一个新对象 new Invocation(target, method, args)
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}