【Mybatis源码分析】02-Mapper映射的解析过程

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

上一篇我们讲解到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方法负责将一个节点(如)下的子节点包装进一个MixedSqlNode对象中。在xml映射文件中,每一个元素都是一种SqlNode,MixedSqlNode将所有子节点统一管理,它们都继承了SqlNode,所以说MixedSqlNode是一个委派模式。      public class MixedSqlNode implements SqlNode {       private final List<SqlNode> contents;       public MixedSqlNode(List<SqlNode> contents) {         this.contents = contents;       }       @Override       public boolean apply(DynamicContext context) {         for (SqlNode sqlNode : contents) {           sqlNode.apply(context);         }         return true;       }     }  isDynamic = true;有两种情况,一种是TextSqlNode含有${..}一种是含有这种元素。根据isDynamic返回分为DynamicSqlSource和RawSqlSource。     public SqlSource parseScriptNode() {         MixedSqlNode rootSqlNode = parseDynamicTags(context);         SqlSource sqlSource = null;         if (isDynamic) {           sqlSource = new DynamicSqlSource(configuration, rootSqlNode);         } else {           sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);         }         return sqlSource;       }  最后通过builderAssistant对象将以上从等标签解析出来的一系列对象组拼起来形成一个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注解。

  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值