Mybatis拦截器源码分析
2021/7/9 22:06:32
本文主要是介绍Mybatis拦截器源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目录
前言
一、interceptor的加载过程
二、代理对象创建
1.Configuration类
2.pluginAll
3.wrap
三、代理对象调用
总结
前言
Mybatis的拦截器可以实现记录日志,sql拦截等功能,作为一个常用的插件,我们有必要了解其运行的原理。本文基于原始的mybatis,没有结合spring,主要从源码角度分析interceptor的运行原理,主要包括了拦截器的加载,代理对象的创建和调用过程。
一、interceptor的加载过程
在XMLConfigBuilder解析配置文件的过程中,会解析其中的plugin节点,并将拦截器加载到拦截器链中。
//解析配置文件中的拦截器 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); configuration.addInterceptor(interceptorInstance); } } } public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); }
二、代理对象创建
1.Configuration类
在Configuration类中创建ParameterHandler,resultSetHandler,StatementHandler和Executor的过程中,会调用interceptorChain.pluginAll方法。
//创建参数处理器 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); } // 拦截 处理plugins executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
2.pluginAll
在pluginAll方法中,会依次调用所有拦截器的plugin方法,在项目中,plugin方法一般会直接调用Plugin类的wrap方法,
//调用所有拦截器的plugin方法 public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
public Object plugin(Object target) { return Plugin.wrap(target, this); }
3.wrap
在wrap方法中,从拦截器注解中获取需要拦截的类和方法信息,然后判断当前对象有没有实现需要被拦截的接口,如果实现了就创建当前对象的代理对象。
public static Object wrap(Object target, Interceptor interceptor) { //从拦截器注解中获取需要拦截的类和方法信息 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); //mybatis中可能被拦截的对象的类型 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; }
在getSignatureMap方法中,通过遍历注解中的Signature,将类和方法信息添加到SignatureMap中。
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { //获取注解 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(); Map<Class<?>, Set<Method>> signatureMap = new HashMap<>(); //遍历Signature for (Signature sig : sigs) { //将注解中的类和方法信息添加到signatureMap中 Set<Method> 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; }
SignatureMap的key值是需要被拦截的接口,获取当前创建的对象实现的接口,如果接口符合拦截条件,将接口添加到数组中返回。
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { Set<Class<?>> interfaces = new HashSet<>(); while (type != null) { //获取type的接口 for (Class<?> c : type.getInterfaces()) { //判断当前接口是否符合拦截条件 if (signatureMap.containsKey(c)) { interfaces.add(c); } } type = type.getSuperclass(); } return interfaces.toArray(new Class<?>[interfaces.size()]); }
三、代理对象调用
创建代理对象的InvocationHandler本身是一个Plugin对象哎。所以代理对象调用的时候会调用Plugin的invoke方法。在该方法中,会首先从signatureMap中取出当前对象需要被拦截的方法集合,然后判断当前方法是否在其中,如果在其中就进行拦截,执行拦截器的方法,否则直接执行原始方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //获取需要拦截的方法 Set<Method> methods = signatureMap.get(method.getDeclaringClass()); //是Interceptor实现类注解的方法才会拦截处理 if (methods != null && methods.contains(method)) { //调用拦截器的intercept方法,传入参数是由target,method和args构成的invocation对象 return interceptor.intercept(new Invocation(target, method, args)); } //直接调用对象的方法 return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }
在拦截器的intercept方法中会传入一个Invocation对象,在该对象中存储了当前对象,方法和参数信息,拦截方法可以通过调用proceed方法,继续执行原始对象的方法。算是一个封装,直接传被代理对象,方法和参数也可以。
//执行对象的原始方法 public Object proceed() throws InvocationTargetException, IllegalAccessException { return method.invoke(target, args); }
总结
本文主要分析了Interceptor对象的加载,代理对象的创建和执行过程,相比于aop简单很多,多个拦截器对同一个接口进行拦截会产生多层代理。
这篇关于Mybatis拦截器源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-27本地多文件上传的简单教程
- 2024-11-27低代码开发:初学者的简单教程
- 2024-11-27如何轻松掌握拖动排序功能
- 2024-11-27JWT入门教程:从零开始理解与实现
- 2024-11-27安能物流 All in TiDB 背后的故事与成果
- 2024-11-27低代码开发入门教程:轻松上手指南
- 2024-11-27如何轻松入门低代码应用开发
- 2024-11-27ESLint开发入门教程:从零开始使用ESLint
- 2024-11-27Npm 发布和配置入门指南
- 2024-11-27低代码应用课程:新手入门指南