mybatis源码解读篇

本文深入解析了Mybatis的初始化过程,包括配置文件的解析、别名处理、mapper方法注册、SQL执行流程,以及缓存和插件的配置。重点介绍了Mapper的执行机制,从SQLSession获取mapper到最终结果映射的全过程。
摘要由CSDN通过智能技术生成

mybatis初始化原理

传统的查数据库方式,没有现在的方便;

我简单写一个以回顾一下这些东西。

 private static final String URL = "jdbc:mysql://localhost:3306/ali?useSSl=false&zeroDateTimeBehavior=CONVERT_TO_NULL&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";

    private static final String USERNAME = "root";

    private static final String PAASWORD = "root";

    private static final Logger logger = LoggerFactory.getLogger(TraditionConnection.class);

    public static Connection connection() throws ClassNotFoundException, SQLException {
        Class.forName(Driver.class.getName());
        return DriverManager.getConnection(URL, USERNAME, PAASWORD);
    }

    public static  List<Map<String, Object>> menuQuery(String sql, Object... args) throws SQLException, ClassNotFoundException {
        logger.info("执行的sql: {}", sql);
        logger.info("参数:{}", args);
        Connection connection = connection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0; i < args.length; i++) {
            preparedStatement.setObject(i + 1, args[i]);
        }
        List<Map<String, Object>> result = new ArrayList<>();
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            Map<String, Object> obj = new HashMap<>();
            obj.put("name", resultSet.getString("name"));
            obj.put("id", resultSet.getInt("id"));
            result.add(obj);
        }
        close(connection, preparedStatement, resultSet);
        logger.info("结果数据条数:{}", result.size());
        return result;
    }

    public static void close(Connection connection, Statement statement, ResultSet resultSet) throws SQLException {
        if (resultSet != null) {
            resultSet.close();
        }
        if (statement != null) {
            statement.close();
        }
        if (connection != null) {
            connection.close();
        }


    }
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        String query = "select * from menu where name = ?";
        List<Map<String, Object>> result = menuQuery(query, "菜单测试");
        System.out.println("结果: " + JSON.toJSONString(result));
    }

传统方式需要加载驱动,声明sql等操作,而现在的框架都像上面一样进行了各种封装,使得我们使用更加方便,下面来看看mybatis的方式。(只看主要方法)

mybatis初始化的入口是sqlSessionFactory.build();

InputStream is = new FileInputStream(ResourceUtils.getFile("classpath:Mybaties.xml"));
SqlSessionFactoryBuilder s = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = s.build(is);
// xml解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 通过parse解析xml配置
return build(parser.parse());

进入parser.parse()方法,里面找到parseConfiguration方法;

这个方法里的每一个调用对应配置文件下的每一个节点,然后再由每一个方法去解析对应的节点,先不说设计模式,这样的作法比较美观、简洁、易读,平常开发的时候,也应该像这种;比如写serviceImpl时,虽然功能逻辑,条理清晰,顺着写也没毛病,代码行数也差不多七八十至百行,但这样的代码可以更简介优化,看看有没有功能、逻辑,可以提取成一个方法,而在主方法里,只看到你哪一行代码做了什么,下面做了什么,整体逻辑更加清晰,在排错时也能更得心应手。

 private void parseConfiguration(XNode root) {
    try {
      // 读取配置节点(连接数据库的配置)
      propertiesElement(root.evalNode("properties"));
        // 配置运行参数
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
        // 配置类别名
      typeAliasesElement(root.evalNode("typeAliases"));
        // 配置插件
      pluginElement(root.evalNode("plugins"));
        // 查询到的结果映射到目标类可以由objectFactory执行
      objectFactoryElement(root.evalNode("objectFactory"));
        // 对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // 反射工厂
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // 运行环境配置
      environmentsElement(root.evalNode("environments"));
        // 数据库提供者
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // 类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
        // mapper类配置
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

这里主要讲解两个方法,其他比较简单

解析别名

mybatis可以配置别名,有两种方式:

  • 一种是XML的方式,通过typeAliaes下的typeAlias标签,
    <typeAliases>
        <typeAlias alias="isAlias" type="com.lry.ma.dao.MenuDao"/>
    </typeAliases>

标签还有package,这个是配置mapper所在的包路径,会将包路径下的所有mapper注册,不会添加别名

    <typeAliases>
        <package name="com.lry.ma.dao"/>
    </typeAliases>
  • 一种是对应mapper类上使用@Alias注解配置就行,
@Alias("menuAlias")
public interface MenuDao {
}

源码中,它先是解析了typeAliases

      typeAliasesElement(root.evalNode("typeAliases"));

然后传入方法,进行详细解析,里面会进行判断,如果不存在子标签就退出,如果有,就是上面说的两种情况,package标签和typeAlias标签,并且,解析方式也不一样

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
          // 扫描package标签下的对象
        if ("package".equals(child.getName())) {
            // 获取到package标签的name,也就是mapper的包名
          String typeAliasPackage = child.getStringAttribute("name");
            // 通过别名注册器,注册别名;;
            // 注意:里面注册前有一个判断,就是  !type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()
            // 所以,实际上,package标签不会注册别名
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
            // typeAlias 标签解析,按配置的每个对象添加别名
            // 它有两个属性,alias -》 别名,type -》 类全名
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
              // 注册别名:key-》别名,value -》mapper class
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

先看下package标签的解析:

image-20230820102321647

这里并不会将package标签下扫描到的class进行别名注册,因为,它有一个实质性的判断type.isInterface()mapper都是接口,所以这里过不了;

image-20230820103736373

然后再看typeAlias标签的解析:

image-20230820103936954

注册别名的两个方法:

方法一:以class注册,别名以类名,但如果类上有注解,则以注解为准

  public void registerAlias(Class<?> type) {
      // 以类名为别名
    String alias = type.getSimpleName();
      // 获取mapper类上是否有别名注解@Alias,有就以注解的为准
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    } 
      // 注册:其实就是一个HashMap,key -> alias ,value -> mapper class
      // 这个方法也是最底层的注册方法
    registerAlias(alias, type);
  }

方法二:指定别名,与类名,都以指定为准,不做其他操作;

最底层的注册方法

  public void registerAlias(String alias, Class<?> value) {
      // 别名不可能为空
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // 别名统一化
    String key = alias.toLowerCase(Locale.ENGLISH);
      // 不存在该别名,并且别名对应的class也不相等才能被注册;
      // 也就是说,如果出现别名一样的,那么就是后来者覆盖前者
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
      // key -> alias ,value -> mapper class
    TYPE_ALIASES.put(key, value);
  }

解析mapper的方法

image-20230820111500260

配置mappers标签可以有4种方式:

// 配置包路径,将路径下的mapper全都注册
<package name="com.lry.mybatis.dao"/>
// 配置mapper.xml
<mapper resource="mapper/MenuMapper.xml"/>
// 配置mapper类全名,如果这样配置,那么走的是解析注解sql的方式解析,不会解析到resource下的mapperxml,也就是说,这样配置后,mapper.xml会失效
<mapper class="com.lry.mybatis.dao.MenuMapper"/>
// 配置mapper.xml绝对路径
<mapper url="file:E:\Program File\work\workspace\lry\pro\mybatis\target\classes\mapper\MenuMapper.xml"/>

使用addMapper方法注册mapper时(这里的注册就是放到一个map中管理),都会经过一个方法hasMapper(type)判断是否存在相同的的mapper,已经注册过的mapper在注册,会抛出异常;

 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
            // mappers标签下,使用package标签的话,会扫描包路径下的文件,批量注册mapper,
          String mapperPackage = child.getStringAttribute("name");
            // 注册mapper,会判断是否当前mapper已经注册过
          configuration.addMappers(mapperPackage);
        } else {
            // resource下的相对路径
          String resource = child.getStringAttribute("resource");
            // 绝对路径
          String url = child.getStringAttribute("url");
            // 类全名
          String mapperClass = child.getStringAttribute("class");
            // 上面三个个属性只能配置一个,如下:if else ,每个分支只有一个是不为空的,其他的都为空
          if (resource != null && url == null && mapperClass == null) {
              // 第一种:解析mapper.xml
              // 这里是配置了resource ,相对路径解析mapper.xml文件
            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) {
              // 第二种:解析url
              // 这里是配置了URL,远程连接解析
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
              // 第三种:解析mapper class及sql注解,走这里不会解析resource下的mapper.xml
              // 这里是配置了class,接口信息解析(这里的逻辑和上面package标签里的一样)
            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.");
          }
        }
      }
    }
  }

解析注解sql方式

这里先看注解方式的解析(习惯用注解方式的Mapper);

解析注解sql方式的情况有:1. package标签注册mapper,2. mapper标签class属性不为空时

public <T> void addMapper(Class<T> type) {
    // 不是接口,则退出
    if (type.isInterface()) {
        // 判断当前的mapper是否注册过
        // 和别名不一样,出现重复的mapper.xml是不合理的,因为这可能导致业务逻辑的替换
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
          // key -> class, value -> mapper proxy factory(mapper代理工厂)
          // 这个map是作为mapper注册的容器
          // 之后我们getMapper,就是从这个knownMappers map中去的,取出后,再生成代理对象得到我们使用的mapper
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // 解析Mapper class
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

可以看到,它实例化了一个mapper解析类MapperAnnotationBuilder,它默认这次了sql注解:

image-20230820113549723

这里还不是最底层的东西,进入parser.parse()方法里面,有这么一段,它会先判断是否加载过这个类,没有才进行解析,毕竟解析也是消耗资源的,从逻辑上来说也是必须的。

 public void parse() {
    String resource = type.toString();
     // 判断是否加载过这个类
    if (!configuration.isResourceLoaded(resource)) {
        // 加载xml(加载mapper.xml)
        // 找  包名/类名.xml,如:com.lry.mybatis.dao.MenuMapper.xml
      loadXmlResource();
        // 添加到已加载的容器
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
        // 解析缓存注解
      parseCache();
      parseCacheRef();
        // 获取到接口方法
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
            // 这个isBridge请百度
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

加载mapper.xml

image-20230820113947124

type是我们定义的mapper类对象,type.getName()拿到的就是类全名,如:com.lry.mybatis.MenuMapper,那么这里的解析有可以理解为,当我们的mapper.xml是放在resources/mapper目录下的,那么这里的inputStream一定是null,因为它解析的路径是:resources/com/lry/mybatis/MenuMapper.xml,这就是默认的解析路径,即使你在方法上使用@Select配置了sql,在对应路径下也有xml配置,xml还是会被加载。

 private void loadXmlResource() {
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        // 读取的路径是mapper接口对应的包名下的xml,也是默认读取路径。
        // 如:com.lry.mybatis.dao.MenuMapper.xml
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
          // 如果在resource下有对应包名的xml,就会加载到,inputStream就不会为null
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
          // xml 解析,如果是配置xml sql,不是注解sql就走parse
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

那么既配置类注解sql,也配置了xml sql,会执行哪一个?

答案是:报异常!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0c8UXKoT-1692525879066)(E:/BaiduNetdiskWorkspace/%E5%BE%85%E5%8F%91%E5%B8%83%E6%96%87%E7%AB%A0/Typora/typora/images/image-20230820114600932.png)]

解析xml后,进入这个方法解析注解sql,然后在assistant.addMappedStatement()中注册方法时抛出异常;

void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      // 省略...
        // 
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }

这有个问题,就是它都把xml解析了,注解也解析了,然后在注册sql声明的时候抛异常,感觉多此一举啊,既然让使用者指定了注解解析方式,自己还加一个默认解析,然后又不给注册上去,不理解设计者是怎样考虑的。

解析mapper.xml

image-20230820115124445

这里有一部分和注解sql方式解析的方法loadXmlResource里有部分是一样的:

XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();

首先我们要知道,它怎么加载的mapper.xml

InputStream inputStream = Resources.getResourceAsStream(resource);

它和注解sql方式的不同,注解sql方式的因为没有指定mapper.xml的路径,所以以默认路径包名/类名.xml为路径查找,所以是找不到的,但是这里是人为的指定了路径,如mapper/menuDao.xml,那这是相对路径,但是这个相对路径它的默认基础路径(根目录)其实是resource目录:

image-20230820120404696

所以,在它通过XMLMapperBuilder解析xml时,就已经将mapper.xml读取出来了,也就是下面的resource属性:

public void parse() {
    // 判断是否加载过
    if (!configuration.isResourceLoaded(resource)) {
        // 解析mapper节点
      configurationElement(parser.evalNode("/mapper"));
        // 将已解析的xml文件添加记录
      configuration.addLoadedResource(resource);
        // 绑定命名空间
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

解析mapper节点

private void configurationElement(XNode context) {
    try {
        // 当前mapper.xml的命名空间,即对应的mapper类的类全名
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
        // builderAssistant就相当于当前读取的mapper节点的一个对象副本
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
        // 这个很少写,参数映射
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // 这个经常写,结果集映射
      resultMapElements(context.evalNodes("/mapper/resultMap"));
        // sql节点,通常会把一些通用的动态sql放到sql标签中,通过include标签静态引入
      sqlElement(context.evalNodes("/mapper/sql"));
        // 解析mapper方法
      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);
    }
  }

这个参数解析的不是很复杂,就解析了节点然后将解析后的结果放到parametermappings中,这里有一个typeHandler的东西,这个放后面讲,是个很有用的东西。

 @SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
解析resultMap标签

resultMapNode.getStringAttribute方法解析节点属性,默认值为null。该方法在解析时会被递归调用,所以它比较通用,其实标签属性也差不多。

 private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
     // 异常上下文ErrorContext,用于记录mybatis的错误信息,然后打印
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
     // 下面是解析resultmap属性,
    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");
     // 自动映射,当我们没有写resultMap这个标签映射字段时,mybatis会帮我们去做映射
    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 {
          // 解析result、collection标签
        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;
    }
  }

上面的代码就是解析类似下面的xml:

    <resultMap id="NodeDefStylePortMap" type="com.lry.mybatis.model.MenuBo" extends="MenuMap">
        <collection property="menus" javaType="java.util.ArrayList" ofType="com.lry.mybatis.model.Menu">
            <result property="id" column="id" jdbcType="INTEGER"/>
            <result property="name" column="name" jdbcType="VARCHAR"/>
        </collection>
        <discriminator javaType="">
            <case value=""></case>
        </discriminator>
    </resultMap>

然后是解析sql,其实这里也就是解析实例sql的标签属性,然后放到map里,没有其他解析。

image-20230820135736103

 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);
      }
    }
  }
解析方法标签

image-20230820135809214

image-20230820135823293

之后是解析mapper方法对应的标签(select、update、insert、delete);

进入buildStatementFromContext,往里走,就是下面方法,一连串的解析标签属性;

 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");
     // sql语句的类型;statementType 是声明类型,说明在下面
    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));
     // select 和其他语句不一样,所以这里会设定一个isSelect,分开解析
    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 parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 解析selectKey标签,并生成SelectKeyGenerator
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // 解析sql
    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);
     // 这里判断有没有keyGenerator,如果是有selectKey标签,解析的时候会生成一个selectGenerator 
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        // 没有selectKey标签,就解析useGeneratedKeys属性
      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);
  }

需要注意的几点:

  1. 上面有一个statementType这个是sql语句的类型,他有三个值:STATEMENT, PREPARED, CALLABLE;

STATEMENT:sql语句,字符串拼接,对应的是${}

PREPARED:预处理sql,preparedStatement 的默认类型,对应的是#{}

CALLABLE:存储过程,callableStatement

  1. insert返回id,有不同,mysql可以通过useGeneratedKeys可以返回自增id,也可以通过selectKey标签,Oracle就只能通过selectKey标签了,selectKey标签也和select标签一样,会添加到mapppedStatement里面(如下图);根据代码顺序,设置新增返回id的方式也有优先级,如果设置了selectKey标签,那么设置的useGeneratedKeys就会失效。

image-20201025160923653.png

  1. mapper的别名不区分大小写,在上面注册mapper时有提到,为了名称统一:alias.toLowerCase(Locale.ENGLISH); 他会把mapper注册到一个别名库中。

    image-20230820140848724

解析sql是下面这句,它这里解析会将#{}这部分替换成?,然后在后面执行的时候通过这个sqlsource 做预编译处理。

 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    if (isDynamic) {
        // 动态sql解析
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        // 静态sql
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

image-20230820142125669

这里它会区分是否是动态sql,即是否含有动态标签,如<if></if> 、<where></where> 、${}这些,#{}这个不是动态标签的标志;

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);
          // 这里进行区分动态sql
        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);
  }

解析完的sql,会把变量替换成占位符的方式,比如:select * from menu where id = #{id} -> select * from menu where id = ?

 public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

在解析xml后,后面还有一句:bindMapperForNamespace();这里的意思是字面意思,就是添加命名空间,之前解析的是xml,没有设计class,那么这里就是把class读出来,然后再解析一些mapper class,因为,Mybatis 支持xml 也支持注解sql,所以,在注册mapper时,都是需要解析mapper的。

image-20230820143744467

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
      // 命名空间为空,则跳过
    if (namespace != null) {
      Class<?> boundType = null;
      try {
          // 加载mapper class
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // 记录加载的mapper
          configuration.addLoadedResource("namespace:" + namespace);
            // 注册mapper class
          configuration.addMapper(boundType);
        }
      }
    }
  }

然后addMapper就和注解sql的解析方式是一样的方法

image-20230820144111847

最后后面还有3句parse的代码,这个是在做补足解析,意思就是在正常解析过程中,失败了,在这里还会做一次,也就是,重试一次;

image-20230820142814852

private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
      Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolve();
          iter.remove();
        } catch (IncompleteElementException e) {
          // ResultMap is still missing a resource...
        }
      }
    }
  }

mybatis执行流程

回忆一下,在上面初始化过程中,Mybatis注册了mapper:key -> mapper class , value -> mapper proxy factory(mapper代理工厂),所以底层应该是获取这个factory,然后通过factory进行创建代理类;

我们从getMapper方法进行跟踪

 MenuMapper ndao = getSqlSessionFactory().getMapper(MenuMapper.class);

1. 委托configuration获取mapper

注意,上面提过保存的是mapper class和factory,所以这个this (SQLSession)是做什么的,思考下?

return configuration.<T>getMapper(type, this);

2. configuration通过mapperRegistry(type,sqlSession)获取mapper

mapperRegistry.getMapper(type, sqlSession);

3. MapperRegistry.getMapper(type, sqlSession)

位置:org.apache.ibatis.binding.MapperRegistry#getMapper

这里也就是那个mapper注册类的缓存获取的逻辑;

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
     // 拿到ProxyFactory,这个是在初始化时创建添加的
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 通过Mapper proxy factory(mapper代理工厂),利用传入SQLSession创建代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

这里,mapper代理工厂,通过JDK动态代理,利用mapper接口,创建了一个代理对象,而代理的对象是MapperProxy,那也就是说,mapper接口执行的方法逻辑,是由MapperProxy完成的。

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
protected T newInstance(MapperProxy<T> mapperProxy) {
    // jdk动态代理,接口就是Mapper接口
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

MapperProxy里的invoke方法(执行mapper方法的逻辑):

 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 如果是默认的object的方法就直接执行,不去重写
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
          // 如果是默认方法(接口可以定义default方法),就绑定到proxy对象上
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
      // 这里生成一个method方法对象,这一步会解析方法的参数、类型、返回类型、方法执行的类型等;
     // 生成的这些mapper方法对象,都会进行缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);
      // 这里才是真正执行sql方法的方法
    return mapperMethod.execute(sqlSession, args);
  }

4. mapperMethod构造(mapper参数解析)

image-20230820162211555

image-20230820162228866

这里cachedMapperMethod(method);这里将method封装为mybatis需要的对象,里面还有一个比较重要的东西,就是它new的MethodSignature,里面它有一属性paramNameResolver,通过它解析参数:

this.paramNameResolver = new ParamNameResolver(configuration, method);

方法参数有3中情况:

  1. @Param指定参数名
  2. 使用实际参数名:默认是使用实际参数名的,可以通过配置useActualParamName
  3. 以参数序号为参数名

三种情况下,是顺序判断的,只要其中有一种情况符合就行,第三种情况其实就是补漏,在所有情况都不行的情况下的一个替补;

  public ParamNameResolver(Configuration config, Method method) {
      // 获取参数类型
    final Class<?>[] paramTypes = method.getParameterTypes();
      // 参数注解
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
      // 有序map
    final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
      // 参数计数
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        // 这里就是出现特殊的参数,就直接退出了,就不会保存这类型的参数
        // 特殊参数是指:参数是否是RowBounds  ResultHandler 类的子类或接口,百度比较详细
      if (isSpecialParameter(paramTypes[paramIndex])) {
        continue;
      }
      String name = null;
        // 第一种:@Param指定参数名
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
            // 拿到@Param注解,并将@Param的value值给
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
        // 第二种:使用实际参数名
        // 默认是使用实际参数名的,可以通过配置useActualParamName
        // 
      if (name == null) {
          // 没有@Param,就取实际名称,这里的isUseActualParamName默认true
        if (config.isUseActualParamName()) {
          name = getActualParamName(method, paramIndex);
        }
          // 第三种:以参数序号为参数名
        if (name == null) {
          // 如果实际名称也取不到,就用map的大小,一般也不会出现这样的情况
          name = String.valueOf(map.size());
        }
      }
        // 这里用索引为key,后面的取值都是按参数顺序遍历的,所以这里以索引为键,然后再放到SortedMap中
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }

5. 实际执行通过SQLSession执行对应方法

image-20230820163002876

execute方法就可以看出,实际执行时,还是通过sqlSession来做的

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        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;
  }

6. 执行前解析方法参数

增删改查的方法执行前都会执行参数的解析:

image-20230820163441915

解析参数的代码如下:

这里的names就是上面解析参数的names,也就是说,names的值是:

  1. 如果有@Param那么,names就是注解的value;
  2. 如果没有@Param,那么就是实际参数名;
  3. 如果既没有@Param也没有实际参数名,那么就是序号(1,2,3…)
public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    // 判断参数是否存在
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
        // 没有注解,且只有一个就返回
      return args[names.firstKey()];
    } else {
        // 存放参数对象的
      final Map<String, Object> param = new ParamMap<Object>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
          // 这里将names里存的参数名,放入map中
          // 1. 如果有`@Param`那么,names就是注解的value;
		  // 2. 如果没有`@Param`,那么就是实际参数名;
 	      // 3. 如果既没有`@Param`也没有实际参数名,那么就是序号(1,2,3...)
        param.put(entry.getValue(), args[entry.getKey()]);
          // 这里是按照参数顺序,设置固定的参数名,param1开始递增:(param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // 这里是防止重复添加
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

所以这里的参数返回就是:

  1. null,没有参数;

  2. 一个参数,返回参数值;

  3. map对象,key -> @Param的值,或者是实际参数名,也或者是参数序号,value -> 参数对象,同时还要对应每一个参数再多添加param的参数,格式是(param1, param2,...);比如:

    // mapper方法定义如下;
    List<Menu> queryAllByLimit(@Param("o11") int offset, @Param("o22") int limit);
    
    // 参数
    queryAllByLimit(1, 10);
    
    // 那么上面这段代码里的names就是[o11,o22]
    
    // 最终返回的map就是:
    [o11, 1]
    [o22, 10]
    [param1, 1]
    [param2, 10]
    

7. SQLSession里的执行又丢给executor

image-20230820165040097

image-20230820165058214

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        // 拿到对应的mapper方法声明
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

在这里它会对参数进行一个封装,先判断这个object(参数对象),上一步它对参数进行了解析,会返回3种值,只有第二种会出现符合这里的collectionarray

那么返回只有一个对象的,这里会再封装一遍,是list的话,就在包一层map,key为“list”,是array的话,key为“array”,最后结果就很统一了,参数集合都是一个map。

  private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("array", object);
      return map;
    }
    return object;
  }

8. 构造sql对象(解析动态sql)

这里 BoundSql boundSql = ms.getBoundSql(parameterObject);会去拿绑定的sql对象。

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
     // 拿绑定的sql对象
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 创建缓存key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

如果是静态sql,那么在加载mapper配置时就加载到configuration里了,而动态sql,它还会进行解析,${}替换为变量值,如果sql是这样的:select * from menu where name = ? and id = ${id},那么这一步就会被转为成:select * from menu where name = ? and id = 1;,之后在调用sqlSourceParser.parse()处理,将sql包装成静态sql对象。

注意点:将#{}变量替换为?这个操作在初始化时已经完成了,它属于静态sql。

  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
      // 在这里会通过TextSqlNode 和 StaticTextSqlNode 进行处理sql语句,
      // TextSqlNode是动态sql处理对象,它的处理方法和处理 #{} 一样,只是这个替换为了变量值
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
      // 然后在通过sqlSourceParser 再处理一遍,那么放回静态sql对象
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

9. 先查询二级缓存

位置:org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)

image-20230820172327744

他在查询是会尝试从二级缓存中获取,如果没有就会直接查询;可以看到使用缓存和不使用缓存都调用了一个query的重载方法,使用缓存的方式比直接查询多了一些获取缓存的步骤。

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
        // 刷新缓存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
          // 直接取缓存
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
            // 缓存是空的才会查表
          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);
  }

10. 尝试获取一级缓存

一级缓存得到的结果为空,才会调用queryFromDatabase() 方法;这里的queryStack类似一个计数,因为可能会连续执行多个sql。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
        // 尝试获取缓存
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
          // 这个方法针对的是statementType = callable的sql,就是执行存储过程语句的,再处理参数缓存
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
          // 查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

当一级缓存没有时执行这个方法;

 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
     // 一级缓存保存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        // 当sql语句的类型为CALLABLE时,就是存储过程语句执行
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

11. statementHandler执行底层方法

doQuery方法进去后,通过configuration对象获取statementHandler,通过它去执行连接数据库查询结果映射等操作。

  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();
        // 根据不同的声明类型(3种:sql,预处理sql,存储过程 )创建声明处理器
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 预编译
      stmt = prepareStatement(handler, ms.getStatementLog());
   return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

12. 设置Statement参数和typeHandler

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
     // 参数化,在这里进行参数的设置
    handler.parameterize(stmt);
    return stmt;
  }

handler.parameterize(stmt)里面走就是setParameters方法,是下面的方法,它有一个比较有用的地方是typeHandle这个对象。

public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 拿到xml里的参数映射对象
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
            // 获取参数的类型处理器,
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
              // 通过类型处理器转换参数值。如果是自定义的类型处理器,走的就是我们自定义的方法
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

typeHandler运用例子

这边我写了个例子:一个menu对象,属性deleted定义为枚举,然后再定义一个handle类来处理我们的这个属性,修改mapper.xml支持改属性。

我这里的例子不是很严谨,应该定义一个接口的,但这是个例子,就简单一点。

public class Menu implements Serializable {
    private static final long serialVersionUID = 499536196155109971L;
    private Long id;
    private String name;
    private DeleteEnum deleted;
    // 省略setter getter
}

定义DeleteEnum

public enum DeleteEnum {
    DELETED(1, "已删除"),
    UN_DELETED(0, "未删除");

    private Integer id;

    private String desc;
// 省略构造器 和 setter getter
}

定义类型处理器

public class DeleteTypeHandle<E extends DeleteEnum> implements TypeHandler<E> {

    private Class<E> tClass;

    private E[] enums;

    public DeleteTypeHandle(Class<E> tClass){
        if (tClass == null) {
            throw new RuntimeException("类型不能为空");
        }
        this.tClass = tClass;
        this.enums = tClass.getEnumConstants();
        if (this.enums == null) {
            throw new RuntimeException("类型不能为空");
        }

    }
    /**
     在执行query时设置参数时执行这个方法
     */
    @Override
    public void setParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getId());
    }

    /**
     结果映射时执行这个
     */
    @Override
    public E getResult(ResultSet rs, String columnName) throws SQLException {
        if (!rs.wasNull()) {
            return transform(rs.getInt(columnName));
        }
        return null;
    }
    /**
     结果映射时执行这个
     */
    @Override
    public E getResult(ResultSet rs, int columnIndex) throws SQLException {
        if (!rs.wasNull()) {
            return transform(rs.getInt(columnIndex));
        }
        return null;
    }
    /**
     结果映射时执行这个
     */
    @Override
    public E getResult(CallableStatement cs, int columnIndex) throws SQLException {
        if (!cs.wasNull()) {
            return transform(cs.getInt(columnIndex));
        }
        return null;
    }
    /**
     遍历我们的枚举,找到对应的枚举对象
     */
    private E transform(Integer id) {
        for (E e : enums) {
            if (e.getId().equals(id)) {
                return e;
            }
        }
        throw new RuntimeException("未知枚举类型");
    }
}

mapper.xml里的查询语句修改:

注意这里需要写javaType、typeHandler两个属性

  <select id="querySelective2" resultMap="MenuMap">
        select <include refid="base_column_list"></include>
        from menu
        where name = #{param1,jdbcType=VARCHAR}
        and deleted = #{deleted, javaType=integer,javaType=com.lry.mybatis.config.DeleteEnum, typeHandler=com.lry.mybatis.config.DeleteTypeHandle}
    </select>

结果映射修改:

   <resultMap type="com.lry.mybatis.model.Menu" id="MenuMap">
                 <result property="id" column="id" jdbcType="INTEGER"/>
                 <result property="name" column="name" jdbcType="VARCHAR"/>
                 <result property="deleted" column="deleted" jdbcType="INTEGER" javaType="com.lry.mybatis.config.DeleteEnum" typeHandler="com.lry.mybatis.config.DeleteTypeHandle"/>
            </resultMap>

image-20201104163345824

image-20201104162532392

image-20201104163944155

13. 结果映射

位置:org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets

回到doQuery方法中,通过StatementHandler执行查询方法

handler.<E>query(stmt, resultHandler)
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    // 包装查询项结果
    ResultSetWrapper rsw = getFirstResultSet(stmt);
// 从mapper声明中获取到结果映射,这个在初始化时就放入的了。
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
        // 数据绑定在这里做
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

这里的包装,将一些相关的东西赋值到属性中,像类型处理器、结果集、查询列和列类型。

这里的columnNames存放了sql查询出的列名,jdbcTypes 存放了对应的列类型。

public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    final ResultSetMetaData metaData = rs.getMetaData();
    final int columnCount = metaData.getColumnCount();
    for (int i = 1; i <= columnCount; i++) {
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      classNames.add(metaData.getColumnClassName(i));
    }
  }
 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
        // 这里是resultMap有嵌套是才会进入
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
            // 处理查询结果
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
            // 处理结果放到返回结果集合中
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

handleRowValues往里走是下面这段;对读取的行数据,进行赋值,并存储到集合里。

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    skipRows(rsw.getResultSet(), rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
        // 这里创建结果行对象并设置值
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
        // 存储对象
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, false)) {
          // 自动映射设置,配置了resultType,在这里映射
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
        // resultMap在这里映射
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

分别看看applyAutomaticMappingsapplyPropertyMappings里是什么。

  1. applyAutomaticMappings这个方法没有设置自动映射都会走,所以resultMap和resultType的都走了这个方法。但是如果是设置了resultMap,不会走那个for循环,所以这个方法更像是为了resultType类型而做方法。
 private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
     // 获取未映射的列;resultType会获取到实体对应的列,而如果是resultMap,这里是空的,这个列的名称也起的比较清楚“未自定映射的列”
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }
 private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
     // 到自动映射里拿
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    if (autoMapping == null) {
      autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
        // 没有设置自动映射,就在这里获取sql查询出来的列,这个列的来源是: ResultSetWrapper rsw = getFirstResultSet(stmt);
      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
      for (String columnName : unmappedColumnNames) {
        String propertyName = columnName;
       // ...
      }
      autoMappingsCache.put(mapKey, autoMapping);
    }
    return autoMapping;
  }
  1. applyPropertyMappings这个方法是设置里resultMap类型的方法走的。这个方法比resultType的直接一点,拿到属性名和值,直接设置。
 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;
    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);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        if (property == null) {
          continue;
        } else if (value == DEFERED) {
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

缓存

流程里面说过,他会先查询二级缓存,然后在查询一级缓存,那么这些缓存具体是什么,怎么使用呢?

一级缓存:

本地缓存。它是SQLSession级别的缓存,不需要任何配置,默认开启。

缓存对象是BaseExecutor对象的属性PerpetualCache,在查询是会将结果放到缓存中,一次SQLSession的创建表示一个会话,只要会话不关闭,那么这个缓存就是生效的。

有几种情况会使缓存失效:

  1. 上面说过的,只要SQLSession不断,缓存就存在,所以调用了close()方法会使缓存失效;

  2. 调用了SQLSession的方法clearCache()方法,会清空缓存;

  3. 执行修改操作如update、delete、insert,会清空缓存;

对应的存储源码,在上面已经有了,这里就截个图

image-20201222224610650

有一点,下面的查询会看到commit,但是对于一级缓存来说,不用commit也可以进行缓存。

不同的SQLSession,打印两次sql

image-20201222222220095

如下结果,两次查询,只有一次sql打印

image-20201222195648772.png

调用clearCache()方法后再查询,打印了两次sql

image-20201222195844602.png

调用异常update,打印了两次查询sql

image-20201222195939366.png

调用一次insert,打印了两次查询sql

image-20201222200031472.png

调用一次delete,打印了两次查询sql

image-20201222200138360.png

二级缓存:

与一级缓存不同,它作用的范围更广,它属于namespace级别的缓存,只要是相同的方法都可以共享。

该功能需要手动配置生效,配置方法:

  1. 在mybatis.xml(配置文件)中配置,默认是开启的(value = true),所以这一步可以跳过

     <setting name="cacheEnabled" value="true" />
    
  2. 在mapper.xml中配置,针对单个mapper生效

    说明:

     <cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
    

    eviction:清除策略

    LRU:最近最少使用:移除最长时间不被使用的对象;

    FIFO:先进先出:按对象进入缓存的顺序来移除它们

    SOFT:软引用:基于垃圾回收器状态和软引用规则移除对象

    WEAK:弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象

    flushInterval:(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新;

    readOnly:属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false;

    size:可存储的结果引用个数

  3. 某个方法不需缓存,可以设置useCache="false"

执行器executor有两个子类,BaseExecutor 和 CachingExecutor,在配置了二级缓存后,走的就是CachingExecutor 这个类的方法。

经过测试后,事务不提交,二级缓存也还存在;

执行insert、delete、update时,缓存也会被刷新;

插件

配置方式

  1. 实现Interceptor接口

    @Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
    public class CustomInterceptor implements Interceptor {
    
        /**
         * 业务逻辑
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // Invocation对象进行了封装,自由度更高
            System.out.println("自定义。。。");
            Object proceed = invocation.proceed();
            System.out.println("自定义结束。");
            return proceed;
        }
    
        /**
         * 添加到连接器链
         * @param target
         * @return
         */
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    }
    
  2. 在mybatis配置文件中添加插件配置

<plugins>
	<plugin interceptor="com.lry.mybatis.config.CustomInterceptor">
</plugin>
  1. SpringBoot配置方式,还有配置文件的方式,就不提了。

    @Configuration
    @ConditionalOnBean(SqlSessionFactory.class)
    @AutoConfigureAfter(MybatisAutoConfiguration.class)
    public class MybatisConfig {
    
        @Autowired
        private List<SqlSessionFactory> sqlSessionFactoryList;
    
        @PostConstruct
        public void addPageInterceptor() {
            CustomInterceptor interceptor = new CustomInterceptor();
            for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
                sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
            }
        }
    
    }
    

官网还是比较详细的:https://mybatis.org/mybatis-3/zh/configuration.html#plugins

mybatis提供Interceptor接口,只要实现并添加到拦截链中,就可以进行拦截,它可以拦截下面几个类:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

注解@Signature里的值也是他们的参数类型,作为方法签名。

它的method可以参考他们的接口方法,注意insert方法底层在executor里是update。

插件原理

而对于这个拦截器一开始看有些复杂,它的底层还是动态代理,捋一捋,mybatis提供了一个插件接口Interceptor,方法对象Invocation,拦截链InterceptorChain,还有一个Plugin 的插件工具类相当于。

它的结构都是高度封装的比较复杂些,刚开始都把我绕进去了,我把它简化如下:

image-20210105191954448.png

public interface IPlayGame {

    void play();
}
public interface IPluginBusiness {

    /**
     * 业务实现
     * @param target
     * @param method
     * @param args
     */
    Object intercept(Object target, Method method, Object[] args);

}
public class PlayGame implements IPlayGame{

    @Override
    public void play() {
        System.out.println("打游戏");
    }
}
public class PlayGameHandler implements InvocationHandler {

    private Object target;
    private IPluginBusiness pluginBusiness;

    public PlayGameHandler(Object target, IPluginBusiness pluginBusiness) {
        this.target = target;
        this.pluginBusiness = pluginBusiness;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return pluginBusiness.intercept(target, method, args);
    }
}
public class PluginBusinessImpl implements IPluginBusiness {
    @Override
    public Object intercept(Object target, Method method, Object[] args) {
        System.out.println("插件[1]开始。。。");
        Object invoke = null;
        try {
            invoke = method.invoke(target, args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println("插件[1]结束。");
        return invoke;
    }
}
public class PluginChain {

    private List<IPluginBusiness> pluginBusinesInterfaces = new ArrayList<>();

    public void addPlugin(IPluginBusiness pluginBusinesInterface) {
        pluginBusinesInterfaces.add(pluginBusinesInterface);
    }

    public List<IPluginBusiness> getPluginBusinesInterfaces() {
        return pluginBusinesInterfaces;
    }
}
public class PluginsIncationHandler implements InvocationHandler {

    private Object target;

    public PluginsIncationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("插件的动态代理。。。");
        return method.invoke(target, args);
    }
}

 public static void main() {
        // 1. 构建插件业务对象
        PluginBusinessImpl plugin = new PluginBusinessImpl();
        // 2. 将插件业务对象封装成代理对象
        PluginsIncationHandler pluginHandler = new PluginsIncationHandler(plugin);
        IPluginBusiness proxy = (IPluginBusiness)Proxy.newProxyInstance(plugin.getClass().getClassLoader(), new Class[]{IPluginBusiness.class}, pluginHandler);
        // 3. 将代理对象放到插件链
        PluginChain chain = new PluginChain();
        chain.addPlugin(proxy);

        // 1. 构建插件业务对象
        PluginBusinessImpl2 plugin2 = new PluginBusinessImpl2();
        // 2. 将插件业务对象封装成代理对象
        PluginsIncationHandler pluginHandler2 = new PluginsIncationHandler(plugin2);
        IPluginBusiness proxy2 = (IPluginBusiness)Proxy.newProxyInstance(plugin.getClass().getClassLoader(), new Class[]{IPluginBusiness.class}, pluginHandler2);
        // 3. 将代理对象放到插件链
        chain.addPlugin(proxy2);
        // 4. 将所有的插件都封装到待执行的对象上
        IPlayGame playGame = new PlayGame();
        for (IPluginBusiness pluginBusiness : chain.getPluginBusinesInterfaces()) {
            PlayGameHandler playGameHandler = new PlayGameHandler(playGame, pluginBusiness);
            playGame = (IPlayGame)Proxy.newProxyInstance(PlayGame.class.getClassLoader(), new Class[]{IPlayGame.class}, playGameHandler);
        }
        playGame.play();
    }

image-20210105192510457

运行结果如上所示,我们可以随意的插入我们的插件逻辑,从main方法来看它的步骤就很清晰:

  1. 插件接口提供一个插件业务的接口,参数为target, method, args
  2. 实现插件,并生成代理对象
  3. 将插件代理对象放到插件链中
  4. 实现业务对象,
  5. 生成业务对象代理,插件对象作为参数,将invoke方法替换为插件的方法

mybatis的优化思路可能是这样的:

  1. 插件的功能独立,只要加入插件就能实现插件前后处理的效果,需要代理实际业务对象,但不能产生依赖,经理少的去关联业务对象的东西,那么就可以把target, method, args传入(intercept方法);
  2. 同时,按功能划分,插件的功能可以提供一个实现对实际业务对象进行代理封装(warp方法);
  3. 作为管理插件的插件链类,必须有添加插件功能,可以额外的增加使插件生效的功能(pluginAll方法);

遵循了开发六原则,对功能划分细化,使得代码更加健壮。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值