MyBatis 与 Spring 是如何结合在一起工作的——mybatis-spring(version:1.2.2)

在MyBatis-Spring的项目中,我们一般会为MyBatis配置两个配置文件 beans-mybatis.xml mybatis-config.xml
其中 beans-mybatis.xml 中配置的是MyBatis 和 Spring结合使用时委托给 spring 管理的 bean。
mybatis-config.xml 中是MyBatis 自身的配置。

例:beans-mybatis.xml 

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="dataSource" p:configLocation="classpath:mybatis-config.xml"
        p:mapperLocations="classpath:mapper/**/*.xml" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="com.cn.kvn.usage.dao" />

 

mybatis-config.xml:

<configuration>
    <settings>
        <setting name="lazyLoadingEnabled" value="false" />
        <!-- logback日志指定打印sql用 -->
        <setting name="logPrefix" value="dao." />
    </settings>
    <typeAliases>
        <!-- 别名定义 -->
    </typeAliases>
    <!-- 插件 -->
    <plugins>
        <plugin interceptor="com.cn.kvn.framework.jdbc.mybatis.interceptor.PageInterceptor">
            <property name="dialectClassName" value="com.cn.kvn.framework.jdbc.mybatis.interceptor.MySQLDialect" />
        </plugin>
    </plugins>
</configuration>

 

mybatis-spring的入口就在bean的定义那里(beans-mybatis.xml ): SqlSessionFactoryBean 、MapperScannerConfigurer

#####1. SqlSessionFactoryBean

SqlSessionFactoryBean实现了 InitializingBean ,在 afterPropertiesSet() 中会对 mybatis的配置 p:configLocation="classpath:mybatis-config.xml" 、 p:mapperLocations="classpath:mapper/**/*.xml" 进行解析。

org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory():

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    Configuration configuration; // MyBatis 的配置,存放 mybatis-config.xml 中的配置
    XMLConfigBuilder xmlConfigBuilder = null; // mybatis-config.xml 的解析器,解析出来的内容放在 Configuration 中
    if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      configuration = new Configuration();
      configuration.setVariables(this.configurationProperties);
    }

    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }

    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

    if (hasLength(this.typeAliasesPackage)) { // typeAlias : 类型别名,用于sqlmap中类型(parameterType、resultType)的配置
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); // 注册 typeAliases
      }
    }

    if (!isEmpty(this.typeAliases)) { // typeAlias : 类型别名,用于sqlmap中类型(parameterType、resultType)的配置
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias); // 注册 typeAliases
      }
    }

    if (!isEmpty(this.plugins)) { // plugins : mybatis 的插件,即 MyBatis 的拦截器和扩展点。可以针对Executor 、 StatementHandler 、 ResultSetHandler 和 PameterHandler 进行拦截扩展
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin); // 添加 MyBatis Interceptor
      }
    }

    if (hasLength(this.typeHandlersPackage)) { // typeHandler : 类型转换器,对java类型和sqlmap中的类型进行转换
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan); // 注册 typeHandler
      }
    }

    if (!isEmpty(this.typeHandlers)) { // typeHandler : 类型转换器,对java类型和sqlmap中的类型进行转换
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler); // 注册 typeHandler
      }
    }

    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse(); // xmlConfigBuilder解析 mybatis-spring.xml 配置
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset(); // 重置 ErrorContext
      }
    }

    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory(); // 默认的 transactionFactory
    }

    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    configuration.setEnvironment(environment);

    if (this.databaseIdProvider != null) {
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); // DatabaseId 用于对多数据库的支持
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse(); // xmlMapperBuilder:解析 mapperLocation 中指定的 sqlmap 文件,即解析 sql 语句
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset(); // 重置 ErrorContext
        }

      }
    }
    return this.sqlSessionFactoryBuilder.build(configuration); // 将 configuration 配置设置到 org.apache.ibatis.session.defaults.DefaultSqlSessionFactory 中
}

附:ErrorContext: 解析配置文件时,用于存储异常信息(ThreadLocal实现的)

跟配置解析相关的类:

XMLConfigBuilder : 解析 mybatis-config.xml 中 MyBatis 自身的配置。
XmlMapperBuilder : 解析 sqlmap 中的配置,包括 <resultMap> 和 sql 语句
  ResultMapResolver : 解析 sqlmap 中的 <resultMap> 节点
  XMLStatementBuilder : 解析 sql 语句(<insert>、<delete>、<update>、<select>)

 

举例:mybatis-config.xml 的解析是通过 XMLConfigBuilder 来完成的

mybatis-config.xml中能够配置的属性:(XMLConfigBuilder#parseConfiguration(XNode))

private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      settingsElement(root.evalNode("settings"));
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

 

其中<settings>标签可以配置的属性如下:(XMLConfigBuilder#settingsElement(XNode))

configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));

 

#####2. MapperScannerConfigurer

MapperScannerConfigurer 的作用是扫描 java 的 Dao 接口,来与 mapper 做映射关系

org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry)

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters(); // java类型过滤器,排除一些不符合要求的 Dao
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); // 进行 Dao 扫描,扫描 basePackage 包下面的类
}

 

org.mybatis.spring.mapper.ClassPathMapperScanner#scan(String... basePackages):

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); // 调用 ClassPathBeanDefinitionScanner#doScan()解析出bean
    for (BeanDefinitionHolder holder : beanDefinitions) {
    GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
    definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName()); // mapperInterface 是 Dao 对应的 bean 的原始类型 
    definition.setBeanClass(MapperFactoryBean.class); // MapperFactoryBean 是 Dao 对应的 bean 的实际类型。也就是所有的 Dao 对应的 bean ,最后都是 MapperFactoryBean,通过 MapperFactoryBean 来做 Dao 的代理,将CRUD操作分发到具体的 sqlmap 中的 sql 去执行。

    definition.getPropertyValues().add("addToConfig", this.addToConfig);

    boolean explicitFactoryUsed = false;
    if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
      definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionFactory != null) {
      definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
      explicitFactoryUsed = true;
    }

    if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
      definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionTemplate != null) {
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      explicitFactoryUsed = true;
    }

    if (!explicitFactoryUsed) {
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); // 让 MapperFactoryBean 可以通过 @Autowired 注解来进行注入。即注入 xxxDao
    }

    return beanDefinitions;
}

 

#####3. MapperFactoryBean

MapperFactoryBean 继承了SqlSessionDaoSupport,默认使用了 org.mybatis.spring.SqlSessionTemplate 来操作CRUD方法。

org.mybatis.spring.SqlSessionTemplate 的构造方法:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator; // 默认使用 MyBatisExceptionTranslator ,对 SQL 异常进行友好转换
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
}

 

SqlSessionTemplate 又是通过 sqlSessionProxy 来操作CRUD方法的。
sqlSessionProxy 是JDK 动态代理,通过 SqlSessionInterceptor 来拦截 org.apache.ibatis.session.SqlSession 接口里面的 CRUD 操作。

org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor:

/**
   * Proxy needed to route MyBatis method calls to the proper SqlSession got
   * from Spring's Transaction Manager
   * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
   * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
   */
  private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator); // 从当前线程中获取一个 SqlSession ,如果没有,就创建一个新的 SqlSession。(org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession(ExecutorType execType))。这样 spring 和 mybatis 就结合在一起了
      try {
        Object result = method.invoke(sqlSession, args); // 执行 SqlSession 的方法(CRUD操作)。
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t); // 对 Method#invoke(Object, Object...) 的异常进行解封装
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); // 对 SQL 异常进行友好转换
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

 

至此,我们还有一个问题没有解决:我们在调用 xxDao 中的方法 xxMethod 时,mybatis-spring.jar 是如何找到 xxMethod 对应的配置文件 xxMapper.xml 中的 sql 语句的?

它是通过多个 JDK 动态代理,去拦截 mapperInterface(xxDao)中的方法来实现的。具体可以看下图:

通过上图,前面的疑问就迎刃而解了。^_^

 

附:MapperFactoryBean 中的 sqlSessionFactory 是如何注入的?

在 Spring 容器里面 IJobBeforehandRetryDao.java 对应的 beanName = "IJobBeforehandRetryDao"

相应的 RootBeanDefinition为:

Root bean: class [org.mybatis.spring.mapper.MapperFactoryBean]; scope=singleton; abstract=false;
 lazyInit=false; autowireMode=2; dependencyCheck=0; autowireCandidate=true; primary=false;
 factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null;
 defined in file [E:\gitWorkspace\dal-job\dal-job-core\target\classes\com\kvn\dal\core\dao\IJobBeforehandRetryDao.class]

IJobBeforehandRetryDao 对应的 bean 是一个 FactoryBean。Spring 对 FactoryBean 里面的属性有特殊处理。

AbstractAutowireCapableBeanFactory#autowireByType(String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs)

MapperFactoryBean#setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)会被调用。

这样,MapperFactoryBean 中的属性 sqlSession就会被赋值。 this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);

排查结果如下: 1.使用mvn dependency:tree返回如下信息: [INFO] --- maven-dependency-plugin:3.1.2:tree (default-cli) @ zysygh --- [INFO] com.ghzy:zysygh:jar:0.1.196 [INFO] +- org.springframework.boot:spring-boot-starter-data-redis:jar:2.2.13.RELEASE:compile [INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.2.13.RELEASE:compile [INFO] | | +- org.springframework.boot:spring-boot:jar:2.2.13.RELEASE:compile [INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.2.13.RELEASE:compile [INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile [INFO] | | \- org.yaml:snakeyaml:jar:1.25:runtime [INFO] | +- org.springframework.data:spring-data-redis:jar:2.2.12.RELEASE:compile [INFO] | | +- org.springframework.data:spring-data-keyvalue:jar:2.2.12.RELEASE:compile [INFO] | | | \- org.springframework.data:spring-data-commons:jar:2.2.12.RELEASE:compile [INFO] | | +- org.springframework:spring-tx:jar:5.2.12.RELEASE:compile [INFO] | | +- org.springframework:spring-oxm:jar:5.2.12.RELEASE:compile [INFO] | | \- org.springframework:spring-aop:jar:5.2.12.RELEASE:compile [INFO] | \- io.lettuce:lettuce-core:jar:5.2.2.RELEASE:compile [INFO] | +- io.netty:netty-common:jar:4.1.58.Final:compile [INFO] | +- io.netty:netty-handler:jar:4.1.58.Final:compile [INFO] | | +- io.netty:netty-resolver:jar:4.1.58.Final:compile [INFO] | | +- io.netty:netty-buffer:jar:4.1.58.Final:compile [INFO] | | \- io.netty:netty-codec:jar:4.1.58.Final:compile [INFO] | +- io.netty:netty-transport:jar:4.1.58.Final:compile [INFO] | \- io.projectreactor:reactor-core:jar:3.3.13.RELEASE:compile [INFO] | \- org.reactivestreams:reactive-streams:jar:1.0.3:compile [INFO] +- org.springframework.boot:spring-boot-starter-mail:jar:2.2.13.RELEASE:compile [INFO] | +- org.springframework:spring-context-support:jar:5.2.12.RELEASE:compile [INFO] | | +- org.springframework:spring-beans:jar:5.2.12.RELEASE:compile [INFO] | | \- org.springframework:spring-context:jar:5.2.12.RELEASE:compile [INFO] | \- com.sun.mail:jakarta.mail:jar:1.6.5:compile [INFO] | \- com.sun.activation:jakarta.activation:jar:1.2.2:compile [INFO] +- org.springframework.boot:spring-boot-starter-thymeleaf:jar:2.2.13.RELEASE:compile [INFO] | +- org.thymeleaf:thymeleaf-spring5:jar:3.0.12.RELEASE:compile [INFO] | | \- org.thymeleaf:thymeleaf:jar:3.0.12.RELEASE:compile [INFO] | | +- org.attoparser:attoparser:jar:2.0.5.RELEASE:compile [INFO] | | \- org.unbescape:unbescape:jar:1.1.6.RELEASE:compile [INFO] | \- org.thymeleaf.extras:thymeleaf-extras-java8time:jar:3.0.4.RELEASE:compile [INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.2.13.RELEASE:compile [INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.2.13.RELEASE:compile [INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.10.5:compile [INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.10.5:compile [INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.10.5:compile [INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.2.13.RELEASE:compile [INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.41:compile [INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.41:compile [INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.41:compile [INFO] | +- org.springframework.boot:spring-boot-starter-validation:jar:2.2.13.RELEASE:compile [INFO] | | +- jakarta.validation:jakarta.validation-api:jar:2.0.2:compile [INFO] | | \- org.hibernate.validator:hibernate-validator:jar:6.0.22.Final:compile [INFO] | | +- org.jboss.logging:jboss-logging:jar:3.4.1.Final:compile [INFO] | | \- com.fasterxml:classmate:jar:1.5.1:compile [INFO] | +- org.springframework:spring-web:jar:5.2.12.RELEASE:compile [INFO] | \- org.springframework:spring-webmvc:jar:5.2.12.RELEASE:compile [INFO] | \- org.springframework:spring-expression:jar:5.2.12.RELEASE:compile [INFO] +- org.mybatis.spring.boot:mybatis-spring-boot-starter:jar:2.3.2:compile [INFO] | +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.2.13.RELEASE:compile [INFO] | | +- com.zaxxer:HikariCP:jar:3.4.5:compile [INFO] | | \- org.springframework:spring-jdbc:jar:5.2.12.RELEASE:compile [INFO] | +- org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:jar:2.3.2:compile [INFO] | \- org.mybatis:mybatis-spring:jar:2.1.2:compile [INFO] +- org.springframework.boot:spring-boot-starter-logging:jar:2.2.13.RELEASE:compile [INFO] | +- ch.qos.logback:logback-classic:jar:1.2.3:compile [INFO] | | \- ch.qos.logback:logback-core:jar:1.2.3:compile [INFO] | \- org.slf4j:jul-to-slf4j:jar:1.7.30:compile [INFO] +- mysql:mysql-connector-java:jar:8.0.26:runtime [INFO] +- org.projectlombok:lombok:jar:1.18.16:compile (optional) [INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.2.13.RELEASE:test [INFO] | +- org.springframework.boot:spring-boot-test:jar:2.2.13.RELEASE:test [INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.2.13.RELEASE:test [INFO] | +- com.jayway.jsonpath:json-path:jar:2.4.0:test [INFO] | | \- net.minidev:json-smart:jar:2.3:test [INFO] | | \- net.minidev:accessors-smart:jar:1.2:test [INFO] | | \- org.ow2.asm:asm:jar:5.0.4:test [INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test [INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test [INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.5.2:test [INFO] | | +- org.junit.jupiter:junit-jupiter-api:jar:5.5.2:test [INFO] | | | +- org.opentest4j:opentest4j:jar:1.2.0:test [INFO] | | | \- org.junit.platform:junit-platform-commons:jar:1.5.2:test [INFO] | | +- org.junit.jupiter:junit-jupiter-params:jar:5.5.2:test [INFO] | | \- org.junit.jupiter:junit-jupiter-engine:jar:5.5.2:test [INFO] | +- org.junit.vintage:junit-vintage-engine:jar:5.5.2:test [INFO] | | +- org.apiguardian:apiguardian-api:jar:1.1.0:test [INFO] | | +- org.junit.platform:junit-platform-engine:jar:1.5.2:test [INFO] | | \- junit:junit:jar:4.12:test [INFO] | +- org.mockito:mockito-junit-jupiter:jar:3.1.0:test [INFO] | +- org.assertj:assertj-core:jar:3.13.2:test [INFO] | +- org.hamcrest:hamcrest:jar:2.1:test [INFO] | +- org.mockito:mockito-core:jar:3.1.0:test [INFO] | | +- net.bytebuddy:byte-buddy:jar:1.10.19:test [INFO] | | +- net.bytebuddy:byte-buddy-agent:jar:1.10.19:test [INFO] | | \- org.objenesis:objenesis:jar:2.6:test [INFO] | +- org.skyscreamer:jsonassert:jar:1.5.0:test [INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test [INFO] | +- org.springframework:spring-core:jar:5.2.12.RELEASE:compile [INFO] | | \- org.springframework:spring-jcl:jar:5.2.12.RELEASE:compile [INFO] | +- org.springframework:spring-test:jar:5.2.12.RELEASE:test [INFO] | \- org.xmlunit:xmlunit-core:jar:2.6.4:test [INFO] +- com.aliyun:aliyun-java-sdk-core:jar:4.5.3:compile [INFO] | +- com.google.code.gson:gson:jar:2.8.6:compile [INFO] | +- org.apache.httpcomponents:httpclient:jar:4.5.13:compile [INFO] | | \- commons-codec:commons-codec:jar:1.13:compile [INFO] | +- org.apache.httpcomponents:httpcore:jar:4.4.14:compile [INFO] | +- commons-logging:commons-logging:jar:1.2:compile [INFO] | +- javax.xml.bind:jaxb-api:jar:2.3.1:compile [INFO] | | \- javax.activation:javax.activation-api:jar:1.2.0:compile [INFO] | +- org.jacoco:org.jacoco.agent:jar:runtime:0.8.5:compile [INFO] | +- org.ini4j:ini4j:jar:0.5.4:compile [INFO] | +- org.slf4j:slf4j-api:jar:1.7.30:compile [INFO] | +- io.opentracing:opentracing-api:jar:0.33.0:compile [INFO] | \- io.opentracing:opentracing-util:jar:0.33.0:compile [INFO] | \- io.opentracing:opentracing-noop:jar:0.33.0:compile [INFO] +- com.aliyun:dysmsapi20170525:jar:4.1.0:compile [INFO] | +- com.aliyun:tea-util:jar:0.2.23:compile [INFO] | +- com.aliyun:endpoint-util:jar:0.0.7:compile [INFO] | +- com.aliyun:tea:jar:1.3.1:compile [INFO] | | \- com.squareup.okhttp3:okhttp:jar:3.14.9:compile [INFO] | | \- com.squareup.okio:okio:jar:1.17.2:compile [INFO] | +- com.aliyun:tea-openapi:jar:0.3.8:compile [INFO] | | +- com.aliyun:credentials-java:jar:1.0.1:compile [INFO] | | | \- com.aliyun:credentials-api:jar:1.0.0:compile [INFO] | | +- com.aliyun:alibabacloud-gateway-spi:jar:0.0.2:compile [INFO] | | \- com.aliyun:tea-xml:jar:0.1.6:compile [INFO] | | \- org.dom4j:dom4j:jar:2.0.3:compile [INFO] | \- com.aliyun:openapiutil:jar:0.2.2:compile [INFO] | \- org.bouncycastle:bcprov-jdk18on:jar:1.78.1:compile [INFO] +- com.alibaba:fastjson:jar:1.2.75:compile [INFO] +- org.mybatis:mybatis:jar:3.5.9:compile [INFO] +- org.jetbrains:annotations:jar:13.0:compile [INFO] +- org.springframework.boot:spring-boot-configuration-processor:jar:2.2.13.RELEASE:compile (optional) [INFO] +- redis.clients:jedis:jar:3.1.0:compile [INFO] | \- org.apache.commons:commons-pool2:jar:2.7.0:compile [INFO] +- io.jsonwebtoken:jjwt-api:jar:0.11.5:compile [INFO] +- io.jsonwebtoken:jjwt-impl:jar:0.11.5:runtime [INFO] \- io.jsonwebtoken:jjwt-jackson:jar:0.11.5:runtime [INFO] \- com.fasterxml.jackson.core:jackson-databind:jar:2.10.5.1:compile [INFO] +- com.fasterxml.jackson.core:jackson-annotations:jar:2.10.5:compile [INFO] \- com.fasterxml.jackson.core:jackson-core:jar:2.10.5:compile [INFO] ------------------------------------------------------------------------ 2.applicaion.properties中的配置内容为logging.config=classpath:logback-spring.xml 3.src/main/resources目录下存在logback-spring.xml文件,但之前里面没有那两行<include>配置项,现已添加。
07-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值