Mybatis运行原理及源码解析
Mybatis源碼解析
一、前言
本文旨在mybatis源碼解析,將整個mybatis運行原理講解清楚,本文代碼地址:
https://github.com/lchpersonal/mybatis-learn
本文使用的mybatis版本:3.2.8,
jdk版本:1.8
數據庫版本:mysql 5.6,數據庫腳本如下,此外在源文件為:resources/mybatis.sql
mybatis主配置文件mybatis-config.xml內容如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><properties resource="dbconfig.properties"></properties><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><mappers><mapper resource="UserMapper.xml"/></mappers>UserMapper.xml的內容如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cl.mybatis.learn.mapper.UserMapper"><select id="selectById" resultType="com.cl.mybatis.learn.user.User">select * from user where id = #{id}</select> </mapper>UserMapper.java內容如下:
package com.cl.mybatis.learn.mapper; import com.cl.mybatis.learn.user.User; /*** @Author: chengli* @Date: 2018/11/24 12:08*/ public interface UserMapper {User selectById(int id); }Mybatis001.java 主函數內容如下:
package com.cl.mybatis.learn; /*** @Author: chengli* @Date: 2018/11/24 12:00*/ public class Mybatis001 {public static void main(String[] args) throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession session = sqlSessionFactory.openSession();try {UserMapper mapper = session.getMapper(UserMapper.class);System.out.println(mapper.selectById(1));} finally {session.close();}} }其他內容過于簡單,詳情請移步github下載源代碼查看。
從main函數的運行步驟,本文將主要通過四個部分探討mybatis的原理:
SqlSessionFactory的創建
SqlSession的創建
UserMapper代理的生成
數據庫查詢操作執行流程
二、SqlSessionFactory的創建
2.1 整體介紹
SqlSessionFactory的創建是SqlSessionFactoryBuilder通過mybatis主配置文件構建出的:
parser.parse()到底做了什么呢?看如下代碼:
private void parseConfiguration(XNode root) {try {/**解析配置文件中的各種屬性*/this.propertiesElement(root.evalNode("properties"));/**解析別名配置*/this.typeAliasesElement(root.evalNode("typeAliases"));/**解析插件配置*/this.pluginElement(root.evalNode("plugins"));/**解析對象工廠元素*/this.objectFactoryElement(root.evalNode("objectFactory"));this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));/**解析mybatis的全局設置信息*/this.settingsElement(root.evalNode("settings"));/**解析mybatis的環境配置*/this.environmentsElement(root.evalNode("environments"));this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));/**解析類型處理器配置信息*/this.typeHandlerElement(root.evalNode("typeHandlers"));/**解析mapper配置信息*/this.mapperElement(root.evalNode("mappers"));} catch (Exception var3) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);} }實際上就是解析主配置文件中的各個節點,然后保存在Configuration當中,然后使用Configuration創建出一個DefaultSqlsessionFactory對象,至此過程結束:
public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config); }此處,我們可以重點關注如下兩個地方,看看具體在做了什么動作:
this.pluginElement(root.evalNode("plugins")); this.mapperElement(root.evalNode("mappers"));以上兩個點分別是插件注冊,和mapper的掃描注冊。
2.2 插件的注冊
首先我們先看一下源代碼:
private void pluginElement(XNode parent) throws Exception {if (parent != null) {Iterator i$ = parent.getChildren().iterator();while(i$.hasNext()) {XNode child = (XNode)i$.next();String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();/**實例化攔截器類*/Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();interceptorInstance.setProperties(properties);/**將實例化的攔截器類放到configuration中的interceptorChain中*/this.configuration.addInterceptor(interceptorInstance);}}}實際上就是通過interceptor標簽,解析出攔截器類,然后將其實例化并保存到Configuration類中的InterceptorChain中,以備后用。
public void addInterceptor(Interceptor interceptor) {this.interceptorChain.addInterceptor(interceptor); }2.3 mappers的掃描與解析
this.mapperElement(root.evalNode("mappers"));通過代碼我們可以知道,這里主要是解析mappers標簽的。我們先來看一下mappers標簽里面是什么:
<mappers><mapper resource="UserMapper.xml"/> </mappers>所以他會去解析UserMapper.xml中的內容:
<mapper namespace="com.cl.mybatis.learn.mapper.UserMapper"><select id="selectById" resultType="com.cl.mybatis.learn.user.User">select * from user where id = #{id}</select> </mapper>我們看一下源代碼:
private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {/**如果子節點是配置的package,那么進行包自動掃描處理*/ if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");/**如果子節點配置的是resource、url、mapperClass,本文我們使用的是resource*/if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);/**解析resource引入的另外一個xml文件*/XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}} }下面我們具體看一下他是如何解析另一個xml文件的:
public void parse() {if (!configuration.isResourceLoaded(resource)) {/**解析sql語句*/configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);/**解析名稱空間,實際上就是對應綁定的接口類*/bindMapperForNamespace();}parsePendingResultMaps();parsePendingChacheRefs();parsePendingStatements(); }下面我們來看一下 configurationElement(parser.evalNode("/mapper"))到底做了什么:
public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);Class<?> resultTypeClass = resolveClass(resultType);String resultSetType = context.getStringAttribute("resultSetType");StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);boolean useCache = context.getBooleanAttribute("useCache", isSelect);boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);String resultSets = context.getStringAttribute("resultSets");String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? new Jdbc3KeyGenerator() : new NoKeyGenerator();}builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } public MappedStatement addMappedStatement(String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize,Integer timeout,String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType,boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty,String keyColumn,String databaseId,LanguageDriver lang,String resultSets) {if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved");id = applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);statementBuilder.resource(resource);statementBuilder.fetchSize(fetchSize);statementBuilder.statementType(statementType);statementBuilder.keyGenerator(keyGenerator);statementBuilder.keyProperty(keyProperty);statementBuilder.keyColumn(keyColumn);statementBuilder.databaseId(databaseId);statementBuilder.lang(lang);statementBuilder.resultOrdered(resultOrdered);statementBuilder.resulSets(resultSets);setStatementTimeout(timeout, statementBuilder);setStatementParameterMap(parameterMap, parameterType, statementBuilder);setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);MappedStatement statement = statementBuilder.build();configuration.addMappedStatement(statement);return statement; }通過解析一個個的標簽,最終將sql語句的所有信息封裝成MappedStatement對象,然后存儲在configuration對象中。
那么bindMapperForNamespace();又做了什么呢?
實際上就是解析該sql對應的class,并把該class放到configuration中的mapperRegistry中。實際上mybatis的所有配置信息以及運行時的配置參數全部都保存在configuration對象中。
所以整個流程可以用如下的時序圖表示:
三、SqlSession的創建
我們知道,SqlSessionFactory創建的時候實際上返回的是一個DefaultSqlSessionFactory對象。
當執行openSession()操作的時候,實際上執行的代碼如下:
從代碼可以知道,openSession操作會創建Mybatis四大對象之一的Executor對象,創建過程如下
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);}/**如果開啟了二級緩存,executor會被CachingExecutor包裝一次*/if (cacheEnabled) {executor = new CachingExecutor(executor);}/**嘗試將executor使用interceptorChain中的每個interceptor包裝一次(根據配置),這里是對Mybatis強大的插件開發功能做支持*/executor = (Executor) interceptorChain.pluginAll(executor);return executor; }默認情況下會返回一個SimpleExecutor對象。然后SimpleExecutor被封裝到DefaultSqlSession。這里我們需要注意一下,在Executor創建完畢之后,會根據配置是否開啟了二級緩存,來決定是否使用CachingExecutor包裝一次Executor。最后嘗試將executor使用interceptorChain中的每個interceptor包裝一次(根據配置),這里是對Mybatis強大的插件開發功能做支持。
四、Mapper代理的生成
當我們使用如下代碼:
UserMapper mapper = session.getMapper(UserMapper.class);來獲取UserMapper的時候,實際上是從configuration當中的MapperRegistry當中獲取UserMapper的代理對象:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null)throw new BindingException("Type " + type + " is not known to the MapperRegistry.");try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}并且MapperProxy實現了InvocationHandler接口,從以上代碼可以看出,實際上使用的就是jdk的動態代理,給UserMapper接口生成一個代理對象。實際上就是MapperProxy的一個對象,如下圖調試信息所示:
所以整個代理對象生成過程可以用如下時序圖表示:
五、執行查詢語句
我們知道,我們獲取到的UserMapper實際上是代理對象MapperProxy,所以我們執行查詢語句的時候實際上執行的是MapperProxy的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/**如果調用的是Object原生的方法,則直接放行*/if (Object.class.equals(method.getDeclaringClass())) {try {return method.invoke(this, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args); }我們再來看看cachedMapperMethod方法:
private MapperMethod cachedMapperMethod(Method method) {MapperMethod mapperMethod = methodCache.get(method);if (mapperMethod == null) {mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());methodCache.put(method, mapperMethod);}return mapperMethod; }可以看到,先根據方法簽名,從方法緩存中獲取方法,如果為空,則生成一個MapperMethod放入緩存并返回。
所以最終執行查詢的是MapperMethod的execute方法:
public Object execute(SqlSession sqlSession, Object[] args) {Object result;if (SqlCommandType.INSERT == command.getType()) {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));} else if (SqlCommandType.UPDATE == command.getType()) {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));} else if (SqlCommandType.DELETE == command.getType()) {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));/**select查詢語句*/} else if (SqlCommandType.SELECT == command.getType()) {/**檔返回類型為空*/if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;/**當返回many的時候*/} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);/**當返回值類型為Map時*/} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else {/**除去以上情況,執行這里的步驟*/Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);}} else {throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result; }我們示例中執行的分支語句是:
Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);這里有一個查詢參數的解析過程:
public Object convertArgsToSqlCommandParam(Object[] args) {final int paramCount = params.size();if (args == null || paramCount == 0) {return null;/**參數沒有標注@Param注解,并且參數個數為一個*/} else if (!hasNamedParameters && paramCount == 1) {return args[params.keySet().iterator().next()];/**否則執行這個分支*/} else {final Map<String, Object> param = new ParamMap<Object>();int i = 0;for (Map.Entry<Integer, String> entry : params.entrySet()) {param.put(entry.getValue(), args[entry.getKey()]);// issue #71, add param names as param1, param2...but ensure backward compatibilityfinal String genericParamName = "param" + String.valueOf(i + 1);if (!param.containsKey(genericParamName)) {param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}}這里的代碼看起來可能邏輯不太好懂,我這里解釋一下結論就好,這里參數解析如果判斷參數一個只有一個(一個單一參數或者是一個集合參數),并且沒有標注@Param注解,那么直接返回這個參數的值,否則會被封裝為一個Map,然后再返回。
封裝的樣式如下用幾個示例解釋:
/**接口為*/ User selectByNameSex(String name, int sex); /**我們用如下格式調用*/ userMapper.selectByNameSex("張三",0); /**參數會被封裝為如下格式:*/ 0 ---> 張三 1 ---> 0 param1 ---> 張三 param2 ---> 0如下圖所示:
例2:
參數處理完接下來就是調用執行過程,最終調用執行的是DefaultSqlSession中的selectList方法:
public <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) {throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);} finally {ErrorContext.instance().reset();} }這里調用SimpleExecutor的query方法執行查詢操作,接著調用doQuery方法:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();/**這里出現了Mybatis四大對象中的StatementHandler*/StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);} } 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) /**創建StatementHandler并應用到插件支持*/ interceptorChain.pluginAll(statementHandler);return statementHandler; }在創建StatementHandler的同時,應用插件功能,同時創建了Mybatis四大對象中的另外兩個對象:
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {………………/**Mybatis四大對象中的ParameterHandler*/ this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);/**Mybatis四大對象中的ResultSetHandler*/ this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql); }同時在創建的時候,也運用到了插件增強功能:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {………………interceptorChain.pluginAll(parameterHandler);return parameterHandler; }public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {………………interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}接下來就是執行正常的JDB C查詢功能了,參數設置由ParameterHandler操作,結果集處理由ResultSetHandler處理。至此,整個查詢流程結束。
整個查詢階段的時序圖可以用如下圖表示:
整個查詢流程,可以總結如下圖:
六、Mybatis插件開發
6.1 四大對象
Mybatis四大對象指的是:Executor、StatementHandler、ParamaterHandler、ResultSetHandler。Mybatis允許我們在四大對象執行的過程中對其指定方法進行攔截,這樣就可以很方便了進行功能的增強,這個功能跟Spring的切面編程非常類似。上文我們都有提到過,在四大對象創建的時候,都進行了插件增強,下面我們就來講解一下其實現原理。
例如我們希望在sql語句之前前后進行時間打印,計算出sql執行的時間。此功能我們就可以攔截StatementHandler。這里我們需要時間Mybatis提供的Intercaptor接口。
package com.cl.mybatis.learn.intercaptor;/*** @Author: chengli* @Date: 2018/11/24 17:37*//**該注解簽名告訴此攔截器攔截四大對象中的哪個對象的哪個方法,以及方法的簽名信息*/ @Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}) }) public class SqlLogPlugin implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {long begin = System.currentTimeMillis();try {return invocation.proceed();} finally {long time = System.currentTimeMillis() - begin;System.out.println("sql 運行了 :" + time + " ms");}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {} }此時,攔截器的配置就完成了,運行結果如下:
DEBUG 11-24 17:51:34,877 ==> Preparing: select * from user where id = ? (BaseJdbcLogger.java:139) DEBUG 11-24 17:51:34,940 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:139) DEBUG 11-24 17:51:34,990 <== Total: 1 (BaseJdbcLogger.java:139) sql 運行了 :51 ms User{id=1, name='張三', age=42, sex=0}6.2 插件原理探究
從代碼我們可以看出:
public Object plugin(Object target) {return Plugin.wrap(target, this);}該代碼是對目標對象的包裝,實際運行的時候,是使用的包裝之后的類,運行的時候執行的是intercept方法。那么現在我們來看下它是怎么進行包裝的:
public static Object wrap(Object target, Interceptor interceptor) {Map<Class<?>, Set<Method>> 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; }而Plugin類實現了InvocationHanlder接口:
public class Plugin implements InvocationHandler {…… }顯然這里使用的就是JDK的動態代理,對目標對象包裝了一層。
假如一個對象被多個攔截器進行了多次包裝,那么后包裝的在最外層會先執行。
總結
以上是生活随笔為你收集整理的Mybatis运行原理及源码解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java GC日志查看和分析
- 下一篇: Spring事物详解和传播行为