聊一聊SpringBoot的自动装配原理
前言
通过两个简单的案例:在Spring中集成MyBatis、在SpringBoot中集成MyBatis
找出两者的差异,初探Spring发展到SpringBoot的部分演化过程
以MyBatis为例,简单梳理自动配置过程
一、Spring整合MyBatis
1.1pom文件
- pom.xml
<!-- Spring JDBC -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version>
</dependency><!-- MyBatis核心包 -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.10</version>
</dependency><!-- MyBatis-Spring 整合包 -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.6</version>
</dependency><!-- Druid数据库连接池 -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.23</version>
</dependency><!-- MySQL数据库驱动 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.26</version>
</dependency>
1.2配置类
- MyBatisConfig
package com.lazy.snail;import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;import javax.sql.DataSource;/*** @ClassName MyBatisConfig* @Description TODO* @Author lazysnail* @Date 2024/11/5 16:20* @Version 1.0*/
@Configuration
@PropertySource("classpath:db.properties")
@MapperScan("com.lazy.snail.mapper")
public class MyBatisConfig {@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Value("${jdbc.driverClassName}")private String driverClassName;@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();// 基本配置dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);// Druid 高级配置return dataSource;}@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(dataSource);return factoryBean.getObject();}
}
1.3数据源属性配置文件
# db.properties
jdbc.url=jdbc:mysql://*.*.*.*:3306/snail_db
jdbc.username=username
jdbc.password=password
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
1.4mapper
- UserMapper
package com.lazy.snail.mapper;import com.lazy.snail.domain.User;
import org.apache.ibatis.annotations.Insert;
import org.springframework.stereotype.Repository;/*** @ClassName UserMapper* @Description TODO* @Author lazysnail* @Date 2024/11/5 16:27* @Version 1.0*/
@Repository
public interface UserMapper {@Insert("insert into user_info(user_id, name, email) values(#{userId}, #{name}, #{email})")void insert(User user);
}
1.5测试类
- SpringTest
package com.lazy.snail;import com.lazy.snail.domain.User;
import com.lazy.snail.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;@Slf4j
public class SpringTest {@Testvoid test() {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);UserService userService = context.getBean(UserService.class);userService.createUser(new User(1, "lazysnail", "lazy_snail@aliyun.com"));}
}
二、SpringBoot整合MyBatis
2.1pom文件
- pom.xml
<!-- springboot集成mybatis 自动为mybatis创建和配置所需的bean,简化mybatis的使用 -->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version>
</dependency><!-- 数据库连接池 -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version>
</dependency><!-- 数据库驱动 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version>
</dependency>
2.2配置文件
- application.yml
spring:datasource:druid:url: jdbc:mysql://*.*.*.*:3306/snail_dbusername: usernamepassword: passworddriver-class-name: com.mysql.cj.jdbc.Driver
2.3mapper
- UserMapper
package com.lazy.snail.mapper;import com.lazy.snail.domain.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;/*** @ClassName UserMapper* @Description TODO* @Author lazysnail* @Date 2024/10/10 14:03* @Version 1.0*/
@Mapper
public interface UserMapper {@Insert("insert into user_info(user_id, name, email) values(#{userId}, #{name}, #{email})")void insert(User user);
}
2.4测试类
- ApplicationTests
package com.lazy.snail;import com.lazy.snail.domain.User;
import com.lazy.snail.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class ApplicationTests {@Autowiredprivate UserService userService;@Testvoid contextLoads() {User user = new User();user.setUserId(4);userService.createUser(user);}
}
三、Spring与SpringBoot整合MyBatis区别
3.1pom文件差异
-
Spring中单独引入功能模块的依赖,如mybatis、mybatis-spring
-
SpringBoot中引入starter概念,需要整合mybatis,只需要引入mybatis-spring-boot-starter依赖
[!NOTE]
Starter POMs are a set of convenient dependency descriptors that you can include in your application. You get a one-stop-shop for all the Spring and related technology that you need, without having to hunt through sample code and copy paste loads of dependency descriptors. For example, if you want to get started using Spring and JPA for database access, just include the
spring-boot-starter-data-jpa
dependency in your project, and you are good to go.通过引入 Starter,无需手动查找和复制大量依赖,避免了我们去引入依赖所带来的麻烦
3.2配置类及配置文件差异
- Spring中我们需要自定义MyBatis的配置类,专门管理DataSource、SqlSessionFactory、MapperScannerConfigurer这些组件
- SpringBoot集成MyBatis时没有任何的相关配置类(基础版),因为SpringBoot提供了自动配置功能,MyBatis 的大部分配置可以通过
mybatis-spring-boot-starter
自动完成,只需要引入相关的依赖,并在application.properties
或application.yml
中进行一些简单配置即可,Spring Boot 会自动创建并配置所需的 Bean(如SqlSessionFactory
、MapperScannerConfigurer
等)。
四、SpringBoot怎么自动配置MyBatis
4.1MyBatis自动配置信息位置
4.2读取自动配置信息
4.3解析bean定义信息
4.3.1解析Application中@Import
/*** 激活Spring应用上下问的自动配置功能,尝试猜测和配置需要的bean。* 自动配置类通常基于类路径和定义的bean来应用。* 例如:* 类路径下有tomcat-embedded.jar意味着可能想要一个TomcatServletWebServerFactory的bean*(除非自定义了ServletWebServerFactory的bean)** 当使用@SpringBootApplication注解时,上下文的自动配置功能自动开启。* 添加这个注解没有额外的作用。* * 自动配置尽可能的智能,当你自定义了更多配置,自动配置会慢慢弱化。* 如果不想用某些配置,你可以通过excludeName手动排除。* 也可以通过exclude排除。* 自动配置总是在用户定义bean注册之后应用。* * 用@EnableAutoConfiguration注解的类包(通常通过@SpringBootApplication)具有特定的意义,通常是默认的。* 例如:* 它将在扫描@Entity类时使用。* 通常建议将@EnableAutoConfiguration(如果不使用@SpringBootApplication)放在根包中,以便可以搜索所有子包和类。* * 自动配置类是常规的Spring @Configuration bean。* 它们是使用importcandidate和springfactoresloader机制定位的(与这个类相关)。* 通常自动配置bean是@Conditional注解的bean(最常使用@ConditionalOnClass和@ConditionalOnMissingBean注解)。**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";/*** 通过class排除不需要的自动配置类*/Class<?>[] exclude() default {};/*** 通过类名称排除不需要的自动配置类*/String[] excludeName() default {};}
4.3.1.1获取自动配置入口
// AutoConfigurationImportSelector
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}// exclude excludeNameAnnotationAttributes attributes = getAttributes(annotationMetadata);// 所有META-INF/spring.factories中的自动配置类List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);// 去重configurations = removeDuplicates(configurations);Set<String> exclusions = getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);// 去掉需要排除的configurations.removeAll(exclusions);// 过滤器过滤configurations = getConfigurationClassFilter().filter(configurations);fireAutoConfigurationImportEvents(configurations, exclusions);return new AutoConfigurationEntry(configurations, exclusions);
}
4.3.1.2过滤器应用
-
OnClassCondition过滤器会对配置类中@ConditionalOnClass和@ConditionalOnMissingClass进行匹配过滤
-
// MybatisAutoConfiguration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
-
如果classpath下没有SqlSessionFactory或者SqlSessionFactoryBean,该配置类将被过滤
-
-
OnBeanCondition过滤器会对配置类中@ConditionalOnSingleCandidate进行匹配过滤
-
// MybatisAutoConfiguration @ConditionalOnSingleCandidate(DataSource.class)
-
应用上下文中是否存在DataSource类型的单个候选 bean。如果存在且只有一个这样的 bean,则条件匹配。
-
4.3.1.3过滤结果
4.3.1.4遍历处理自动配置类
后续就是将MybatisAutoConfiguration中涉及到的bean定义信息注册到容器中。
五、总结
1. Spring Boot 启动和自动配置的初始化
当 Spring Boot 启动时,SpringApplication.run()
方法会被调用,Spring 容器会加载并处理配置类和自动配置类。在自动配置过程中,Spring Boot 会根据 @EnableAutoConfiguration
注解和 @AutoConfiguration
注解扫描和加载符合条件的自动配置类。
2. 自动配置类的选择
Spring Boot 自动配置是通过 spring.factories
文件来加载的。spring.factories
文件列出了所有自动配置类,在应用启动时,这些自动配置类会被加载到 Spring 容器中。
spring.factories
文件
在 spring-boot-autoconfigure
模块中,spring.factories
文件中会包含对 MyBatis 自动配置类 MybatisAutoConfiguration
的引用。该文件告诉 Spring Boot 当满足特定条件时,加载 MybatisAutoConfiguration
类。
3. 处理 @EnableAutoConfiguration
和条件注解
Spring Boot 使用了许多条件注解(@Conditional
系列注解),来决定是否启用某些配置类。
@ConditionalOnClass
和 @ConditionalOnMissingClass
MybatisAutoConfiguration
配置类上有 @ConditionalOnClass
注解,表示只有当 SqlSessionFactory
、SqlSessionFactoryBean
等 MyBatis 相关类在类路径中时,才会启用该自动配置类。@ConditionalOnClass
由 OnClassCondition
过滤器进行检查,判断类路径中是否存在这些类。
MybatisAutoConfiguration
还会使用 @ConditionalOnSingleCandidate
注解来检查 Spring 容器中是否存在且只有一个 DataSource
类型的 Bean。如果条件满足,则加载 MybatisAutoConfiguration
。
4. 加载 MybatisAutoConfiguration
配置类
在满足了 @ConditionalOnClass
和 @ConditionalOnSingleCandidate
等条件后,MybatisAutoConfiguration
类会被加载并注册到 Spring 容器中。
5. 创建 DataSource
和 SqlSessionFactory
MybatisAutoConfiguration
配置类内部有多个 @Bean
方法,其中主要的两个是 DataSource
和 SqlSessionFactory
的配置。Spring Boot 会根据现有的条件自动配置这些 Bean。
DataSource
- 如果你没有手动配置
DataSource
,Spring Boot 会使用默认的数据库连接池(如 HikariCP)自动配置一个DataSource
Bean。 - 如果已经在应用上下文中存在
DataSource
,则自动配置会跳过该步骤。
SqlSessionFactory
SqlSessionFactory
是 MyBatis 的核心对象,它需要 DataSource
来进行初始化。MybatisAutoConfiguration
类会检查容器中是否已有 SqlSessionFactory
Bean,如果没有,则会创建一个新的 SqlSessionFactory
。
@ConditionalOnMissingBean
注解表示只有当容器中没有SqlSessionFactory
时,才会创建这个 Bean。
6. 创建 SqlSessionTemplate
SqlSessionTemplate
是 MyBatis 与 Spring 整合的核心类,负责事务管理和会话的创建。MybatisAutoConfiguration
会根据 SqlSessionFactory
创建一个 SqlSessionTemplate
:
如果容器中没有 SqlSessionTemplate
Bean,MybatisAutoConfiguration
会创建并注册一个 SqlSessionTemplate
Bean。
7. 配置 MapperScannerConfigurer
或 @MapperScan
Spring Boot 中的 MyBatis 自动配置已经提供了自动扫描 Mapper 的功能,通常不需要手动配置 MapperScannerConfigurer
。@MapperScan
注解用于指定扫描 Mapper 接口的包:
Spring Boot 会自动扫描该包下的 Mapper 接口,并将其注册为 Spring Bean。