2023-09-11  阅读(2)
原文作者:一直不懂 原文地址: https://blog.csdn.net/shenchaohao12321/article/details/79841046

上一篇我们讲解到mapperElement方法用来解析mapper的,我们以packae属性为例详细分析一下:

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            }
            ...
           }
           ...
         }
         ...
     }

取得mappers标签下的package标签的name属性,调用configuration.addMappers(mapperPackage)方法,

    public void addMappers(String packageName) {
        mapperRegistry.addMappers(packageName);
      }

我们看一下MapperRegistry的addMappers方法:

    public void addMappers(String packageName, Class<?> superType) {
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        for (Class<?> mapperClass : mapperSet) {
          addMapper(mapperClass);
        }
      }
    public void addMappers(String packageName) {
        addMappers(packageName, Object.class);
    }
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
          if (hasMapper(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            // It's important that the type is added before the parser is run
            // otherwise the binding may automatically be attempted by the
            // mapper parser. If the type is already known, it won't try.
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }

addMappers方法将包packageName下的所有类对象传入了addMapper方法,

addMapper方法将接口类对象放入一个map中,key为class对象value为new MapperProxyFactory(type),这个MapperProxyFactory就是为类对象产生代理对象的工厂。

    public class MapperProxyFactory<T> {
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
      public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
      public Class<T> getMapperInterface() {
        return mapperInterface;
      }
      public Map<Method, MapperMethod> getMethodCache() {
        return methodCache;
      }
      @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    }

可以看到MapperProxyFactory就是使用JDK的动态代理Proxy.newProxyInstance方法创建一个代理对象,动态代理需要的InvocationHandler对象就是

这个mapperProxy,这个mapperProxy对象包含了从外部传入的SqlSession对象,动态代理需要的接口和一个Map<Method, MapperMethod>。

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
          } else if (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
      }
  1. 如果调用代理对象方法是从Object继承的方法,直接调用父类的方法。
  2. 若果调用的方法是接口的静态抽象方法,以方法句柄的方式调用方法,方法句柄比反射拥有更好的性能
  3. 否则包装一个MapperMethod对象然后调用execute方法,在构造MapperMethod对象的时候会对其缓存避免重复调用此方法时重新构造MapperMethod
    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;
      }

到现在为止我们知道调用Mapper对象的方法实际就是调用apperMethodM对象的execute方法就可以了,后面分析完mapper的xml解析后再详细看一下MapperMethod,因为MapperMethod会用到解析mapper的xml文件解析出来的MappedStatement,毕竟调用Mapper接口的代理对象的方法才会出发execute方法,那时Mapper映射文件早已经解析好放入Configuration中了。
再addMapper方法中下面这两句代码就是解析映射文件的了。
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);

parser.parse();

    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
          loadXmlResource();
          configuration.addLoadedResource(resource);
          assistant.setCurrentNamespace(type.getName());
          parseCache();
          parseCacheRef();
          Method[] methods = type.getMethods();
          for (Method method : methods) {
            try {
              // issue #237
              if (!method.isBridge()) {
                parseStatement(method);
              }
            } catch (IncompleteElementException e) {
              configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
          }
        }
        parsePendingMethods();
      }

接下来逐步分析一下parse方法中的关键步骤。

1 loadXmlResource

    private void loadXmlResource() {
        if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
          String xmlResource = type.getName().replace('.', '/') + ".xml";
          InputStream inputStream = null;
          try {
            inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
          } catch (IOException e) {
            // ignore, resource is not required
          }
          if (inputStream != null) {
            XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
            xmlParser.parse();
          }
        }
      }

XMLMapperBuilder从名字可以看出是准们解析xml映射文件的,BaseBuilder的子类,根据类名定位到xml文件,调用xmlParser.parse()方法解析。

1.1 xmlParser.parse()

    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {//避免重复解析
          configurationElement(parser.evalNode("/mapper"));//画重点
          configuration.addLoadedResource(resource);    //解析过了
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
      }

1.1.1 configurationElement

    private void configurationElement(XNode context) {
        try {
          String namespace = context.getStringAttribute("namespace");
          if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          builderAssistant.setCurrentNamespace(namespace);
          cacheRefElement(context.evalNode("cache-ref"));//共用别的namespace的缓存实现,也就是cache-ref属性指定的映射文件中<cache>的实现类。
          cacheElement(context.evalNode("cache"));
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          sqlElement(context.evalNodes("/mapper/sql"));
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
      }

1.1.1.1 cacheElement

    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);
          Properties props = context.getChildrenAsProperties();
          builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }
      }

type就是我们在标签type属性指定的类名,要求实现Cache接口,如果type没有指定就用默认的PERPETUAL(早已经注册过的别名的PerpetualCache),一个永不过期的缓存实现内部就是一个HashMap
eviction属性指定缓存过期的淘汰方式也是一个Cache实现,需要包装type的实现,所以是一个包装者模式的应用,默认为LRU(早已经注册过的别名的LruCache)最近最少使用到的先淘汰。
builderAssistant.useNewCache内部会创建一个CacheBuilder来设置上面解析出来的属性,通过反射调用Cache实现类的带有一个String构造参数的构造方法将namespace传入实例化我们的type。

所以我们编写自己的缓存类时要注意这一点。

    public Cache build() {
        setDefaultImplementations();
        Cache cache = newBaseCacheInstance(implementation, id);//id就是namespace
        setCacheProperties(cache);
        // issue #352, do not apply decorators to custom caches
        if (PerpetualCache.class.equals(cache.getClass())) {
          for (Class<? extends Cache> decorator : decorators) {
            cache = newCacheDecoratorInstance(decorator, cache);//eviction装饰type
            setCacheProperties(cache);
          }
          cache = setStandardDecorators(cache);
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
          cache = new LoggingCache(cache);
        }
        return cache;
      }
    private Constructor<? extends Cache> getBaseCacheConstructor(Class<? extends Cache> cacheClass) {
        try {
          return cacheClass.getConstructor(String.class);
        } catch (Exception e) {
          throw new CacheException("Invalid base cache implementation (" + cacheClass + ").  " +
              "Base cache implementations must have a constructor that takes a String id as a parameter.  Cause: " + e, e);
        }
      }

1.1.1.2 parameterMapElement parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。

1.1.1.3 resultMapElement

    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
        String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
        String type = resultMapNode.getStringAttribute("type",
            resultMapNode.getStringAttribute("ofType",
                resultMapNode.getStringAttribute("resultType",
                    resultMapNode.getStringAttribute("javaType"))));
        String extend = resultMapNode.getStringAttribute("extends");
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
        Class<?> typeClass = resolveClass(type);
        Discriminator discriminator = null;
        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
        resultMappings.addAll(additionalResultMappings);
        List<XNode> resultChildren = resultMapNode.getChildren();
        for (XNode resultChild : resultChildren) {
          if ("constructor".equals(resultChild.getName())) {
            processConstructorElement(resultChild, typeClass, resultMappings);
          } else if ("discriminator".equals(resultChild.getName())) {
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
          } else {
            List<ResultFlag> flags = new ArrayList<ResultFlag>();
            if ("id".equals(resultChild.getName())) {
              flags.add(ResultFlag.ID);
            }
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
          }
        }
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
        try {
          return resultMapResolver.resolve();
        } catch (IncompleteElementException  e) {
          configuration.addIncompleteResultMap(resultMapResolver);
          throw e;
        }
      }

大体意思就是将resultMap标签转换成ResultMap对象,resultMap标签的子标签result转换成ResultMapping对象,如果没有指定result的javaType,Mybatis就会根据property和type算出实际的javaType。最终将ResultMap存入Configuration的resultMaps(一个HashMap)中。

1.1.1.4 sqlElement

存储sql到Configuration.sqlFragments供引用

    private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
        for (XNode context : list) {
          String databaseId = context.getStringAttribute("databaseId");
          String id = context.getStringAttribute("id");
          id = builderAssistant.applyCurrentNamespace(id, false);
          if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
            sqlFragments.put(id, context);//存储sql到Configuration.sqlFragments
          }
        }
      }

1.1.1.5 buildStatementFromContext

    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
          }
        }
      }

XMLStatementBuilder看名字解析...标签的。

    public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        ... //获取属性值 太长了 省略了
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
        // Include Fragments before parsing
        XMLIncludeTransformer 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))
              ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
    
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }

注释一:
如果包含则通过refid去configuration找到对应的,此步骤可以根据configuration的variables动态替换refid中${..}的内容
然后替换的文本中的${..}用variables,将替换后的文本插入到当前节点中替换掉原有的
注释二:

langDriver.createSqlSource中通过XMLScriptBuilder将node节点转换为SqlSource。我们着重分析一下XMLScriptBuilder

    public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
        super(configuration);
        this.context = context;
        this.parameterType = parameterType;
        initNodeHandlerMap();
      }
      private void initNodeHandlerMap() {
        nodeHandlerMap.put("trim", new TrimHandler());
        nodeHandlerMap.put("where", new WhereHandler());
        nodeHandlerMap.put("set", new SetHandler());
        nodeHandlerMap.put("foreach", new ForEachHandler());
        nodeHandlerMap.put("if", new IfHandler());
        nodeHandlerMap.put("choose", new ChooseHandler());
        nodeHandlerMap.put("when", new IfHandler());
        nodeHandlerMap.put("otherwise", new OtherwiseHandler());
        nodeHandlerMap.put("bind", new BindHandler());
      }

在构造函数中,注册了9种NodeHandler,每一种NodeHandler负责处理一种动态标签如WhereHandler处理标签。

    protected MixedSqlNode parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList<SqlNode>();
        NodeList children = node.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
          XNode child = node.newXNode(children.item(i));
          if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
            String data = child.getStringBody("");
            TextSqlNode textSqlNode = new TextSqlNode(data);
            if (textSqlNode.isDynamic()) {
              contents.add(textSqlNode);
              isDynamic = true;
            } else {
              contents.add(new StaticTextSqlNode(data));
            }
          } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
            String nodeName = child.getNode().getNodeName();
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) {
              throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            handler.handleNode(child, contents);
            isDynamic = true;
          }
        }
        return new MixedSqlNode(contents);
      }

parseDynamicTags方法负责将一个节点(如等标签解析出来的一系列对象组拼起来形成一个MappedStatement对象放入configuration中。

1.1.2 bindMapperForNamespace

    private void bindMapperForNamespace() {
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
          Class<?> boundType = null;
          try {
            boundType = Resources.classForName(namespace);
          } catch (ClassNotFoundException e) {
            //ignore, bound type is not required
          }
          if (boundType != null) {
            if (!configuration.hasMapper(boundType)) {
              // Spring may not know the real resource name so we set a flag
              // to prevent loading again this resource from the mapper interface
              // look at MapperAnnotationBuilder#loadXmlResource
              configuration.addLoadedResource("namespace:" + namespace);
              configuration.addMapper(boundType);
            }
          }
        }
      }

对于那些以xml文件为入口而非class或package为入口的也就是下配置的是resource属性的,会通过namespace获取到MapperClass放入configuration,所以对于这种形式配置无需xml和Mapper放在同一目录下。

1.1.3 parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();

分别为因:

  1. resultMap的extends的没有解析,等待其余resultMap都解析完毕再次调用ResultMapResolver的resolve方法解析。此时若从configuration还得不到id为extends的ResultMap则忽略,因为parsePendingResultMaps中捕获了IncompleteElementException并啥也不做。
  2. refId引用的还未解析缓存下来,等到解析完毕再次解析。
  3. Cache-ref为解析完被耽误的MappedStatement再次解析。

2 parseCache();parseCacheRef();

    解析标记在Mapper类上的@CacheNamespace和@CacheNamespaceRef,此处作用于xml映射文件的<cache>作用一样,如果两处都使用了则注解会覆盖xml映射文件。
    private void parseCache() {
        CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
        if (cacheDomain != null) {
          Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
          Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
          Properties props = convertToProperties(cacheDomain.properties());
          assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
        }
      }

3 parseStatement(method)方法与XMLStatementBuilder的作用一样,XMLStatementBuilder转换映射文件到MappedStatement,这个方法是处理Method以及其上的Mybatis注解。


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文