linux cpu占用率如何看
263
2022-09-17
原来MyBatis插件/拦截器(Plugin/Interceptyor)的实现原理这么简单
一、前言
前文(从JDK动态代理一步步推导到MyBatis Plugin插件实现原理)中,我们从JDK动态代理推导出了MyBatis Plugin的基本实现原理;本文我们就详细分析一下Mybatis Plugin的实现原理;
MyBatis Plugin是MyBatis高可扩展性的体现之一,其用来改变或扩展MyBatis的现有功能;例如:分析项目中存在那些慢SQL(统计SQL执行时间)、分页工具PageHelper也是基于该机制实现的分页;
MyBatis对外提供的扩展点有四个,分别对应MyBatis的四大接口。
二、MyBatis四大接口
我们都知道MyBatis的底层封装了JDBC,并依赖其实现数据库操作;所以MyBatis他的整体工作流程和JDBC类似,大致可分为:预编译SQL语句、SQL参数处理、执行SQL语句、封装结果集;对应这四步,MyBatis提供了四大接口,分别为:Executor、ParameterHandler、StatementHandler、ResultSetHandler。
针对四大接口的几乎所有核心方法都可以被MyBatis Plugin拦截;具体如下:
三、源码分析
我们这里关注的是最原生的MyBatis(不考虑SpringBoot),以MyBatis官网的一个Plugin例子为例(mybatis-config.xml -->
拦截类:
// ExamplePlugin.java@Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})})public class ExamplePlugin implements Interceptor { private Properties properties = new Properties(); public Object intercept(Invocation invocation) throws Throwable { // implement pre processing if need Object returnObject = invocation.proceed(); // implement post processing if need return returnObject; } public void setProperties(Properties properties) { this.properties = properties; }}
上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行底层映射语句的内部对象。
1、拦截器何时加载?
我们自定义拦截器之后,是需要在全局配置文件(mybatis-config.xml)中引入的; 因此我们首先从配置文件的解析来看;
phase1 > 在实例化SqlSessionFactory时,我们通过将全局配置文件mybatis-config.xml序列化成二进制流,进而构建SqlSessionFactory;
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
phase2 > 进入SqlSessionFactoryBuilder类中,看其build()方法如何初始化配置类?
public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null);}public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 初始化配置类 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 这里会做配置文件的解析、拦截器的添加; return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } }}
phase2.1 > 进入到XMLConfigBuilder#parse()方法看配置文件内容的解析, 解析出配置文件中的plugin节点;
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 真正解析配置文件的地方 parseConfiguration(parser.evalNode("/configuration")); return configuration;}/** * 此处是真正解析配置文件的地方(省略部分代码,我们只看Plugin相关的) */private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); // 省略部分代码 ..... // case1:解析出配置文件中的`plugin节点` pluginElement(root.evalNode("plugins")); // 省略部分代码 ..... mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); }}
phase2.2 > 根据配置的interceptor属性实例化Interceptor对象,然后添加到 Configuration对象中的InterceptorChain属性中;
private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // case2:根据配置的`interceptor属性`实例化`Interceptor对象` String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); // Interceptor对象添加到 `Configuration对象`中的`InterceptorChain属性`中; configuration.addInterceptor(interceptorInstance); } }}/** * Configuration#addInterceptor()方法 */public void addInterceptor(Interceptor interceptor) { // 进入到InterceptorChain#addInterceptor()方法 interceptorChain.addInterceptor(interceptor);}
我们发现拦截器链InterceptorChain和从JDK动态代理一步步推导到MyBatis Plugin插件实现原理一文中最后的拦截器链一样耶(实际就是模仿的这里的写法);
public class InterceptorChain { private final List
总而言之,初始化配置文件的时候会把所有的拦截器添加到拦截器链中;下面来看一下什么时候把拦截器插入到需要拦截的接口中。
2、拦截器何时被使用?拦截哪些接口?
按住Command快捷键,接着点InterceptorChain,我们发现只有Configuration类中会使用到InterceptorChain;
再进入Configuration类,全局搜索InterceptorChain;
从上述图片可以看出mybatis 在实例化Executor、ParameterHandler、ResultSetHandler、StatementHandler四大接口对象的时候会调用interceptorChain.pluginAll() 方法将所有的拦截器插入进去;
public class Configuration { // 集成拦截器链 protected final InterceptorChain interceptorChain = new InterceptorChain(); // 1、创建参数处理器 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); // 在此处插入插件/拦截器 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } // 2、创建结果集处理器 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; } // 3、创建SQL语句处理器 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); } // 4、创建执行器 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; // 防止粗心之人将defaultExecutorType设成null executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; // 判断产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor 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); } // 如果要走缓存,则采用装饰者模式使用CachingExecutor(默认就是有缓存),默认都是返回CachingExecutor if (cacheEnabled) { executor = new CachingExecutor(executor); } // 在此处插入插件/拦截器,通过插件改变Executor行为 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
其实本质上就是遍历拦截器链中所有拦截器的plugin() 方法;mybatis官方推荐的plugin方法是Plugin.wrap()方法,Plugin类其实就是我们在动态代理一文中提到的TargetProxy;该类实现了JDK动态代理中的InvocationHandler接口:
/** * InvocationHandler JDK动态代理 * * @author Clinton Begin */public class Plugin implements InvocationHandler { private Object target; private Interceptor interceptor; /** * 方法签名,Key为接口Class,value为方法 */ private Map
Plugin类中对每个拦截器的@Intercepts中@Signature注解中配置的类型 1> 首先和目标对象所实现的接口进行匹配,如果匹配上了,则说明目标对象是当前拦截器所关注的。
2> 其次在代理对象执行的时候(invoke()方法),对目标方法进行判断:判断其是不是当前拦截器所关注的方法,如果是则执行拦截器的intercept方法,否则直接执行目标方法。
四、总结
MyBatis中的拦截器机制其实就是基于JDK的动态代理实现,所有可能被拦截的处理类都会生成一个代理类;如果有N个拦截器,就会有N个代理,而层层生成动态代理是比较耗性能的。
虽然我们能指定插件拦截的位置,但这个是在执行方法时利用反射动态判断的,初始化的时候只是简单的把拦截器插入到所有可以拦截的地方。
因此非必要尽量不要编写拦截器。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~