拦截器可在mybatis进行sql底层处理的时候执行额外的逻辑,最常见的就是分页逻辑、对结果集进行处理过滤敏感信息等。
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } 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; } 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); } 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; }
从上面的代码可以看到mybatis支持的拦截类型只有四种(按拦截顺序)
1.Executor 执行器接口
2.StatementHandler sql构建处理器
3.ParameterHandler 参数处理器
4.ResultSetHandler 结果集处理器
public class InterceptorChain { private final Listinterceptors = 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); } } #Interceptor public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; default Object plugin(Object target) { return Plugin.wrap(target, this); } default void setProperties(Properties properties) { // NOP } }
mybatis拦截器本质上使用了jdk动态代理,interceptorChain拦截器链中存储了用户定义的拦截器,会遍历进行对目标对象代理包装。
用户自定义拦截器类需要实现Interceptor接口,以及实现intercept方法,plugin和setProperties方法可重写,plugin方法一般不会改动,该方法调用了Plugin的静态方法wrap实现了对目标对象的代理
public class Plugin implements InvocationHandler { // 拦截目标对象 private final Object target; // 拦截器对象-执行逻辑 private final Interceptor interceptor; // 拦截接口和拦截方法的映射 private final Map, Set > signatureMap; private Plugin(Object target, Interceptor interceptor, Map , Set > signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } // 获取jdk代理对象 public static Object wrap(Object target, Interceptor interceptor) { // 存储拦截接口和拦截方法的映射 Map , 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; } @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)) { // Invocation存储了目标对象、拦截方法以及方法参数 return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } private static Map , Set > getSignatureMap(Interceptor interceptor) { // 获取Intercepts注解值不能为空 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // issue #251 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } Signature[] sigs = interceptsAnnotation.value(); // key 拦截的类型 Map , Set > signatureMap = new HashMap<>(); for (Signature sig : sigs) { Set methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>()); try { // 获取拦截的方法 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; } private static Class>[] getAllInterfaces(Class> type, Map , Set > signatureMap) { Set > interfaces = new HashSet<>(); while (type != null) { for (Class> c : type.getInterfaces()) { if (signatureMap.containsKey(c)) { interfaces.add(c); } } type = type.getSuperclass(); } return interfaces.toArray(new Class>[interfaces.size()]); } }
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Intercepts { /** * Returns method signatures to intercept. * * @return method signatures */ Signature[] value(); } @Documented @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Signature { /** * Returns the java type. * * @return the java type */ Class> type(); /** * Returns the method name. * * @return the method name */ String method(); /** * Returns java types for method argument. * @return java types for method argument */ Class>[] args(); }
可以看到,当被拦截的方法被执行时主要调用自定义拦截器的intercept方法,把拦截对象、方法以及方法参数封装成Invocation对象传递过去。
在getSignatureMap方法中可以看到,自定义的拦截器类上需要添加Intercepts注解并且Signature需要有值,Signature注解中的type为需要拦截对象的接口(Executor.class/StatementHandler/ParameterHandler/ResultSetHandler),method为需要拦截的方法的方法名,args为拦截方法的方法参数类型。
接下来举一个拦截器实现对结果集下划线转驼峰的例子来简要说明
/** * @author dxu2 * @date 2022/7/14 * map结果转驼峰 */ @Intercepts(value = {@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})}) public class MyInterceptor implements Interceptor { @SuppressWarnings("unchecked") @Override public Object intercept(Invocation invocation) throws Throwable { // 调用目标方法 List
这个例子拦截的是ResultSetHandler的handleResultSets方法,这个方法是用来对结果集处理的,看intercept方法首先调用了目标对象的方法接着强转为List
返回的是List类型,然后遍历列表,若元素是map类型的再进行处理把key值转化为驼峰形式重新put到map中。
最后不要忘了把自定义的拦截器添加到配置中,这边是使用xml配置的,添加完后接着运行测试代码,可以看到列user_id已经转换成驼峰形式了。
#mapper接口 List