MyBatis插件之拦截器(Interceptor)_第1页
MyBatis插件之拦截器(Interceptor)_第2页
MyBatis插件之拦截器(Interceptor)_第3页
MyBatis插件之拦截器(Interceptor)_第4页
MyBatis插件之拦截器(Interceptor)_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

MyBatis插件之拦截器(Interceptor)⼀.背景在很多业务场景下我们需要去拦截sql,达到不⼊侵原有代码业务处理⼀些东西,⽐如:分页操作,数据权限过滤操作,SQL执⾏时间性能监控等等,这⾥我们就可以⽤到Mybatis的拦截器Interceptor⼆.Mybatis核⼼对象介绍从MyBatis代码实现的⾓度来看,MyBatis的主要的核⼼部件有以下⼏个:Configuration初始化基础配置,⽐如MyBatis的别名等,⼀些重要的类型对象,如,插件,映射器,ObjectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中SqlSessionFactorySqlSession⼯⼚SqlSession作为MyBatis⼯作的主要顶层API,表⽰和数据库交互的会话,完成必要数据库增删改查功能ExecutorMyBatis执⾏器,是MyBatis调度的核⼼,负责SQL语句的⽣成和查询缓存的维护StatementHandler封装了JDBCStatement操作,负责对JDBCstatement的操作,如设置参数、将Statement结果集转换成List集合。ParameterHandler负责对⽤户传递的参数转换成JDBCStatement所需要的参数,ResultSetHandlerTypeHandler负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;负责java数据类型和jdbc数据类型之间的映射和转换MappedStatementMappedStatement维护了⼀条<select|update|delete|insert>节点的封装,SqlSource负责根据⽤户传递的parameterObject,动态地⽣成SQL语句,将信息封装到BoundSql对象中,并返回BoundSql表⽰动态⽣成的SQL语句以及相应的参数信息三.Mybatis执⾏概要图四.MyBatis拦截器原理实现1.Mybatis⽀持对Executor、StatementHandler、PameterHandler和ResultSetHandler接⼝进⾏拦截,也就是说会对这4种对象进⾏代理2.⾸先从配置⽂件解析开始通过SqlSessionFactoryBean去构建Configuration添加拦截器并构建获取SqlSessionFactory/***{@codeFactoryBean}thatcreatesanMyBatis{@codeSqlSessionFactory}.*ThisistheusualwaytosetupasharedMyBatis{@codeSqlSessionFactory}inaSpringapplicationcontext;*theSqlSessionFactorycanthenbepassedtoMyBatis-basedDAOsviadependencyinjection.**Either{@codeDataSourceTransactionManager}or{@codeJtaTransactionManager}canbeusedfortransaction*demarcationincombinationwitha{@codeSqlSessionFactory}.JTAshouldbeusedfortransactions*whichspanmultipledatabasesorwhencontainermanagedtransactions(CMT)arebeingused.**@authorPutthibongBoonbong*@authorHunterPresnall*@authorEduardoMacarron*@authorEddúMeléndez*@authorKazukiShimizu**@see#setConfigLocation*@see#setDataSource*/publicclassSqlSessionFactoryBeanimplementsFactoryBean<SqlSessionFactory>,InitializingBean,ApplicationListener<ApplicationEvent>{privatestaticfinalLogLOGGER=LogFactory.getLog(SqlSessionFactoryBean.class);privateResourceconfigLocation;privateConfigurationconfiguration;privateResource[]mapperLocations;privateDataSourcedataSource;privateTransactionFactorytransactionFactory;privatePropertiesconfigurationProperties;privateSqlSessionFactoryBuildersqlSessionFactoryBuilder=newSqlSessionFactoryBuilder();//...此处省略部分源码/***Builda{@codeSqlSessionFactory}instance.**ThedefaultimplementationusesthestandardMyBatis{@codeXMLConfigBuilder}APItobuilda*{@codeSqlSessionFactory}instancebasedonanReader.*Since1.3.0,itcanbespecifieda{@linkConfiguration}instancedirectly(withoutconfigfile).**@returnSqlSessionFactory*@throwsIOExceptionifloadingtheconfigfilefailed*/protectedSqlSessionFactorybuildSqlSessionFactory()throwsIOException{Configurationconfiguration;//根据配置信息构建Configuration实体类XMLConfigBuilderxmlConfigBuilder=null;if(this.configuration!=null){configuration=this.configuration;configuration=this.configuration;if(configuration.getVariables()==null){configuration.setVariables(this.configurationProperties);}elseif(this.configurationProperties!=null){configuration.getVariables().putAll(this.configurationProperties);}}elseif(this.configLocation!=null){xmlConfigBuilder=newXMLConfigBuilder(this.configLocation.getInputStream(),null,this.configurationProperties);configuration=xmlConfigBuilder.getConfiguration();}else{if(LOGGER.isDebugEnabled()){LOGGER.debug("Property'configuration'or'configLocation'notspecified,usingdefaultMyBatisConfiguration");}configuration=newConfiguration();if(this.configurationProperties!=null){configuration.setVariables(this.configurationProperties);}}//...此处省略部分源码if(hasLength(this.typeAliasesPackage)){String[]typeAliasPackageArray=tokenizeToStringArray(this.typeAliasesPackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);for(StringpackageToScan:typeAliasPackageArray){configuration.getTypeAliasRegistry().registerAliases(packageToScan,typeAliasesSuperType==null?Object.class:typeAliasesSuperType);if(LOGGER.isDebugEnabled()){LOGGER.debug("Scannedpackage:'"+packageToScan+"'foraliases");}}}if(!isEmpty(this.typeAliases)){for(Class<?>typeAlias:this.typeAliases){configuration.getTypeAliasRegistry().registerAlias(typeAlias);if(LOGGER.isDebugEnabled()){LOGGER.debug("Registeredtypealias:'"+typeAlias+"'");}}}//查看是否注⼊拦截器,有的话添加到Interceptor集合⾥⾯if(!isEmpty(this.plugins)){for(Interceptorplugin:this.plugins){configuration.addInterceptor(plugin);if(LOGGER.isDebugEnabled()){LOGGER.debug("Registeredplugin:'"+plugin+"'");}}}//...此处省略部分源码returnthis.sqlSessionFactoryBuilder.build(configuration);}//...此处省略部分源码}通过原始的XMLConfigBuilder构建configuration添加拦截器publicclassXMLConfigBuilderextendsBaseBuilder{//解析配置privatevoidparseConfiguration(XNoderoot){try{//省略部分代码pluginElement(root.evalNode("plugins"));}catch(Exceptione){thrownewBuilderException("ErrorparsingSQLMapperConfiguration.Cause:"+e,e);}}privatevoidpluginElement(XNodeparent)throwsException{if(parent!=null){for(XNodechild:parent.getChildren()){Stringinterceptor=child.getStringAttribute("interceptor");Propertiesproperties=child.getChildrenAsProperties();InterceptorinterceptorInstance=(Interceptor)resolveClass(interceptor).newInstance();interceptorInstance.setProperties(properties);//调⽤InterceptorChain.addInterceptorconfiguration.addInterceptor(interceptorInstance);}}}}上⾯是两种不同的形式构建configuration并添加拦截器interceptor,上⾯第⼆种⼀般是以前XML配置的情况,这⾥主要是解析配置⽂件的plugin节点,根据配置的interceptor属性实例化Interceptor对象,然后添加到Configuration对象中的InterceptorChain属性中3.定义了拦截器链,初始化配置⽂件的时候就把所有的拦截器添加到拦截器链中org.apache.ibatis.plugin.InterceptorChain源代码如下:publicclassInterceptorChain{privatefinalList<Interceptor>interceptors=newArrayList<Interceptor>();publicObjectpluginAll(Objecttarget){//循环调⽤每个Interceptor.plugin⽅法for(Interceptorinterceptor:interceptors){target=interceptor.plugin(target);}returntarget;}//添加拦截器publicvoidaddInterceptor(Interceptorinterceptor){interceptors.add(interceptor);}publicList<Interceptor>getInterceptors(){returnCollections.unmodifiableList(interceptors);}}4.从以下代码可以看出mybatis在实例化Executor、ParameterHandler、ResultSetHandler、StatementHandler四⼤接⼝对象的时候调⽤interceptorChain.pluginAll()⽅法插⼊进去的。其实就是循环执⾏拦截器链所有的拦截器的plugin()⽅法,mybatis官⽅推荐的plugin⽅法是Plugin.wrap()⽅法,这个类就是我们上⾯的TargetProxy类org.apache.ibatis.session.Configuration类,其代码如下:publicclassConfiguration{protectedfinalInterceptorChaininterceptorChain=newInterceptorChain();//创建参数处理器publicParameterHandlernewParameterHandler(MappedStatementmappedStatement,ObjectparameterObject,BoundSqlboundSql){//创建ParameterHandlerParameterHandlerparameterHandler=mappedStatement.getLang().createParameterHandler(mappedStatement,parameterObject,boundSql);//插件在这⾥插⼊parameterHandler=(ParameterHandler)interceptorChain.pluginAll(parameterHandler);returnparameterHandler;}//创建结果集处理器publicResultSetHandlernewResultSetHandler(Executorexecutor,MappedStatementmappedStatement,RowBoundsrowBounds,ParameterHandlerparameResultHandlerresultHandler,BoundSqlboundSql){//创建DefaultResultSetHandlerResultSetHandlerresultSetHandler=newDefaultResultSetHandler(executor,mappedStatement,parameterHandler,resultHandler,boundSql,rowBounds)//插件在这⾥插⼊resultSetHandler=(ResultSetHandler)interceptorChain.pluginAll(resultSetHandler);returnresultSetHandler;}//创建语句处理器publicStatementHandlernewStatementHandler(Executorexecutor,MappedStatementmappedStatement,ObjectparameterObject,RowBoundsrowBounds,//创建路由选择语句处理器StatementHandlerstatementHandler=newRoutingStatementHandler(executor,mappedStatement,parameterObject,rowBounds,resultHandler,boundSq//插件在这⾥插⼊statementHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler);returnstatementHandler;}publicExecutornewExecutor(Transactiontransaction){returnnewExecutor(transaction,defaultExecutorType);}//产⽣执⾏器publicExecutornewExecutor(Transactiontransaction,ExecutorTypeexecutorType){executorType=executorType==null?defaultExecutorType:executorType;//这句再做⼀下保护,囧,防⽌粗⼼⼤意的⼈将defaultExecutorType设成null?executorType=executorType==null?ExecutorType.SIMPLE:executorType;Executorexecutor;//然后就是简单的3个分⽀,产⽣3种执⾏器BatchExecutor/ReuseExecutor/SimpleExecutorif(ExecutorType.BATCH==executorType){executor=newBatchExecutor(this,transaction);}elseif(ExecutorType.REUSE==executorType){executor=newReuseExecutor(this,transaction);}else{executor=newSimpleExecutor(this,transaction);}//如果要求缓存,⽣成另⼀种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutorif(cacheEnabled){executor=newCachingExecutor(executor);}//此处调⽤插件,通过插件可以改变Executor⾏为executor=(Executor)interceptorChain.pluginAll(executor);returnexecutor;}}5.Mybatis的Plugin动态代理org.apache.ibatis.plugin.Plugin源代码如下publicclassPluginimplementsInvocationHandler{publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){//从拦截器的注解中获取拦截的类名和⽅法信息Map<Class<?>,Set<Method>>signatureMap=getSignatureMap(interceptor);//取得要改变⾏为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)Class<?>type=target.getClass();//取得接⼝Class<?>[]interfaces=getAllInterfaces(type,signatureMap);//产⽣代理,是Interceptor注解的接⼝的实现类才会产⽣代理if(interfaces.length>0){returnProxy.newProxyInstance(type.getClassLoader(),interfaces,newPlugin(target,interceptor,signatureMap));}returntarget;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{try{//获取需要拦截的⽅法Set<Method>methods=signatureMap.get(method.getDeclaringClass());//是Interceptor实现类注解的⽅法才会拦截处理if(methods!=null&&methods.contains(method)){//******调⽤Iercept,也即插⼊了我们⾃⼰的逻辑********returnercept(newInvocation(target,method,args));}//最后还是执⾏原来逻辑returnmethod.invoke(target,args);}catch(Exceptione){throwExceptionUtil.unwrapThrowable(e);}}//取得签名Map,就是获取Interceptor实现类上⾯的注解,要拦截的是那个类(Executor//,ParameterHandler,ResultSetHandler,StatementHandler)的那个⽅法privatestaticMap<Class<?>,Set<Method>>getSignatureMap(Interceptorinterceptor){//取Intercepts注解,例⼦可参见ExamplePlugin.javaInterceptsinterceptsAnnotation=interceptor.getClass().getAnnotation(Intercepts.class);//issue#251//必须得有Intercepts注解,没有报错if(interceptsAnnotation==null){thrownewPluginException("No@Interceptsannotationwasfoundininterceptor"+interceptor.getClass().getName());}//value是数组型,Signature的数组Signature[]sigs=interceptsAnnotation.value();//每个class⾥有多个Method需要被拦截,所以这么定义Map<Class<?>,Set<Method>>signatureMap=newHashMap<Class<?>,Set<Method>>();for(Signaturesig:sigs){Set<Method>methods=signatureMap.get(sig.type());if(methods==null){methods=newHashSet<Method>();signatureMap.put(sig.type(),methods);}try{Methodmethod=sig.type().getMethod(sig.method(),sig.args());methods.add(method);}catch(NoSuchMethodExceptione){thrownewPluginException("Couldnotfindmethodon"+sig.type()+"named"+sig.method()+".Cause:"+e,e);}}returnsignatureMap;}//取得接⼝privatestaticClass<?>[]getAllInterfaces(Class<?>type,Map<Class<?>,Set<Method>>signatureMap){privatestaticClass<?>[]getAllInterfaces(Class<?>type,Map<Class<?>,Set<Method>>signatureMap){Set<Class<?>>interfaces=newHashSet<Class<?>>();while(type!=null){for(Class<?>c:type.getInterfaces()){//拦截其他的⽆效if(signatureMap.containsKey(c)){interfaces.add(c);}}type=type.getSuperclass();}returninterfaces.toArray(newClass<?>[interfaces.size()]);}}6.我们⾃⼰实现的拦截器@Slf4j@Component@Intercepts({@Signature(method="prepare",type=StatementHandler.class,args={Connection.class,Integer.class}),@Signature(method="query",type=Executor.class,args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class,CacheKey.c@SuppressWarnings("unchecked")publicclassSqliteDataSourceInterceptorimplementsInterceptor{@OverridepublicObjectplugin(Objecttarget){//调⽤插件returnPlugin.wrap(target,this);}@OverridepublicvoidsetProperties(Propertiespr

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论