延迟加载前言:
在很多真实的实战的业务场景中,由于业务的复杂度,都会让我们进行过多的进行一些连接查询,在数据量少的时候,我们或许感受不到查询给我们带来的效率影响,在数据量和业务复杂的时候我们进行过多的连接查询会大大减低我们的一个查询效率,并查询出一些多余字段。为了解决这个问题的出现,延迟加载能够解决当前这个问题,在不需要一些数据的时候我们不进行获取,在不改变逻辑的前提下获取想要的数据。延迟加载机制能很好的避免一些无谓的性能开销而提出来的,所谓的延迟加载就是当在真正需要的时候,才真正执行数据加载。
mybatis实现延迟加载配置:
<!-- 改变mybatis为我们提供的默认框架配置 -->
<settings>
<!--开启mybatis支持延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--false,每个延迟加载属性会按需加载,在 3.4.1 及之前的版本中默认为 true-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
mybatis实现延迟加载实现配置:
mybatis实现查询主要为我们提供了相应的resultMap标签里面可以进行一个相应的配置,其中association和collection具有延迟加载的功能,我们在进行延迟加载的配置的时候只需要在配置即可。配置如下:
<resultMap id="userAddress" type="org.apache.ibatis.bean.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="password" column="password"></result>
//这里配置的就是我们配置的延迟加载核心配置,当获取相应address的数据集时,我们会调用select="findByAddress"关联的查询语句,将column="id"作为参数传递。
<association property="addresses" javaType="org.apache.ibatis.bean.Address" select="findByAddress" column="id"></association>
</resultMap>
//该查询就是对应上面select="findByAddress"的关联查询,就是当我们需要这个address的结果集的时候,在进行一个查询的操作。
<select id="findByAddress" resultType="org.apache.ibatis.bean.Address">
select * from address where uid = #{uid}
</select>
接下来就为大家带来延迟加载的执行步骤,从源码一步步解析到如何执行到延迟加载的步骤。这里我们必须要了解到相关的代理技术,mybatis提供了相关2中代理模式javassist 和cglib代理技术提供延迟加载的支撑。核心接口为ProxyFactory.class,这是为我们提供的反射工厂,该接口mybatis为我们提供了2个实现类JavassistProxyFactory.class对应的为javassist代理以及CglibProxyFactory.class对应的cglib代理。其实也大同小异,默认mybatis为我们提供的代理实现为javassist代理。原理解析如下:
重查询起初查询一步步讲解,这些具体执行过程,不做详细讲解,当前主要重点讲解延迟加载如何实现。需要了解相应的查询执行原理,请观看我其他的mybatis的讲解博客。
当前也带大家回顾mybatis执行查询的一个过程,当然不做详细讲解。
拿到相应的映射对象,使用执行器执行查询
查询的过程我们就不进行一个细节的讲解了,我们主要关注延迟加载的一个原理,其中延迟加载的体现主要体现在我们处理结果集DefaultResultSetHandler.class对象里面体现。
在进行处理结果集第一个执行主要方法handleResultSets()方法进行结果处理
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//主要用于数据库查询出来的结果集对象
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//结果包装器,内部封装数据库查询出来的结果集,返回第一列的结果集,后续在处理结果集的映射关系的时候都会根据该对象的内部进行处理
ResultSetWrapper rsw = getFirstResultSet(stmt);
//获取查询出来的结果集需要映射的成的具体对象信息,就是将标签resultMap映射成相应的对象
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
//处理结果集对象进行封装成Java对象,封装成ResultMap定义的对象结果集,里面有数据结果集映射的信息
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
......
执行该方法时候里面处理的关键方法为handleResultSet()
内部主要关注handleRowValues()
在这次测试当中我们是处理的简单的结果集,调用的方法handleRowValuesForSimpleResultMap()
该方法里getRowValue()比较核心和关键,因为创建出代理结果集对象就是在该方法中体现出来
其中我们需要关注的又多出一个对象ResultLoaderMap.class内部维护相应的延迟加载具体实现对象,后面会详细讲解。关注createResultObject(),这里也创建了ResultLoaderMap.class对象后续的讲解都会介绍到该对象,请重点关注下该对象。
下面就是相应的代理对象的一个创建过程。
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
//对象构造参数类型
final List<Class<?>> constructorArgTypes = new ArrayList<>();
//对象的构造参数具体数据
final List<Object> constructorArgs = new ArrayList<>();
//创建结果集对象,是我们需要的结果集需要封装成的结果集对象
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
//判断当前是否需要封装成我们自己映射的结果集ResultMap标签里面的结果集
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
//拿到resultMap标签内部的result标签的详细,都会被映射成resultMapping对象,里面关联着result标签里面配置的详细信息
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
//判断每一个标签里面的信息是否符合延迟加载的配置,如果存在为我们创建一个代理目标对象
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
//创建一个被代理的懒加载结果集对象,默认使用的是JavassistProxyFactory()代理工厂
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
下面我们关注的是我们代理对象的一个创建过程,因为只有生成一个代理对象,在我们执行相应的获取时,才能起到延迟加载数据的一个过程,也证实了只有在需要数据的时候才进行加载获取。知道动态代理的各位相信大家都知道,在执行相应的方法之前执行一个代理的方法,不管是那种代理方式,所有的思想都是一样的,所以在我们执行相应方法获取数据的时候,都会进行一个代理方法的一个执行,获取到我们想得到的数据,然后进行一个赋值操作。其中这里我要讲的我们需要比较重点关注一下ResultLoaderMap.class对象,内部维护着相应的延迟加载信息和相应处理器。
/**
* 创建代理对象
* @param target TODO 目标对象,需要被代理的对象
* @param lazyLoader TODO 延迟加载集合,内部维护具体延迟加载器
* @param configuration TODO 核心全局配置类
* @param objectFactory TODO 对象工厂
* @param constructorArgTypes TODO 对象构造函数类型
* @param constructorArgs TODO 对象构造函数具体数据
* @return
*/
@Override
public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}
该JavassistProxyFactory.class内部维护着二个静态内部类,实现了具体的一个代理操作,在执行目标方法之前都会先执行一个又代理方法提供的一个方法。(当前我们讲解EnhancedResultObjectProxyImpl.class)该类实现了MethodHandler.class接口的invoke方法,等同于jdk动态代理提供的InvocationHandler.class方法。接下来关注具体创建代理对象的方法
public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
//需要被代理的目标对象类型
final Class<?> type = target.getClass();
//创建代理执行器,该类实现类MethodHandler类,在执行目标方法之前会优先执行invoke方法,我这里比喻成代理执行器
EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
//创建代理代理对象。参数1:目标对象,参数2:代理执行器,参数3:构造参数类型,参数4:构造参数具体数据
Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
//该方法其实和以上代理也没有什么关系,但是我也做一个我对该方法的一个理解。设想当前,该目标实体类当中有多个需要延迟加载的一个属性,
// 如果我们不做对象的一个赋值的话,就会创建多个代理对象,这其实是一个比较消耗资源的一个表现。
PropertyCopier.copyBeanProperties(type, target, enhanced);
return enhanced;
}
就是创建代理对象的具体方法,下面也给讲解下创建代理对象的具体实现,就算你没有了解过javassist代理技术,其实从源码的分析中你也会不难看出每一步都具体做了什么。
static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
ProxyFactory enhancer = new ProxyFactory();
//设置该对象的父类
enhancer.setSuperclass(type);
try {
type.getDeclaredMethod(WRITE_REPLACE_METHOD);
if (LogHolder.log.isDebugEnabled()) {
LogHolder.log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");
}
}
Object enhanced;
//构造参数类型
Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
//构造参数的具体数据
Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
try {
//创建代理对象
enhanced = enhancer.create(typesArray, valuesArray);
} catch (Exception e) {
throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e);
}
//设置代理对象方法处理器
((Proxy) enhanced).setHandler(callback);
return enhanced;
}
这样我们就得到了一个代理的目标结果集对象,当然这也并没有结束,这只是当前延迟加载的一个基础实现,设想我们如何知道什么方法需要被延迟加载,需要被延迟加载怎么执行如何执行,怎么获取相应的sql语句进行一个操作数据库,怎么将数据进行一个赋值操作。是怎么做的,这些数据是怎么保存进去的,又是怎么在延迟加载的时候获取到这些数据的。希望大家在阅读源码的时候都是带着一个思考的角度去观看源码。有什么写的不对的地方希望大家指出,共同成长。这里我们又回到该createResultObject()方法。因为就是获取相应的对象的,上面已经给大家讲解到了获取对象的过程。
下面我们重点关注createResultObject()方法当中的applyPropertyMappings()方法
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
//结果映射的列表结果集
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
//获取ResultMap标签里面子标签定义的每一列标签的具体信息,封装成ResultMapping.class对象
/**
* 例如:ResultMapping.class就是resultMap标签里面子标签的具体映射信息。例如: <id property="id" column="id"></id>
* <resultMap id="userAddress" type="org.apache.ibatis.bean.User">
* <id property="id" column="id"></id>
* <result property="username" column="username"></result>
* <result property="password" column="password"></result>
* <association property="addresses" javaType="org.apache.ibatis.bean.Address" select="findByAddress" column="id"></association>
* </resultMap>
*/
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
//或得每一列的结果集,每一列数据库查询出来的结果集
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
//需要映射的属性 (String address)这里是实体类需要映射的
final String property = propertyMapping.getProperty();
......省略
上面我有说到如何知道什么方法需要被延迟加载,需要被延迟加载的方法怎么执行如何执行等一些问题,applyPropertyMappings()方法内调用的getPropertyMappingValue()方法里面就有一个核心的体现,将我们需要被延迟加载的一些条件和相应的执行语句都已经需要封装的结果集对象都在该方法中进行。
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
//判断当前属性是否需要被延迟加载,如果不为空说明当前该属性需要被延迟加载的,其实我们可以关注
//ResultLoaderMap.class对象
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
这里带大家在回顾下ResultMapping.class对象就是对result标签的一个数据解析封装的对象,里面存储了相应的信息。我们重点关注下getPropertyMappingValue()方法内部的getNestedQueryMappingValue()该方法,该方法就是为我们延迟加载的条件已经相应的具体信息进行一个设置。其中最为重要的体现就是将我们所需要知道延迟加载类的信息都被封装成了一个对象,ResultLoader.class
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
//嵌套查询需要执行的方法全路径名称
final String nestedQueryId = propertyMapping.getNestedQueryId();
//属性名称,用于绑定相应执行信息。(当然这也是需要被延迟加载执行的时候才有用,不然其实用处也并不大)
final String property = propertyMapping.getProperty();
//根据方法的全路径名,获取相应的MappedStatement,前面我也有讲解,所有在xml以及注解里面定义的sql都会被封装成该对象
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
//需要查询的参数类型
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
//延迟加载的一个参数条件,例如: <association column="id"></association> 配置里面的column值
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
if (nestedQueryParameterObject != null) {
//获取到相应的sql语句对象,内部维护我们的sql语句相关信息
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
//拿到缓存的key,这里指的是二级缓存
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
//延迟加载需要映射成的对象<association property="addresses" javaType="org.apache.ibatis.bean.Address"......里面的javaType
final Class<?> targetType = propertyMapping.getJavaType();
//是否开启了缓存,当前我们测试没有开启二级缓存
if (executor.isCached(nestedQuery, key)) {
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
value = DEFERRED;
} else {
//上述的延迟加载的对象信息都会被封装成一个ResultLoader.class对象,这里面有该对象的所有信息。
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
//判断该对象是否是需要延迟加载
if (propertyMapping.isLazy()) {
//添加需要被懒加载触发的方法,内部使用一个map进行维护ResultLoaderMap.class
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERRED;
} else {
value = resultLoader.loadResult();
}
}
}
return value;
}
lazyLoader.addLoader(property, metaResultObject, resultLoader)该操作就将我们的信息进行一个绑定操作。维护在ResultLoaderMap.class对象当中。
具体关注 lazyLoader.addLoader(property, metaResultObject, resultLoader)
private final Map<String, LoadPair> loaderMap = new HashMap<>();
public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {
String upperFirst = getUppercaseFirstProperty(property);
if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) {
throw new ExecutorException("Nested lazy loaded result property '" + property
+ "' for query id '" + resultLoader.mappedStatement.getId()
+ " already exists in the result map. The leftmost property of all lazy loaded properties must be unique within a result map.");
}
loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));
}
这里我们发现将延迟加载的属性作为key和具体的延迟加载信息再次封装成LoadPair.class对象进行一个维护绑定。在后期我们进行一个延迟加载的时候,执行方法就是拿根据属性获取相应的具体实现操作的执行器。LoadPair.class类是ResultLoaderMap.class内部维护的静态内部类。具体操作都让LoadPair.class进行一个操作,提升了一个安全性。好了上述我们已经进行了一个延迟加载的一个原理讲解,其实到这一步已经是完成了一个从返回结果集对象的一个代理,和内部需要被延迟加载属性的信息绑定,其实已经完成。为了让大家更好的了解到执行的全部流程,下面就来讲解执行延迟加载的具体过程。
我们做一个测试:
注意当前这个user对象已经是一个代理对象,上面我也进行了一个详细的讲解,在执行代理方法的时候会先执行代理执行器的方法。
@Override
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
System.out.println("------------------------------------------------------------------------懒加载触发");
final String methodName = method.getName();
boolean state = false;
try {
synchronized (lazyLoader) {
System.out.println("执行方法名称为:"+methodName);
if (WRITE_REPLACE_METHOD.equals(methodName)) {
Object original;
if (constructorArgTypes.isEmpty()) {
original = objectFactory.create(type);
} else {
original = objectFactory.create(type, constructorArgTypes, constructorArgs);
}
PropertyCopier.copyBeanProperties(type, enhanced, original);
if (lazyLoader.size() > 0) {
return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
} else {
return original;
}
} else {
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
System.out.println("正在准备执行延迟加载----------->");
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
System.out.println("第一个if判断");
lazyLoader.loadAll();
} else if (PropertyNamer.isSetter(methodName)) {
System.out.println("第二个if判断");
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
} else if (PropertyNamer.isGetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
//判断延迟加载里面是否存在该苏醒,其实就是我们上述在ResultLoaderMap内部维护的那个map,就是简单的看是否存在当前的key,如果存在就是需要延迟加载的我们就进行一个加载查询
if (lazyLoader.hasLoader(property)) {
state = true;
//如果是我们需要代理执行的方法就会进行一个执行,这里我说明下这里所有的set和get方法都会触发该方法。所以我们上述会进行一个判断
lazyLoader.load(property);
}
}
}
}
}
if (state){
System.out.println("执行数据来了,正在执行");
Object invoke = methodProxy.invoke(enhanced, args);
System.out.println("结果数据为:"+invoke);
}
Object invoke = methodProxy.invoke(enhanced, args);
return invoke;
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
下面就会使用到LoadPair.class做操作,上面有认真阅读的同学应该知道map里面维护什么。
private final Map<String, LoadPair> loaderMap = new HashMap<>();
public boolean load(String property) throws SQLException {
LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
if (pair != null) {
pair.load();
return true;
}
return false;
}
这里我们重点关注pair.load();方法的一个执行调用,这里要注意下,这里我省略的都是一些参数验证的一些代码,如果一些参数为空的话就是抛出异常。
public void load(final Object userObject) throws SQLException {
if (this.metaResultObject == null || this.resultLoader == null) {
......省略
final Configuration config = this.getConfiguration();
final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
......省略
this.metaResultObject = config.newMetaObject(userObject);
this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
metaResultObject.getSetterType(this.property), null, null);
}
......省略
if (this.serializationCheck == null) {
final ResultLoader old = this.resultLoader;
this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
}
this.metaResultObject.setValue(property, this.resultLoader.loadResult());
}
这里其中最为重要的就是this.metaResultObject.setValue(property, this.resultLoader.loadResult());
方法,上述都是在获取相应的组件,都为一些配置信息,为我们做一些相关验证作用,这里最为核心,也是最为重要的就是this.resultLoader.loadResult()这也是核心获取值的方法。
public ResultLoader(Configuration config, Executor executor, MappedStatement mappedStatement, Object parameterObject, Class<?> targetType, CacheKey cacheKey, BoundSql boundSql) {
//全局核心配置类信息
this.configuration = config;
//执行器
this.executor = executor;
//语句映射器
this.mappedStatement = mappedStatement;
//参数信息
this.parameterObject = parameterObject;
//需要映射的类型信息,就是延迟加载需要封装的的类型
this.targetType = targetType;
//对象工厂
this.objectFactory = configuration.getObjectFactory();
//缓存key
this.cacheKey = cacheKey;
//延迟加载需要执行sql语句
this.boundSql = boundSql;
//创建结果执行器
this.resultExtractor = new ResultExtractor(configuration, objectFactory);
//线程id
this.creatorThreadId = Thread.currentThread().getId();
}
public Object loadResult() throws SQLException {
List<Object> list = selectList();
//结果集映射对象的属性信息,就是将结果集数据封装到延迟加载对象里面
resultObject = resultExtractor.extractObjectFromList(list, targetType);
return resultObject;
}
相信阅读上面内容比较认真的都会知道这一部分这些构造参数就是需要被延迟加载的一些类型信息已经执行语句,对应执行器。下面这一个步骤就是得到我们想要的结果集,如果对执行流程不是很了解的可以去观看我的另一篇博客,拦截器的执行原理,这里面很好的讲解了整个执行过程。
https://blog.csdn.net/m0_49516995/article/details/118273675?spm=1001.2014.3001.5501
private <E> List<E> selectList() throws SQLException {
//获取执行器
Executor localExecutor = executor;
if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
localExecutor = newExecutor();
}
try {
//执行查询得到结果集
return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
} finally {
if (localExecutor != executor) {
localExecutor.close(false);
}
}
}
具体请看:
好了上述就是延迟加载的一个执行流程的源码解析,希望能让大家对延迟加载的原理有个很好的认识。就是希望大家在阅读源码的时候都是带着遗憾去查看相应的源码,这也能更好的给大家带来不少的收获。如果有写的不对的地方希望大家能够指出。这些也是个人的一个理解,如果有不对的地方欢迎大家在评论区提出。谢谢大家。