• MyBatis源码解读(二)


    MyBatis缓存

    如果一级、二级缓存同时开启的话。会先去查询二级缓存再去查询一级缓存。

    二级缓存的开启配置我们就不多做介绍,我们主要进行二级缓存开启后的执行流程。

    1、开启二级缓存

    以下代码就是我们解析配置文件的时候进行解析的代码。会解析到我们开启二级缓存的配置。

    1、
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        try {
            //委托XMLConfigBuilder来解析xml文件,并构建
          XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
          return build(parser.parse());
        } catch (Exception e) {
            //这里是捕获异常,包装成自己的异常并抛出的idiom?,最后还要reset ErrorContext
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            reader.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }
    2、//追parser.parse()中的mapperElement(root.evalNode("mappers"));
      //因为这个方法解析的是我们的Mapper配置文件。
        private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              //10.4自动扫描包下所有映射器
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              if (resource != null && url == null && mapperClass == null) {
                //10.1使用类路径
                ErrorContext.instance().resource(resource);
                InputStream inputStream = Resources.getResourceAsStream(resource);
                //映射器比较复杂,调用XMLMapperBuilder
                //注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                //这个方法就开始解析映射配置文件的。
                mapperParser.parse();
              } else if (resource == null && url != null && mapperClass == null) {
                //10.2使用绝对url路径
                ErrorContext.instance().resource(url);
                InputStream inputStream = Resources.getUrlAsStream(url);
                //映射器比较复杂,调用XMLMapperBuilder
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url == null && mapperClass != null) {
                //10.3使用java类名
                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.");
              }
            }
          }
        }
      }
    
    3、//mapperParser.parse();
        //解析
      public void parse() {
        //如果没有加载过再加载,防止重复加载
        if (!configuration.isResourceLoaded(resource)) {
          //开始解析mapper根节点
          configurationElement(parser.evalNode("/mapper"));
          //标记一下,已经加载过了
          configuration.addLoadedResource(resource);
          //绑定映射器到namespace
          bindMapperForNamespace();
        }
    
        //还有没解析完的东东这里接着解析?  
        parsePendingResultMaps();
        parsePendingChacheRefs();
        parsePendingStatements();
      }
    4、//configurationElement(parser.evalNode("/mapper"));
         private void configurationElement(XNode context) {
        try {
          //1.配置namespace
          String namespace = context.getStringAttribute("namespace");
          if (namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          builderAssistant.setCurrentNamespace(namespace);
          //2.配置cache-ref
          cacheRefElement(context.evalNode("cache-ref"));
          //3.配置cache 这就是我们要找的Cache。
          cacheElement(context.evalNode("cache"));
          //4.配置parameterMap(已经废弃,老式风格的参数映射)
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          //5.配置resultMap(高级功能)
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          //6.配置sql(定义可重用的 SQL 代码段)
          sqlElement(context.evalNodes("/mapper/sql"));
          //7.配置select|insert|update|delete TODO
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
        }
      }
    5、//终于找到了我们想要的Cache节点解析方法。
        private void cacheElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type", "PERPETUAL");
          Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
          String eviction = context.getStringAttribute("eviction", "LRU");
          Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
          Long flushInterval = context.getLongAttribute("flushInterval");
          Integer size = context.getIntAttribute("size");
          boolean readWrite = !context.getBooleanAttribute("readOnly", false);
          boolean blocking = context.getBooleanAttribute("blocking", false);
          //读入额外的配置信息,易于第三方的缓存扩展,例:
    //    <cache type="com.domain.something.MyCustomCache">
    //      <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
    //    </cache>
          Properties props = context.getChildrenAsProperties();
          //调用builderAssistant.useNewCache
          builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }
      }
    6、//解析完我们配置的参数便调用了下面:builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
     public Cache useNewCache(Class<? extends Cache> typeClass,
          Class<? extends Cache> evictionClass,
          Long flushInterval,
          Integer size,
          boolean readWrite,
          boolean blocking,
          Properties props) {
          //这里面又判断了一下是否为null就用默认值,有点和XMLMapperBuilder.cacheElement逻辑重复了
        typeClass = valueOrDefault(typeClass, PerpetualCache.class);
        evictionClass = valueOrDefault(evictionClass, LruCache.class);
        //调用CacheBuilder构建cache,id=currentNamespace
        Cache cache = new CacheBuilder(currentNamespace)
            .implementation(typeClass)
            .addDecorator(evictionClass)
            .clearInterval(flushInterval)
            .size(size)
            .readWrite(readWrite)
            .blocking(blocking)
            .properties(props)
            .build();
        //将我们的缓存配置加入到了核心配置类当中
        configuration.addCache(cache);
        //当前的缓存
        currentCache = cache;
        return cache;
      }
    
    

    经过上面源码流程,我们得知的缓存对象的创建。但是还没完,还有一部分select、update、delete标签中的属性要解析:我们要将创建的缓存对象放入到Statement中去。

    1、
    private void configurationElement(XNode context) {
        try {
          //1.配置namespace
          String namespace = context.getStringAttribute("namespace");
          if (namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          builderAssistant.setCurrentNamespace(namespace);
          //2.配置cache-ref
          cacheRefElement(context.evalNode("cache-ref"));
          //3.配置cache
          cacheElement(context.evalNode("cache"));
          //4.配置parameterMap(已经废弃,老式风格的参数映射)
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          //5.配置resultMap(高级功能)
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          //6.配置sql(定义可重用的 SQL 代码段)
          sqlElement(context.evalNodes("/mapper/sql"));
          //7.配置select|insert|update|delete TODO
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
        }
      }
    
    2、// buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        
         private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
          //构建所有语句,一个mapper下可以有很多select
          //语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
              //核心XMLStatementBuilder.parseStatementNode
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
              //如果出现SQL语句不完整,把它记下来,塞到configuration去
            configuration.addIncompleteStatement(statementParser);
          }
        }
      }
    3、//statementParser.parseStatementNode();主要是去解析select|insert|update|delete这些标签,这里面也涉及到一些缓存配置。
        public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        //如果databaseId不匹配,退出
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
          return;
        }
    
        //暗示驱动程序每次批量返回的结果行数
        Integer fetchSize = context.getIntAttribute("fetchSize");
        //超时时间
        Integer timeout = context.getIntAttribute("timeout");
        //引用外部 parameterMap,已废弃
        String parameterMap = context.getStringAttribute("parameterMap");
        //参数类型
        String parameterType = context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = resolveClass(parameterType);
        //引用外部的 resultMap(高级功能)
        String resultMap = context.getStringAttribute("resultMap");
        //结果类型
        String resultType = context.getStringAttribute("resultType");
        //脚本语言,mybatis3.2的新功能
        String lang = context.getStringAttribute("lang");
        //得到语言驱动
        LanguageDriver langDriver = getLanguageDriver(lang);
    
        Class<?> resultTypeClass = resolveClass(resultType);
        //结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种
        String resultSetType = context.getStringAttribute("resultSetType");
        //语句类型, STATEMENT|PREPARED|CALLABLE 的一种
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    
        //获取命令类型(select|insert|update|delete)
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        //是否要缓存select结果
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        //仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。
        //这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
        // Include Fragments before parsing
        //解析之前先解析<include>SQL片段
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        // Parse selectKey after includes and remove them.
        //解析之前先解析<selectKey>
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
        
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        //解析成SqlSource,一般是DynamicSqlSource
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        String resultSets = context.getStringAttribute("resultSets");
        //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
        String keyProperty = context.getStringAttribute("keyProperty");
        //(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
        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();
        }
    
    	//又去调助手类,通过该方法创建MappedStatement
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered, 
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }  
    
    4、/**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加上namespace前缀
        id = applyCurrentNamespace(id, false);
        //是否是select语句
        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);
    
        //1.参数映射
        setStatementParameterMap(parameterMap, parameterType, statementBuilder);
        //2.结果映射
        setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
        //将之前封装好的缓存对象currentCache设置进去。
        setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);
    
        MappedStatement statement = statementBuilder.build();
        //建造好调用configuration.addMappedStatement
        configuration.addMappedStatement(statement);
        return statement;
      }
    
        
    

    2、二级缓存执行流程

    2.1

    既然二级缓存都已经配置好了,那我们看看二级缓存是怎样使用的呐。

    我们直接在selectList入手:

    //核心selectList
      @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          //根据statement id找到对应的MappedStatement
          MappedStatement ms = configuration.getMappedStatement(statement);
          //转而用执行器来查询结果,注意这里传入的ResultHandler是null,注意这个执行器,如果开启了缓存,他就是CachingExecutor
          return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    
    

    我们继续追一下query方法:

    @Override
      public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
          throws SQLException {
        //得到缓存对象,我们以前准备好存进去的。  
        Cache cache = ms.getCache();
        //默认情况下是没有开启缓存的(二级缓存).要开启二级缓存,你需要在你的 SQL 映射文件中添加一行: <cache/>
        //简单的说,就是先查CacheKey,查不到再委托给实际的执行器去查
        if (cache != null) {
          flushCacheIfRequired(ms);
          if (ms.isUseCache() && resultHandler == null) {
            ensureNoOutParams(ms, parameterObject, boundSql);
            @SuppressWarnings("unchecked")
            //从二级缓存中获取对应结果。
            List<E> list = (List<E>) tcm.getObject(cache, key);
            if (list == null) {
               //这个方法先去执行一级缓存,delegate是simpleExecutor执行器。 
              list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
          }
        }
        return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }
    

    2.2

    既然已经知道了执行流程,接下来看下二级缓存具体的存取操作:

     //得到某个TransactionalCache的值
      public Object getObject(Cache cache, CacheKey key) {
        return getTransactionalCache(cache).getObject(key);
      }
    
    //getTransactionalCache(cache)
      //管理了许多TransactionalCache
    private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
    
    private TransactionalCache getTransactionalCache(Cache cache) {
        TransactionalCache txCache = transactionalCaches.get(cache);
        if (txCache == null) {
          txCache = new TransactionalCache(cache);
          transactionalCaches.put(cache, txCache);
        }
        return txCache;
      }
      
    //getObject(key) 通过上面得到的TransactionalCache对象通过key去获取缓存。
     @Override
      public Object getObject(Object key) {
        Object object = delegate.getObject(key);
        if (object == null) {
          entriesMissedInCache.add(key);
        }
        if (clearOnCommit) {
          return null;
        } else {
          return object;
        }
      }
    
    //Cache是一个接口,不同的缓存类型对象要去实现它。
    

    2.3

    不过还没完,以目前所说的并不能解决事物问题,我们去看看事物问题是如何解决的?

      //缓存对象,里面存着缓存。
      private Cache delegate;
      //commit时要不要清缓存
      private boolean clearOnCommit;
      //事物被提交前,所有从数据库中查询的结果缓存在这里面
      private Map<Object, Object> entriesToAddOnCommit;
      //事物被提交前,当缓存未命中是,CacheKey被存在这个集合中
      private Set<Object> entriesMissedInCache;
    
    //put
     @Override
      public void putObject(Object key, Object object) {
        //添加时不直接放入缓存对象。
        entriesToAddOnCommit.put(key, object);
      }
    //commit
     public void commit() {
        //判断commit的时候判断要不要删除以前的缓存
        if (clearOnCommit) {
          delegate.clear();
        }
        flushPendingEntries();
        reset();
      }
    1、
    private void flushPendingEntries() {
        for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
          //开始存放到真正的缓存当中,所以说不commit根本查不到
          delegate.putObject(entry.getKey(), entry.getValue());
        }
        for (Object entry : entriesMissedInCache) {
          if (!entriesToAddOnCommit.containsKey(entry)) {
            //没命中且不在entriesToAddOnCommit,就是两个缓存都没有,直接赋值为空
            delegate.putObject(entry, null);
          }
        }
      }
    
      private void unlockMissedEntries() {
        for (Object entry : entriesMissedInCache) {
          delegate.putObject(entry, null);
        }
      }
    }
    2、  private void reset() {
        clearOnCommit = false;
        entriesToAddOnCommit.clear();
        entriesMissedInCache.clear();
      }
    
    
    

    MyBatis延迟加载

    延迟加载就是当数据使用的时候再加载进来。例子就是用户关联订单,我们在查询时,首先查询出用户数据,当用到订单数据时再去加载。

    同样,延迟加载配置开启就不做过多介绍,我们来探讨它的部分源码。

    延迟加载的原理就是实现目标对象的代理对象。

    UserDao userDao = sqlSession.getMapper(UserDao.class);

    上图返回的userDao对象为代理对象。我们调用它关联的数据对象的get方法时就会进入拦截器,判断是否需要延迟加载,所以就进入了准备好的SQL将关联对象查出来。

    1、代理对象的创建

    下面展示为结果对象创建代理对象的代码。

    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();
        final List<Object> constructorArgs = new ArrayList<Object>();
        //创建映射后的结果对象
        final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
          //如果有内嵌查询,并且开启了延迟加载,则创建结果对象的的代理对象
          //得到我们再配置文件ResultMapping中配置的属性
          //每一个ResultMapping对应xml文件中的一个配置。
          final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
          for (ResultMapping propertyMapping : propertyMappings) {
            //判断ResultMapping里面子标签有没有配置延迟加载
            if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
              //TODO 使用代理(cglib/javaassist)去创建队里对象。
              return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
            }
          }
        }
        return resultObject;
      }
    //默认使用Javassist
    //protected ProxyFactory proxyFactory = new JavassistProxyFactory();
    

    既然找到的代理工厂我们便去看看代理工厂创建代理对象的代码。

    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
          final Class<?> type = target.getClass();
        //代理类执行时就会去执行它的invoke
          EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
        //生成代理方法
          Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
          PropertyCopier.copyBeanProperties(type, target, enhanced);
          return enhanced;
        }
    

    2、代理对象的执行

    代理对象已经创建完成,我们就可以去看看代理对象的执行过程了。

    @Override
        public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
          final String methodName = method.getName();
          try {
            synchronized (lazyLoader) {
              if (WRITE_REPLACE_METHOD.equals(methodName)) {
                Object original = null;
                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)) {
                  //加载所有延迟加载的属性。
                  if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                    lazyLoader.loadAll();
                    //判断必须以get|set|is开头。
                  } else if (PropertyNamer.isProperty(methodName)) {
                    //根据方法名替换为属性。
                    final String property = PropertyNamer.methodToProperty(methodName);
                    //判断这个属性是否是延迟加载的属性。
                    if (lazyLoader.hasLoader(property)) {
                      //根据延迟属性进行加载
                      lazyLoader.load(property);
                    }
                  }
                }
              }
            }
                  //执行原方法
            return methodProxy.invoke(enhanced, args);
          } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
    
  • 相关阅读:
    编译duilib遇到问题:Error C2371 "IDispatchEx重定义;不同的基类型"
    初尝DirectUI
    ms-onlinetest-question02
    ms-onlinetest-question3
    都是申请空间后不赋初值惹的祸..
    CString接受返回的char*字符串后成为乱码
    CL.exe @C:Users upAppDataLocalTemp mpc8fc399365e34f739eff6191a0c9acde.rsp”。存储控制块地址无效
    Visual Studio Ultimate 2012 静态激活密钥
    如何写入和获取软件的版本信息(VS环境下)
    mt.exe : general error c101008a: Failed to save the updated manifest to the file "DebugResource.dll.embed.manifest". Bpo
  • 原文地址:https://www.cnblogs.com/kenai/p/14918499.html
Copyright © 2020-2023  润新知