Spring Boot笔记
Spring Boot
🌟 模块一:Spring 纯注解配置 (告别 XML)
本模块详解如何用 5 个核心注解完全替代 applicationContext.xml。
1. @Bean —— 手动注册组件
- 核心作用:将方法的返回值对象放入 Spring IOC 容器。
- 等价 XML:
<bean id="xxx" class="com.xxx.Xxx"/> - 关键属性:
value/name:指定 Bean 在容器中的唯一标识(ID)。若不写,默认为方法名。
- 使用场景:第三方包中的类(无法添加
@Component注解),需要手动配置时。
💻 示例代码
@Configuration
public class MyDataSourceConfig {
// 等价于:<bean id="myDruidDataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
@Bean("myDruidDataSource")
public DruidDataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/db_test");
ds.setUsername("root");
ds.setPassword("123456");
return ds;
}
// 默认 ID 为 "userService"
@Bean
public UserService userService() {
return new UserServiceImpl();
}
}
2. @PropertySource —— 加载外部属性文件
- 核心作用:加载指定的
.properties文件到 Spring 环境变量中。 - 等价 XML:
<context:property-placeholder location="classpath:db.properties"/> - 关键属性:
value:文件路径,支持数组加载多个文件。encoding:指定编码(如utf-8),防止中文乱码。
- 配合使用:通常与
@Value("${key}")搭配使用。
💻 示例代码
配置文件 db.properties:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db
jdbc.user=root
jdbc.password=secret
Java 配置类:
@Configuration
// 加载单个文件
@PropertySource(value = "classpath:db.properties", encoding = "utf-8")
// 也可以加载多个:@PropertySource({"classpath:a.properties", "classpath:b.properties"})
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Bean
public DataSource dataSource() {
// 使用加载到的属性值
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
return ds;
}
}
3. @ComponentScan —— 组件扫描
- 核心作用:指定 Spring 扫描哪些包下的注解(
@Component,@Service,@Controller,@Repository)。 - 等价 XML:
<context:component-scan base-package="com.hg"/> - 关键属性:
value/basePackages:要扫描的包名数组。excludeFilters:排除某些组件。includeFilters:只包含某些组件。
- 注意:如果配置类在根包下,有时可以省略,但显式声明更稳妥。
💻 示例代码
@Configuration
// 扫描 com.hg 及其子包
@ComponentScan("com.hg")
// 或者排除特定的控制器
// @ComponentScan(value = "com.hg", excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Controller.class))
public class ScanConfig {
// Spring 会自动发现 @Service, @Controller 等并注册为 Bean
}
4. @Import —— 导入配置类
- 核心作用:导入其他的配置类,实现配置的分模块管理;或者直接导入普通类将其注册为 Bean。
- 等价 XML:
<import resource="spring-dao.xml"/> - 关键属性:
value:要导入的类的 Class 对象。
- 高阶用法:
- 导入
@Configuration类:合并配置。 - 导入普通类:Spring 会直接将该类注册为 Bean,ID 为类名首字母小写。
- 导入
ImportSelector或ImportBeanDefinitionRegistrar:实现高级动态注册(如@EnableXXX的原理)。
- 导入
💻 示例代码
子配置类 RedisConfig.java:
@Configuration
public class RedisConfig {
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory();
}
}
主配置类 AllConfig.java:
@Configuration
@ComponentScan("com.hg")
// 导入其他配置类,相当于把 RedisConfig 里的 Bean 也加进来了
@Import({RedisConfig.class, MyDataSourceConfig.class})
public class AllConfig {
// 这里可以直接注入 RedisConfig 中定义的 Bean
}
5. @Configuration —— 配置类标识
- 核心作用:告诉 Spring 这是一个配置类,替代
applicationContext.xml的地位。 - 特点:
- 类中通常包含
@Bean方法。 - 会被 CGLIB 代理,保证
@Bean方法调用的单例性(即方法内互相调用不会创建新对象)。
- 类中通常包含
- 等价物:整个
applicationContext.xml文件。
💻 示例代码
// 必须加这个注解,否则 @Bean 不生效
@Configuration
@ComponentScan("com.hg")
@PropertySource("classpath:app.properties")
public class SpringMainConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
🚀 模块二:Spring Boot 入门
一、Spring Boot 介绍
- 定义:Spring + Spring MVC 的快速开发脚手架。不是对 Spring 功能的增强,而是简化配置。
- 三大特点:
- 简化 XML:零配置或极少配置。
- 简化 Maven:通过 Starter 解决依赖版本冲突。
- 内嵌 Tomcat:无需部署 WAR,直接运行 JAR。
二、入门案例实操
1. pom.xml 核心配置
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<!-- ① 继承父工程:锁定所有依赖版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<!-- ② 引入 Web 启动器:包含 Spring MVC, Tomcat, Jackson -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
2. Controller 层代码
package com.hg.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // 组合注解:@Controller + @ResponseBody
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello Spring Boot! 欢迎进入快速开发时代 🚀";
}
}
3. 启动类 (关键!)
⚠️ 包结构要求:启动类必须在所有业务包(controller, service, mapper)的父级目录,否则默认扫描不到。
package com.hg; // 【重要】必须是根包
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 核心:开启自动配置 + 组件扫描
public class SpringbootHelloworldApp {
public static void main(String[] args) {
// 启动应用,返回 ApplicationContext
SpringApplication.run(SpringbootHelloworldApp.class, args);
System.out.println("✅ 启动成功!访问 http://localhost:8080/hello");
}
}
三、Spring Boot 启动器 (Starter)
1. 概念
Starter 是一组便捷的依赖描述符。引入一个 Starter,就等于引入了该功能所需的所有 Jar 包和自动配置类。
2. 命名规范
| 类型 | 命名格式 | 示例 | 说明 |
|---|---|---|---|
| 官方 | spring-boot-starter-模块名 |
spring-boot-starter-web |
Spring 官方提供 |
| 第三方 | 模块名-spring-boot-starter |
mybatis-spring-boot-starter |
第三方组织提供 (如 MyBatis-Plus) |
💡 知识补充:常用 Starter
spring-boot-starter-data-jpa: JPA 数据访问spring-boot-starter-data-redis: Redis 支持spring-boot-starter-test: 测试支持 (JUnit, Mockito)spring-boot-starter-security: 安全认证
四、配置文件 (Properties vs YML)
1. application.properties
# 修改 Tomcat 端口
server.port=8090
# 修改项目上下文路径
server.servlet.context-path=/springboot_helloworld
# 设置日志级别
logging.level.com.hg=debug
2. application.yml (推荐)
语法重点:
- 层级用缩进(空格,不能 Tab)。
- 键值对用
:(冒号后必须有空格)。 - 大小写敏感。
server:
port: 8090
servlet:
context-path: /springboot_helloworld
logging:
level:
com.hg: debug
五、两种发布方式
1. JAR 方式 (主流 ✅)
- 原理:内嵌 Tomcat,自包含运行环境。
- 步骤:
- 插件配置 (
pom.xml):<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- 自动寻找 main 方法作为入口 --> </plugin> </plugins> </build> - 打包:
mvn clean package - 运行:
java -jar target/springboot_helloworld.jar
- 插件配置 (
2. WAR 方式 (传统部署)
- 场景:需部署到外部独立的 Tomcat/Jetty 服务器。
- 步骤详解:
- 修改打包方式:
<packaging>war</packaging> - 排除内嵌 Tomcat (防止冲突):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> <!-- 打包时不包含,运行时由外部 Tomcat 提供 --> </dependency> - 修改启动类 (继承初始化器):
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; // 必须继承 SpringBootServletInitializer public class SpringBootHelloworldApp extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { // 指向当前的启动类 return builder.sources(SpringBootHelloworldApp.class); } // 保留 main 方法,方便本地调试 public static void main(String[] args) { SpringApplication.run(SpringBootHelloworldApp.class, args); } } - 部署:将生成的
.war包复制到外部 Tomcat 的webapps/目录下,启动 Tomcat 即可。
- 修改打包方式:
🛠️ 模块三:进阶整合 (异常、测试、多环境、日志)
一、全局异常处理器
1. 前后端分离架构 (重点 🔥)
返回 JSON 数据给前端,而不是跳转错误页面。
- 核心注解:
@ControllerAdvice:全局增强,拦截所有 Controller 的异常。@ExceptionHandler:指定捕获的异常类型。@ResponseBody:返回数据而非视图。
package com.hg.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionResolver {
// 捕获所有 Exception 类型的异常
@ExceptionHandler({Exception.class})
@ResponseBody
public Map<String, Object> handleException(Exception e) {
e.printStackTrace(); // 控制台打印堆栈
Map<String, Object> result = new HashMap<>();
result.put("code", 500);
result.put("msg", "服务器内部错误:" + e.getMessage());
result.put("data", null);
return result;
}
// 也可以针对特定异常做处理
@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public Map<String, Object> handleArithmeticException(ArithmeticException e) {
Map<String, Object> result = new HashMap<>();
result.put("code", 400);
result.put("msg", "数学计算错误:" + e.getMessage());
return result;
}
}
2. 非前后端分离 (了解)
实现 HandlerExceptionResolver 接口,返回 ModelAndView 跳转到错误页。
(注:现代开发极少使用此方式,通常直接用上面的注解方式)
二、整合 JUnit 单元测试
1. 依赖引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2. 三种启动方式的对比与代码
方式 A:Main 方法手动启动 (原始)
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = ctx.getBean(UserService.class);
方式 B:Spring 整合 JUnit (老式)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OldSpringTest { ... }
方式 C:Spring Boot 整合 JUnit (推荐 ✅)
- 特点:
@SpringBootTest自动加载配置,无需 XML,无需 Runner。
package com.hg.test;
import com.hg.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
// 加载完整的 Spring 上下文,默认查找启动类
@SpringBootTest(classes = SpringbootHelloworldApp.class)
public class SpringbootJunitTest {
@Autowired
private UserService userService;
@Test
public void testFindUser() {
// 直接调用,如果配置正确,userService 不为 null
assert userService != null;
System.out.println("测试通过!用户服务已注入。");
// 执行具体业务逻辑测试
// User user = userService.findById(1L);
}
}
三、多环境配置 (Profiles)
解决开发、测试、生产环境配置不同的问题。
1. 文件命名规则
主文件:application.yml
子文件:application-环境名.yml
application-dev.yml(开发环境)application-test.yml(测试环境)application-prod.yml(生产环境)
2. 配置文件内容示例
application-dev.yml:
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/dev_db
username: dev_user
password: dev_pass
application-test.yml:
server:
port: 8082
spring:
datasource:
url: jdbc:mysql://test-server:3306/test_db
username: test_user
password: test_pass
application-prod.yml:
server:
port: 8083
spring:
datasource:
url: jdbc:mysql://prod-cluster:3306/prod_db
username: prod_user
password: prod_strong_pass
3. 激活环境
在主文件 application.yml 中指定默认激活的环境:
spring:
profiles:
active: dev # 默认激活 dev 环境,修改此处可切换
💡 命令行动态切换:
打包后运行:java -jar app.jar --spring.profiles.active=prod
这样无需修改配置文件即可切换环境。
四、整合 Logback 日志
Spring Boot 默认使用 Logback 作为日志框架,无需额外引入依赖。
1. 配置文件 src/main/resources/logback-spring.xml
使用 -spring 后缀可以让 Logback 读取 Spring Boot 的配置(如 ${APP_NAME})。
<configuration scan="true" scanPeriod="30 seconds">
<!-- 定义变量 -->
<property name="LOG_HOME" value="./logs"/>
<property name="APP_NAME" value="springboot_helloworld"/>
<!-- 控制台输出格式 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出:按天滚动 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天生成一个文件,保留30天 -->
<fileNamePattern>${LOG_HOME}/${APP_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 根节点配置 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<!-- 单独配置某个包的日志级别 -->
<logger name="com.hg.service" level="DEBUG"/>
<logger name="org.springframework.web" level="INFO"/>
</configuration>
2. 代码中使用日志
推荐使用 Lombok 的 @Slf4j 简化代码。
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j // 自动生成 private static final Logger log = ...
@RestController
public class LogDemoController {
@GetMapping("/log-test")
public String logTest() {
log.trace("这是 TRACE 级别日志 (最详细)");
log.debug("这是 DEBUG 级别日志 (调试用)");
log.info("这是 INFO 级别日志 (一般信息)");
log.warn("这是 WARN 级别日志 (警告)");
log.error("这是 ERROR 级别日志 (错误)");
try {
int i = 1 / 0;
} catch (Exception e) {
log.error("发生算术异常", e); // 自动打印堆栈
}
return "日志已记录,请查看 logs 目录或控制台";
}
}
🎁 额外补充知识点 (加分项)
1. @ConfigurationProperties vs @Value
@Value:逐个注入,适合少量配置。@ConfigurationProperties:批量注入,支持松散绑定(my-prop对应myProp),支持 JSR303 校验,推荐用于复杂配置。
@Component
@ConfigurationProperties(prefix = "myapp")
@Data // Lombok
public class MyAppProperties {
private String name;
private int version;
private List<String> features;
}
// yml: myapp.name=xxx, myapp.features[0]=a
2. Spring Boot 自动配置原理简述
- 启动类上的
@EnableAutoConfiguration是关键。 - 它通过
SPI机制加载META-INF/spring.factories(Spring Boot 2.7+ 改为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports) 中的所有配置类。 - 这些配置类上使用
@ConditionalOnClass,@ConditionalOnMissingBean等注解,按需加载。例如:只有当类路径下有JdbcTemplate类时,才自动配置数据源相关 Bean。
3. 热部署 (DevTools)
开发效率神器,修改代码后自动重启应用。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
- IDEA 设置:File -> Settings -> Build, Execution, Deployment -> Compiler -> 勾选 “Build project automatically”。
- Registry:Ctrl+Shift+Alt+/ -> Registry -> 勾选 “compiler.automake.allow.when.app.running”。
🧩 模块四:Spring Boot 自动配置原理与模拟实现
本模块深入探讨 Spring Boot 的核心魔法——“自动配置”(AutoConfiguration)。我们将从注解解析开始,逐步模拟从硬编码到读取配置文件的全过程。
一、核心注解解析
Spring Boot 的自动配置主要由 @SpringBootApplication 注解实现,该注解是一个组合注解:
| 注解 | 作用原理 |
|---|---|
| @SpringBootConfiguration | 组合了 @Configuration,标识启动类本身就是一个配置类。 |
| @ComponentScan | 扫描启动类所在包及其子包下的所有组件(@Component, @Service 等)。 |
| @EnableAutoConfiguration | 核心:开启自动配置。它由 @AutoConfigurationPackage 和 @Import 组成。 |
@AutoConfigurationPackage:自动扫描启动类所在的包及子包中的所有类,生成实体 Bean 并交给 Spring IOC 容器管理(相当于以前的<context:component-scan>)。@Import({AutoConfigurationImportSelector.class}):这是自动配置的“引擎”。它会加载META-INF/spring.factories文件,调用其中定义的自动配置类,完成 Bean 的自动注入。
二、模拟自动配置实现 (分步进阶)
我们将通过 5 个步骤,模拟 Spring Boot 是如何从硬编码走向解耦的自动配置。
2.1 什么是自动配置
遵循“约定大于配置”的原则。在 Spring Boot 启动后,起步依赖(Starter)中的一些 Bean 对象会自动注入到 IOC 容器中。
- 效果:开发者只需在配置文件(如
application.yml)中提供必要的信息(如数据库 URL、用户名、密码),无需手动配置繁琐的 Bean。
2.2 创建基础工程
1. pom.xml 配置
引入 Web 依赖和 Lombok(用于简化代码)。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- 从仓库查找父工程 -->
</parent>
<groupId>com.hg</groupId>
<artifactId>06_springboot_AutoConfiguration</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
2. 启动类 App.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication // 包含了 @Configuration, @EnableAutoConfiguration, @ComponentScan
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext ac = SpringApplication.run(App.class, args);
// 如果配置成功,这里可以直接获取 Bean
System.out.println("Spring Boot 启动成功!");
}
}
2.3 第一阶段:硬编码配置类 (耦合度高)
原理:如果配置类不在启动类的包及其子包下,必须通过 @Import 显式导入。
1. 创建一个测试 Bean (MyBean)
package com.hg.bean;
public class MyBean {
public void myMethod(){
System.out.println("MyBean.myMethod 方法执行了...... 🚀");
}
}
2. 创建配置类 (MyBeanAutoConfiguration)
该类负责将 MyBean 注册到容器。
package com.hg.config;
import com.hg.bean.MyBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 模拟一个自动配置类
public class MyBeanAutoConfiguration {
@Bean
public MyBean myBean(){
return new MyBean();
}
}
3. 在启动类中使用 @Import 导入
@SpringBootApplication
@Import(MyBeanAutoConfiguration.class) // 硬编码导入,如果有很多配置类,这里会非常长
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext ac = SpringApplication.run(App.class, args);
// 获取 Bean 并调用方法
ac.getBean("myBean", MyBean.class).myMethod();
}
}
2.4 第二阶段:使用 ImportSelector 接口 (解耦第一步)
原理:ImportSelector 接口允许我们动态选择要导入的类的全限定名。这样即使有多个配置类,也只需导入 ImportSelector 的实现类。
1. 创建 MyImportSelector 实现类
package com.hg.config;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
// 实现 ImportSelector 接口
public class MyImportSelector implements ImportSelector {
/**
* selectImports 方法返回一个字符串数组
* 数组中的内容就是要导入的类的全限定名 (Fully Qualified Name)
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 这里可以写逻辑判断,目前先写死
return new String[]{
"com.hg.config.MyBeanAutoConfiguration"
// 如果有更多配置类,直接加在这里即可,启动类无需改动
};
}
}
2. 修改启动类
@SpringBootApplication
@Import(MyImportSelector.class) // 现在只导入这个选择器
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext ac = SpringApplication.run(App.class, args);
ac.getBean("myBean", MyBean.class).myMethod();
}
}
2.5 第三阶段:读取配置文件 (模拟 spring.factories)
原理:将需要加载的配置类全限定名写在配置文件中,代码通过 IO 流读取。这是 Spring Boot 实现“开箱即用”的核心机制。
1. 创建配置文件 resources/spring.factories
(注意:这是模拟文件,Spring Boot 官方使用的是 META-INF/spring.factories)
# 写入配置类的全限定名
com.hg.config.MyBeanAutoConfiguration
2. 修改 MyImportSelector 读取文件
package com.hg.config;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> imports = new ArrayList<>();
// 1. 获取类加载器,读取 classpath 下的 spring.factories 文件
InputStream is = MyImportSelector.class.getClassLoader()
.getResourceAsStream("spring.factories");
if (is == null) return imports.toArray(new String[0]);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line = null;
try {
// 2. 逐行读取文件内容
while ((line = br.readLine()) != null) {
// 去除空格并添加到列表
imports.add(line.trim());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return imports.toArray(new String[0]);
}
}
2.6 第四阶段:自定义注解 (可选,提升逼格)
原理:为了代码美观,我们可以封装一个注解,将 @Import 隐藏在注解内部。
1. 创建自定义注解 MyEnableAutoConfiguration
package com.hg.annotation;
import com.hg.config.MyImportSelector;
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 定义注解的保留策略为运行时
@Retention(RetentionPolicy.RUNTIME)
// 定义注解可以修饰类、接口、枚举
@Target(ElementType.TYPE)
// 关键:在此处导入 MyImportSelector
@Import(MyImportSelector.class)
public @interface MyEnableAutoConfiguration {
// 这里可以定义属性,类似 @EnableAutoConfiguration 的 exclude 属性
}
2. 启动类使用自定义注解
@SpringBootApplication
@MyEnableAutoConfiguration // 替换之前的 @Import
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext ac = SpringApplication.run(App.class, args);
ac.getBean("myBean", MyBean.class).myMethod();
}
}
2.7 第五阶段:使用 Spring Boot 官方机制
原理:Spring Boot 默认会扫描 classpath:/META-INF/spring.factories。我们只需要将配置文件放在标准位置,甚至不需要写 @Import,因为 @EnableAutoConfiguration 已经帮我们做了。
1. 在 resources 目录下新建 META-INF/spring.factories
(这是 Spring Boot 约定的配置文件路径)
# 键是固定的 org.springframework.boot.autoconfigure.EnableAutoConfiguration
# 值是我们自定义的配置类全限定名
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hg.config.MyBeanAutoConfiguration
2. 启动类恢复最纯净状态
// 此时 @EnableAutoConfiguration 已经在 @SpringBootApplication 中
// 它会自动扫描 META-INF/spring.factories
@SpringBootApplication
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext ac = SpringApplication.run(App.class, args);
ac.getBean("myBean", MyBean.class).myMethod();
}
}
💡 知识点总结与补充
- SPI 思想:Spring Boot 的自动配置本质上是一种 SPI(Service Provider Interface)机制。框架定义标准(
spring.factories),第三方提供实现(配置类)。 - 条件化配置:真正的 Spring Boot 自动配置类(如
DataSourceAutoConfiguration)上会有大量的@ConditionalOnXXX注解(例如@ConditionalOnClass),意思是“当类路径下有某类时才进行配置”。这保证了如果没有引入 MySQL 依赖,数据源配置就不会生效,不会报错。 - Spring Boot 2.4+ 变更:从 Spring Boot 2.4 开始,
spring.factories的自动加载机制逐渐被META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件机制取代(即直接在文件中写类名,不再需要键值对格式),但在学习原理时,理解spring.factories的加载过程依然是核心。
更多推荐




所有评论(0)