Mybatis支持那些类型插件
- Executor (update, query, flushStatements, commit, rollback,
getTransaction, close, isClosed) - ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
如何使用插件
- 自定义插件实现org.apache.ibatis.plugin.Interceptor接口
- 通过 @Intercepts注解表明插件增强的类型,方法
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class SQLStatsInterceptor 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(); System.out.println("mybatis intercept sql:" + sql); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { String dialect = properties.getProperty("dialect"); System.out.println("mybatis intercept dialect:"+dialect); } }
- config.xml中配置插件
<plugins> <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin"> <property name="pluginProperty" value="100"/> </plugin> <plugin interceptor="org.apache.ibatis.builder.SQLStatsInterceptor"> <property name="dialect" value="test"/> </plugin> </plugins>
为什么只支持上述的四种类型的插件呢?
只是因为在Configuration中定义了如下几个方法:
/** *plugin在此对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; } /** *plugin在此对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; } /** *plugin在此对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; } public Executor newExecutor(Transaction transaction) { return newExecutor(transaction, defaultExecutorType); } /** *plugin在此对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; }
增强实现的源码
在解析config.xml文件的时候会将<plugins>中配置的插件会添加到Configuration中到interceptorChain中
private void pluginElement(XNode parent) throws Exception { //如果存在plugings节点 if (parent != null) { //遍历所有子节点 for (XNode child : parent.getChildren()) { //获取interceptor属性 String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); //创建Interceptor实例 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); //设置属性 interceptorInstance.setProperties(properties); //向拦截器链注入拦截器 configuration.addInterceptor(interceptorInstance); } } } public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); }
SimpleExecutor中执行doUpdate方法是会通过configuration去创建一个StatementHandler
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { log.trace(" SimpleExecutor ms.getConfiguration()"); Configuration configuration = ms.getConfiguration(); log.trace("SimpleExecutor configuration.newStatementHandler"); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt);
上文已经列出来了,在newStatementHandler中会对StatementHandler进行增强
/** *plugin在此对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; }
/** * 拦截器链 * @author Clinton Begin */ public class InterceptorChain { //拦截器列表 private final List 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 getInterceptors() { return Collections.unmodifiableList(interceptors); } } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); }
通过上文中发现似乎所有拦截器都进行了增强处理,事实上不是这样都,具体使用哪些拦截器对接口进行增强是如下方法中处理的。同时可以发现 Plugin实现了InvocationHandler接口,也就是调用处理也是在该类中完成的,下面主要就是Plugin中的实现源码
//对目标对象使用过interceptor进行增强 public static Object wrap(Object target, Interceptor interceptor) { //获取拦截类,方法映射表 Map<class<?>, Set> 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; } private static Map<class<?>, Set> getSignatureMap(Interceptor interceptor) { //获取类上@Intercepts的注解 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // 如果插件上没有@Intercepts注解,抛出异常 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } //@Intercepts注解中所有签名 Signature[] sigs = interceptsAnnotation.value(); Map<class<?>, Set> signatureMap = new HashMap<class<?>, Set>(); //遍历所有签名 for (Signature sig : sigs) { //根据类型从签名映射表中获取方法集合 Set methods = signatureMap.get(sig.type()); if (methods == null) { //如果方法集合为null,则创建一个空集合并放入到映射表中 methods = new HashSet(); signatureMap.put(sig.type(), methods); } try { //根据方法名称,参数类型列表从指定的class中获取方法 Method method = sig.type().getMethod(sig.method(), sig.args()); //如果找到指定的方法则添加到集合中 methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; }
这次可以发现通过JDK的动态代理进行了增强,在进行方法调用对时候会执行一下方法,在该方法中判断当前方法是否需要增强,如果需要就会调用interceptor 进行增强处理
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set methods = signatureMap.get(method.getDeclaringClass()); //如果当前方法需要被增强 if (methods != null && methods.contains(method)) { //调用目标对象对拦截器 return interceptor.intercept(new Invocation(target, method, args)); } //否则直接调用方法 return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }
这次就简单介绍了Mybatis 插件功能的使用和实现方式