MyBatis源码分析——MyBatis的扩展点(pugins)
1、MyBatis擴展點plugins
mybatis的擴展是通過攔截器Interceptor來實現(xiàn)的,本質(zhì)上就是JDK的動態(tài)代理,所以它只能對接口進行攔截,我們一步步看一下MyBatis是如何將這些擴展暴露給我們開發(fā)者使用的。
SqlSession的創(chuàng)建過程【重點】:
mybatis中的SQL都是通過DefaultSqlSession去執(zhí)行的。
MyBatis 是怎么構(gòu)造 DefaultSqlSession 的?:【重點】
通過查看源碼,得知 MyBatis 是通過 DefaultSqlSessionFactory 來構(gòu)造 DefaultSqlSession 的。
DefaultSqlSessionFactory#openSessionFromDataSource(ExecutorType, TransactionIsolationLevel, boolean)
重點看一下 Configuration#newExecutor(Transaction transaction, ExecutorType executorType)
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 默認使用 SimpleExecutorExecutor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction); // 使用 BatchExecutor} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction); // 使用 ReuseExecutor} else {executor = new SimpleExecutor(this, transaction); // 默認使用 SimpleExecutor}if (cacheEnabled) {executor = new CachingExecutor(executor); // 使用 CachingExecutor}executor = (Executor) interceptorChain.pluginAll(executor); // 執(zhí)行所有的MyBatis攔截器,并返回 Executorreturn executor; }至此,我們找到了MyBatis的一個擴展點——攔截器interceptor。
MyBatis Inteceptor是使用JDK的動態(tài)代理來實現(xiàn)的,所以它只能對接口進行攔截。
里面兩個很重要的注解是:@Intercepts、@Signature
@Intercepts : 標記要攔截的方法簽名
@Signature : 方法簽名,唯一的標記一個接口的方法
通過查看源碼,我們還可以知道,MyBatis所有的代理攔截都是通過 InterceptorChain.pluginAll(Object target) 來實現(xiàn)的。
至此,我們得到下圖:
通過上圖可知,Mybatis支持對 Executor 、 StatementHandler 、 ResultSetHandler 和 PameterHandler 進行攔截,也就是說會對這4種對象進行代理。
Executor : 作用是執(zhí)行SQL語句(所有的sql),并且對事務(wù)、緩存等提供統(tǒng)一接口。(在這一層上做攔截的權(quán)限會更大)
StatementHandler : 作用是對 statement 進行預(yù)處理,并且提供統(tǒng)一的原子的增、刪、改、查接口。(如果要在SQL執(zhí)行前進行攔截的話,攔截這里就可以了)
ResultSetHandler : 作用是對返回結(jié)果ResultSet進行處理。
PameterHandler : 作用是對參數(shù)進行賦值。
2、源碼解讀具體實現(xiàn)(以Executor接口為例)
2.1、創(chuàng)建SqlSession時,SqlSessionFactroy會解析mybatis.xml配置文件中的plugins標簽,并將Interceptor屬性定義的Interceptor放到interceptorChain中;
// SqlSessionFactoryBuilder.java public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);// 解析mybatis.xml配置文件,并創(chuàng)建DefaultSqlSessionFactoryreturn build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {reader.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}} // XMLConfigBuilder.javapublic Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));return configuration;}// 解析mybatis.xml中的各個標簽private void parseConfiguration(XNode root) {try {propertiesElement(root.evalNode("properties")); //issue #117 read properties firsttypeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));settingsElement(root.evalNode("settings"));environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}// 解析plugins標簽,并把Interceptor放到interceptorChain中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).newInstance();interceptorInstance.setProperties(properties);configuration.addInterceptor(interceptorInstance);}}}// Configuration,mybatis文件的抽象類public void addInterceptor(Interceptor interceptor) {interceptorChain.addInterceptor(interceptor);}2.2、DefaultSqlSessionFactory.openSession()時使用JDK動態(tài)代理生成@Signature注解指定的被代理類(包含代理的方法以及方法參數(shù))
// DefaultSqlSessionFactory.javapublic SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// 使用Configuration創(chuàng)建Executorfinal Executor executor = configuration.newExecutor(tx, execType, autoCommit);return new DefaultSqlSession(configuration, executor);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}2.3、InterceptorChain生成的具體過程
// InterceptorChain.javapublic Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;} // Interceptor的具體實現(xiàn)類(即我們業(yè)務(wù)上要實現(xiàn)的功能)@Overridepublic Object plugin(Object arg0) {return Plugin.wrap(arg0, this);} // Plugin.javapublic static Object wrap(Object target, Interceptor interceptor) {// getSignatureMap獲取Interceptor類上的@Intercepts(@Signature)內(nèi)容Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);// 生成目標類target(Executor.class)的代理類,實現(xiàn)我們需要的plugin功能if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}// 解析實現(xiàn)Interceptor接口的類上定義的@Intercepts(@Signature)內(nèi)容,獲取需要攔截的類和方法。// 例如:@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);if (interceptsAnnotation == null) { // issue #251throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); }Signature[] sigs = interceptsAnnotation.value();Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();for (Signature sig : sigs) {Set<Method> methods = signatureMap.get(sig.type());if (methods == null) {methods = new HashSet<Method>();signatureMap.put(sig.type(), methods);}try {// sig.type()即Executor.classMethod 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;}3、demo關(guān)鍵步驟
3.1、實現(xiàn)自定義的Interceptor
// 自定義攔截器@Intercepts({@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})public class MyTestInterceptor implements Interceptor {private static final String MSG = "octopus route table info is not exit!";@Overridepublic Object intercept(Invocation arg0) throws Throwable {Object obj = null;try {obj = arg0.proceed();} catch (Throwable e) {if (e.getCause() instanceof MySQLSyntaxErrorException) {MySQLSyntaxErrorException ex = (MySQLSyntaxErrorException) e.getCause();System.out.println("====" + ex.getErrorCode());System.out.println("====" + ex.getSQLState());System.out.println("====" + ex.getMessage());System.out.println("====" + ex.getCause());if (MSG.equals(ex.getMessage())) {throw new RouteTableNoExistException();}}}return obj;}@Overridepublic Object plugin(Object arg0) {return Plugin.wrap(arg0, this);}@Overridepublic void setProperties(Properties arg0) {System.out.println("env value: " + arg0.getProperty("names"));}}3.2、在mybatis.xml中配置plugins
<configuration><plugins><plugin interceptor="com.pinganfu.interceptor.MyTestInterceptor" /></plugins> <environments default="development"><environment id="development"><transactionManager type="MANAGED"><property name="closeConnection" value="false" /></transactionManager><dataSource type="POOLED"><property name="driver" value="${driver}" /><property name="url" value="${jdbcUrl}" /><property name="username" value="${username}" /><property name="password" value="${password}" /></dataSource></environment></environments> <mappers><mapper resource="mappers/TBATMapper.xml" /></mappers> </configuration>3.3、獲取SqlSession
Properties pro = new Properties();try {pro.load(Resources.getResourceAsStream("jdbc.properties"));// 加載mybatis.xml中的pluginsInputStream in = Resources.getResourceAsStream("mybatis.xml");sqlSession = new SqlSessionFactoryBuilder().build(in, "development", pro).openSession();} catch (IOException e) {e.printStackTrace();}4、mybatis針對各種異常的處理
mybatis通過DefaultSqlSession執(zhí)行時,會將發(fā)生的所有異常統(tǒng)一包裝成PersistenceException再拋出,我們可以通過PersistenceException.getCause()獲取具體的異常。
// DefaultSqlSession.javapublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);return result;} catch (Exception e) {// 對執(zhí)行發(fā)生的所有Exception進行wrap之后再拋出throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);} finally {ErrorContext.instance().reset();}} // ExceptionFactory.javapublic static RuntimeException wrapException(String message, Exception e) {// 將Exception進行統(tǒng)一包裝成PersistenceExceptionreturn new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);}總結(jié)
以上是生活随笔為你收集整理的MyBatis源码分析——MyBatis的扩展点(pugins)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java自定义注解Annotation的
- 下一篇: Mockito的使用(一)——@Inje