Java SpringBoot 面试题集
总题数: 26道 | 重点领域: 自动配置、Starter、微服务 | 难度分布: 中级
本文档整理了 Java Spring Boot 的完整26道面试题目,涵盖自动配置、Starter机制、微服务开发等各个方面。
面试题目列表
1. Spring Boot 工程启动以后,我希望将数据库中已有的固定内容,打入到 Redis 缓存中,请问如何处理?
答案:
有以下几种方式可以在 Spring Boot 启动后执行初始化操作:
方式一:实现 CommandLineRunner 接口
1@Component2@Order(1) // 多个实现时可以指定执行顺序3public class RedisInitRunner implements CommandLineRunner {4 @Autowired5 private UserRepository userRepository;6 @Autowired7 private RedisTemplate<String, Object> redisTemplate;8 9 @Override10 public void run(String... args) throws Exception {11 List<User> users = userRepository.findAll();12 users.forEach(user -> {13 redisTemplate.opsForValue().set("user:" + user.getId(), user);14 });15 System.out.println("Redis 缓存初始化完成");16 }17}方式二:实现 ApplicationRunner 接口
1@Component2public class RedisInitApplicationRunner implements ApplicationRunner {3 @Autowired4 private DataService dataService;5 @Autowired6 private RedisTemplate<String, Object> redisTemplate;7 8 @Override9 public void run(ApplicationArguments args) throws Exception {10 // 加载固定配置数据11 Map<String, Object> configData = dataService.loadConfigData();12 configData.forEach((key, value) -> {13 redisTemplate.opsForValue().set(key, value);14 });15 }16}方式三:使用 @PostConstruct 注解
1@Component2public class RedisInitializer {3 @Autowired4 private DataRepository dataRepository;5 @Autowired6 private RedisTemplate<String, Object> redisTemplate;7 8 @PostConstruct9 public void init() {10 // 注意:此时 Spring 容器可能还未完全初始化11 List<Data> dataList = dataRepository.findAll();12 dataList.forEach(data -> {13 redisTemplate.opsForValue().set("data:" + data.getId(), data);14 });15 }16}方式四:监听 ApplicationReadyEvent 事件
1@Component2public class RedisInitListener {3 @Autowired4 private RedisTemplate<String, Object> redisTemplate;5 @Autowired6 private ConfigRepository configRepository;7 8 @EventListener(ApplicationReadyEvent.class)9 public void onApplicationReady() {10 // 应用完全启动后执行11 List<Config> configs = configRepository.findAll();12 configs.forEach(config -> {13 redisTemplate.opsForHash().put("configs", config.getKey(), config.getValue());14 });15 }16}推荐方案: 使用 ApplicationRunner 或监听 ApplicationReadyEvent,因为它们在应用完全启动后执行,此时所有 Bean 都已初始化完成。
2. 说说 Springboot 的启动流程?
答案:
Spring Boot 的启动流程主要分为以下几个阶段:
1. 启动入口
1@SpringBootApplication2public class Application {3 public static void main(String[] args) {4 SpringApplication.run(Application.class, args);5 }6}2. 详细启动流程
11. 创建 SpringApplication 实例2 ├─ 推断应用类型(Servlet、Reactive、None)3 ├─ 加载 ApplicationContextInitializer4 ├─ 加载 ApplicationListener5 └─ 推断主启动类672. 执行 run() 方法8 ├─ 创建 StopWatch(记录启动时间)9 ├─ 配置 Headless 属性10 ├─ 获取并启动 SpringApplicationRunListeners11 ├─ 准备环境(Environment)12 │ ├─ 创建环境对象13 │ ├─ 配置环境14 │ └─ 发布 ApplicationEnvironmentPreparedEvent15 ├─ 打印 Banner16 ├─ 创建 ApplicationContext17 │ ├─ 根据应用类型创建对应的上下文18 │ └─ AnnotationConfigServletWebServerApplicationContext19 ├─ 准备 ApplicationContext20 │ ├─ 设置环境21 │ ├─ 后置处理(postProcessApplicationContext)22 │ ├─ 应用初始化器(applyInitializers)23 │ └─ 发布 ApplicationContextInitializedEvent24 ├─ 加载 Bean 定义25 │ ├─ 加载主配置类26 │ └─ 发布 ApplicationPreparedEvent27 ├─ 刷新上下文(refresh)28 │ ├─ 准备刷新29 │ ├─ 获取 BeanFactory30 │ ├─ 准备 BeanFactory31 │ ├─ 后置处理 BeanFactory32 │ ├─ 调用 BeanFactoryPostProcessor33 │ ├─ 注册 BeanPostProcessor34 │ ├─ 初始化消息源35 │ ├─ 初始化事件广播器36 │ ├─ 刷新特定上下文(创建 Web 容器)37 │ ├─ 注册监听器38 │ ├─ 实例化所有非懒加载单例 Bean39 │ └─ 完成刷新(发布 ContextRefreshedEvent)40 ├─ 刷新后处理(afterRefresh)41 ├─ 发布 ApplicationStartedEvent42 ├─ 调用 Runners(CommandLineRunner、ApplicationRunner)43 ├─ 发布 ApplicationReadyEvent44 └─ 启动完成3. 核心步骤说明
- 推断应用类型:根据 classpath 中的类判断是 Web 应用还是普通应用
- 加载初始化器和监听器:从
META-INF/spring.factories加载 - 准备环境:加载配置文件(application.properties/yml)
- 创建上下文:根据应用类型创建对应的 ApplicationContext
- 自动配置:通过
@EnableAutoConfiguration加载自动配置类 - 启动内嵌容器:如 Tomcat、Jetty、Undertow
- 执行 Runners:执行用户自定义的初始化逻辑
4. 关键源码位置
1// SpringApplication.java2public ConfigurableApplicationContext run(String... args) {3 StopWatch stopWatch = new StopWatch();4 stopWatch.start();5 ConfigurableApplicationContext context = null;6 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();7 configureHeadlessProperty();8 SpringApplicationRunListeners listeners = getRunListeners(args);9 listeners.starting();10 try {11 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);12 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);13 Banner printedBanner = printBanner(environment);14 context = createApplicationContext();15 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,16 new Class[] { ConfigurableApplicationContext.class }, context);17 prepareContext(context, environment, listeners, applicationArguments, printedBanner);18 refreshContext(context);19 afterRefresh(context, applicationArguments);20 stopWatch.stop();21 listeners.started(context);22 callRunners(context, applicationArguments);23 } catch (Throwable ex) {24 handleRunFailure(context, ex, exceptionReporters, listeners);25 throw new IllegalStateException(ex);26 }27 listeners.running(context);28 return context;29}3. 什么是 Spring Boot?
答案:
Spring Boot 是由 Pivotal 团队提供的基于 Spring 框架的全新框架,旨在简化 Spring 应用的初始搭建和开发过程。
核心理念:
- 约定优于配置(Convention over Configuration):提供默认配置,减少开发者配置工作
- 开箱即用(Out of the Box):快速启动和运行应用
- 独立运行:内嵌 Web 容器,可以独立运行
主要特点:
-
简化配置
- 无需繁琐的 XML 配置
- 使用注解和自动配置
- 提供默认配置值
-
快速开发
- 提供大量 Starter 依赖
- 自动配置常用框架
- 快速搭建项目骨架
-
独立运行
- 内嵌 Tomcat、Jetty、Undertow
- 打包成可执行 JAR
- 无需部署到外部容器
-
生产就绪
- 提供健康检查、监控、指标
- Actuator 端点
- 外部化配置
与传统 Spring 的区别:
| 特性 | 传统 Spring | Spring Boot |
|---|---|---|
| 配置方式 | 大量 XML 配置 | 注解 + 自动配置 |
| 依赖管理 | 手动管理版本 | Starter 统一管理 |
| 容器部署 | 需要外部容器 | 内嵌容器 |
| 启动方式 | 部署 WAR 包 | 运行 JAR 包 |
| 开发效率 | 配置繁琐 | 快速开发 |
示例:
1// 传统 Spring 需要大量配置2// Spring Boot 只需一个注解3@SpringBootApplication4public class Application {5 public static void main(String[] args) {6 SpringApplication.run(Application.class, args);7 }8}4. Spring Boot 的核心特性有哪些?
答案:
1. 自动配置(Auto-Configuration)
- 根据项目依赖自动配置 Spring 应用
- 通过
@EnableAutoConfiguration实现 - 可以通过
exclude排除不需要的自动配置
1@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})2. Starter 依赖(Starter Dependencies)
- 预定义的依赖描述符
- 简化 Maven/Gradle 配置
- 常用 Starter:
spring-boot-starter-web:Web 开发spring-boot-starter-data-jpa:JPA 数据访问spring-boot-starter-security:安全框架spring-boot-starter-test:测试框架
3. 内嵌容器(Embedded Server)
- 内置 Tomcat、Jetty、Undertow
- 无需部署 WAR 包
- 直接运行 JAR 包
1<dependency>2 <groupId>org.springframework.boot</groupId>3 <artifactId>spring-boot-starter-web</artifactId>4 <!-- 默认使用 Tomcat -->5</dependency>4. Actuator 监控
- 提供生产级别的监控和管理功能
- 健康检查、指标收集、审计
- 常用端点:
/actuator/health:健康状态/actuator/metrics:应用指标/actuator/info:应用信息/actuator/env:环境变量
5. 外部化配置(Externalized Configuration)
- 支持多种配置源
- 配置文件:application.properties/yml
- 环境变量、命令行参数
- 配置优先级机制
1# application.yml2server:3 port: 80804spring:5 datasource:6 url: jdbc:mysql://localhost:3306/db6. Spring Boot CLI
- 命令行工具
- 快速原型开发
- Groovy 脚本支持
7. 开发者工具(DevTools)
- 热部署(自动重启)
- LiveReload 支持
- 开发时配置
1<dependency>2 <groupId>org.springframework.boot</groupId>3 <artifactId>spring-boot-devtools</artifactId>4 <optional>true</optional>5</dependency>8. 简化的测试
@SpringBootTest注解- 自动配置测试环境
- Mock 支持
1@SpringBootTest2class ApplicationTests {3 @Test4 void contextLoads() {5 }6}5. Spring Boot 是如何通过 main 方法启动 web 项目的?
答案:
Spring Boot 通过 SpringApplication.run() 方法启动 Web 项目,核心原理如下:
1. 启动入口
1@SpringBootApplication2public class Application {3 public static void main(String[] args) {4 SpringApplication.run(Application.class, args);5 }6}2. 启动原理
第一步:创建 SpringApplication 对象
1public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {2 this.resourceLoader = resourceLoader;3 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));4 // 推断应用类型:SERVLET、REACTIVE、NONE5 this.webApplicationType = WebApplicationType.deduceFromClasspath();6 // 加载初始化器7 setInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class));8 // 加载监听器9 setListeners(getSpringFactoriesInstances(ApplicationListener.class));10 // 推断主启动类11 this.mainApplicationClass = deduceMainApplicationClass();12}第二步:推断应用类型
1static WebApplicationType deduceFromClasspath() {2 // 如果存在 DispatcherHandler 且不存在 DispatcherServlet,则为 REACTIVE3 if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) 4 && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)) {5 return WebApplicationType.REACTIVE;6 }7 // 如果不存在 Servlet 相关类,则为 NONE8 for (String className : SERVLET_INDICATOR_CLASSES) {9 if (!ClassUtils.isPresent(className, null)) {10 return WebApplicationType.NONE;11 }12 }13 // 否则为 SERVLET14 return WebApplicationType.SERVLET;15}第三步:创建 ApplicationContext
1protected ConfigurableApplicationContext createApplicationContext() {2 Class<?> contextClass = this.applicationContextClass;3 if (contextClass == null) {4 switch (this.webApplicationType) {5 case SERVLET:6 // Web 应用创建 AnnotationConfigServletWebServerApplicationContext7 contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);8 break;9 case REACTIVE:10 contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);11 break;12 default:13 contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);14 }15 }16 return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);17}第四步:刷新上下文,启动 Web 容器
1// ServletWebServerApplicationContext.java2@Override3protected void onRefresh() {4 super.onRefresh();5 try {6 // 创建 Web 服务器(Tomcat、Jetty、Undertow)7 createWebServer();8 } catch (Throwable ex) {9 throw new ApplicationContextException("Unable to start web server", ex);10 }11}1213private void createWebServer() {14 WebServer webServer = this.webServer;15 ServletContext servletContext = getServletContext();16 if (webServer == null && servletContext == null) {17 // 获取 ServletWebServerFactory(如 TomcatServletWebServerFactory)18 ServletWebServerFactory factory = getWebServerFactory();19 // 创建 Web 服务器并启动20 this.webServer = factory.getWebServer(getSelfInitializer());21 }22}第五步:启动内嵌 Tomcat
1// TomcatServletWebServerFactory.java2@Override3public WebServer getWebServer(ServletContextInitializer... initializers) {4 Tomcat tomcat = new Tomcat();5 // 配置 Tomcat6 Connector connector = new Connector(this.protocol);7 connector.setPort(getPort());8 tomcat.getService().addConnector(connector);9 // 配置 Context10 prepareContext(tomcat.getHost(), initializers);11 // 返回 TomcatWebServer12 return getTomcatWebServer(tomcat);13}1415// TomcatWebServer.java16public TomcatWebServer(Tomcat tomcat, boolean autoStart) {17 this.tomcat = tomcat;18 this.autoStart = autoStart;19 // 启动 Tomcat20 initialize();21}2223private void initialize() {24 // 启动 Tomcat 服务器25 this.tomcat.start();26}3. 关键点总结
- 应用类型推断:根据 classpath 中的类判断是否为 Web 应用
- 创建专用上下文:Web 应用使用
ServletWebServerApplicationContext - 自动配置 Web 容器:通过
ServletWebServerFactory创建内嵌容器 - 启动容器:在
onRefresh()阶段启动 Tomcat/Jetty/Undertow - 注册 DispatcherServlet:自动配置 Spring MVC 的前端控制器
4. 为什么可以通过 main 方法启动?
- Spring Boot 将 Web 容器(Tomcat)作为一个普通对象内嵌到应用中
- 不需要将应用部署到外部容器
- 通过 Java 应用的方式启动,容器随应用启动而启动
- 打包成可执行 JAR,包含所有依赖和内嵌容器
6. SpringBoot 是如何实现自动配置的?
答案:
Spring Boot 自动配置是通过 @EnableAutoConfiguration 注解实现的,核心原理是条件化配置和 SPI 机制。
1. 核心注解
1@SpringBootApplication2 └─ @EnableAutoConfiguration3 └─ @Import(AutoConfigurationImportSelector.class)1@Target(ElementType.TYPE)2@Retention(RetentionPolicy.RUNTIME)3@Documented4@Inherited5@SpringBootConfiguration6@EnableAutoConfiguration // 核心注解7@ComponentScan8public @interface SpringBootApplication {9}2. 自动配置原理
第一步:加载自动配置类
1// AutoConfigurationImportSelector.java2protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, 3 AnnotationAttributes attributes) {4 // 从 META-INF/spring.factories 加载自动配置类5 List<String> configurations = SpringFactoriesLoader.loadFactoryNames(6 EnableAutoConfiguration.class, 7 getBeanClassLoader()8 );9 return configurations;10}第二步:读取 spring.factories 文件
1# spring-boot-autoconfigure.jar 中的 META-INF/spring.factories2org.springframework.boot.autoconfigure.EnableAutoConfiguration=\3org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\4org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\5org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\6...第三步:条件化配置
自动配置类使用 @Conditional 注解进行条件判断:
1@Configuration2@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})3@ConditionalOnMissingBean(DataSource.class)4@EnableConfigurationProperties(DataSourceProperties.class)5public class DataSourceAutoConfiguration {6 7 @Bean8 @ConditionalOnMissingBean9 public DataSource dataSource(DataSourceProperties properties) {10 return properties.initializeDataSourceBuilder().build();11 }12}3. 常用条件注解
| 注解 | 说明 |
|---|---|
@ConditionalOnClass | 类路径存在指定类时生效 |
@ConditionalOnMissingClass | 类路径不存在指定类时生效 |
@ConditionalOnBean | 容器中存在指定 Bean 时生效 |
@ConditionalOnMissingBean | 容器中不存在指定 Bean 时生效 |
@ConditionalOnProperty | 配置文件中存在指定属性时生效 |
@ConditionalOnResource | 类路径存在指定资源时生效 |
@ConditionalOnWebApplication | 是 Web 应用时生效 |
@ConditionalOnNotWebApplication | 不是 Web 应用时生效 |
4. 自动配置示例
Redis 自动配置:
1@Configuration2@ConditionalOnClass(RedisOperations.class)3@EnableConfigurationProperties(RedisProperties.class)4@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})5public class RedisAutoConfiguration {6 7 @Bean8 @ConditionalOnMissingBean(name = "redisTemplate")9 public RedisTemplate<Object, Object> redisTemplate(10 RedisConnectionFactory redisConnectionFactory) {11 RedisTemplate<Object, Object> template = new RedisTemplate<>();12 template.setConnectionFactory(redisConnectionFactory);13 return template;14 }15 16 @Bean17 @ConditionalOnMissingBean18 public StringRedisTemplate stringRedisTemplate(19 RedisConnectionFactory redisConnectionFactory) {20 StringRedisTemplate template = new StringRedisTemplate();21 template.setConnectionFactory(redisConnectionFactory);22 return template;23 }24}5. 配置属性绑定
1@ConfigurationProperties(prefix = "spring.datasource")2public class DataSourceProperties {3 private String url;4 private String username;5 private String password;6 private String driverClassName;7 // getters and setters8}1# application.yml2spring:3 datasource:4 url: jdbc:mysql://localhost:3306/db5 username: root6 password: 1234567 driver-class-name: com.mysql.cj.jdbc.Driver6. 自动配置流程总结
11. @SpringBootApplication2 └─ @EnableAutoConfiguration3 └─ @Import(AutoConfigurationImportSelector.class)452. AutoConfigurationImportSelector6 └─ 读取 META-INF/spring.factories7 └─ 加载所有自动配置类893. 条件判断10 └─ @ConditionalOnXxx 注解11 └─ 满足条件则生效12134. 属性绑定14 └─ @ConfigurationProperties15 └─ 绑定配置文件属性16175. 创建 Bean18 └─ 注册到 Spring 容器7. 如何排除自动配置
1// 方式一:使用 exclude 属性2@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})34// 方式二:配置文件5spring:6 autoconfigure:7 exclude:8 - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration8. 查看生效的自动配置
1# application.yml2debug: true # 启动时打印自动配置报告或使用 Actuator:
1GET /actuator/conditions7. Spring Boot 支持哪些嵌入 Web 容器?
答案:
Spring Boot 支持以下三种主流的嵌入式 Web 容器:
1. Tomcat(默认)
1<dependency>2 <groupId>org.springframework.boot</groupId>3 <artifactId>spring-boot-starter-web</artifactId>4 <!-- 默认包含 Tomcat -->5</dependency>配置示例:
1server:2 port: 80803 tomcat:4 max-threads: 2005 min-spare-threads: 106 max-connections: 100007 accept-count: 1002. Jetty
1<dependency>2 <groupId>org.springframework.boot</groupId>3 <artifactId>spring-boot-starter-web</artifactId>4 <exclusions>5 <!-- 排除 Tomcat -->6 <exclusion>7 <groupId>org.springframework.boot</groupId>8 <artifactId>spring-boot-starter-tomcat</artifactId>9 </exclusion>10 </exclusions>11</dependency>12<!-- 添加 Jetty -->13<dependency>14 <groupId>org.springframework.boot</groupId>15 <artifactId>spring-boot-starter-jetty</artifactId>16</dependency>配置示例:
1server:2 port: 80803 jetty:4 threads:5 max: 2006 min: 103. Undertow
1<dependency>2 <groupId>org.springframework.boot</groupId>3 <artifactId>spring-boot-starter-web</artifactId>4 <exclusions>5 <!-- 排除 Tomcat -->6 <exclusion>7 <groupId>org.springframework.boot</groupId>8 <artifactId>spring-boot-starter-tomcat</artifactId>9 </exclusion>10 </exclusions>11</dependency>12<!-- 添加 Undertow -->13<dependency>14 <groupId>org.springframework.boot</groupId>15 <artifactId>spring-boot-starter-undertow</artifactId>16</dependency>配置示例:
1server:2 port: 80803 undertow:4 threads:5 io: 46 worker: 2007 buffer-size: 10248 direct-buffers: true容器对比:
| 特性 | Tomcat | Jetty | Undertow |
|---|---|---|---|
| 默认容器 | ✅ | ❌ | ❌ |
| 性能 | 中等 | 中等 | 高 |
| 内存占用 | 中等 | 较小 | 最小 |
| 并发处理 | 好 | 好 | 优秀 |
| Servlet 支持 | 完整 | 完整 | 完整 |
| WebSocket | 支持 | 支持 | 支持 |
| 社区活跃度 | 最高 | 高 | 中等 |
| 适用场景 | 通用 | 轻量级 | 高并发 |
选择建议:
- Tomcat:默认选择,社区支持最好,适合大多数场景
- Jetty:轻量级,适合资源受限环境
- Undertow:高性能,适合高并发场景,内存占用最小
8. Spring Boot 中 application.properties 和 application.yml 的区别是什么?
答案:
两者都是 Spring Boot 的配置文件,主要区别在于格式和可读性。
1. 格式对比
application.properties:
1server.port=80802server.servlet.context-path=/api34spring.datasource.url=jdbc:mysql://localhost:3306/db5spring.datasource.username=root6spring.datasource.password=1234567spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver89spring.jpa.hibernate.ddl-auto=update10spring.jpa.show-sql=trueapplication.yml:
1server:2 port: 80803 servlet:4 context-path: /api56spring:7 datasource:8 url: jdbc:mysql://localhost:3306/db9 username: root10 password: 12345611 driver-class-name: com.mysql.cj.jdbc.Driver12 jpa:13 hibernate:14 ddl-auto: update15 show-sql: true2. 主要区别
| 特性 | application.properties | application.yml |
|---|---|---|
| 格式 | key=value | 层级结构 |
| 可读性 | 较差(重复前缀多) | 好(层级清晰) |
| 配置复杂度 | 简单配置友好 | 复杂配置友好 |
| 数组/列表 | 不直观 | 直观 |
| 多行文本 | 不支持 | 支持 |
| 注释 | # 或 ! | # |
| 文件大小 | 较大 | 较小 |
| 学习成本 | 低 | 稍高 |
3. 数组/列表配置对比
properties:
1spring.profiles.active=dev,test2my.servers[0]=dev.example.com3my.servers[1]=prod.example.comyml:
1spring:2 profiles:3 active: dev,test45my:6 servers:7 - dev.example.com8 - prod.example.com4. 多行文本配置
properties:
1# 不支持多行,需要使用 \n2my.description=This is a long \3description that spans \4multiple linesyml:
1# 支持多行2my:3 description: |4 This is a long5 description that spans6 multiple lines5. 加载优先级
当两个文件同时存在时:
application.properties优先级高于application.yml- 相同配置项,properties 会覆盖 yml
6. 使用建议
- 简单项目:使用 properties,配置少,简单直观
- 复杂项目:使用 yml,层级清晰,可读性好
- 团队习惯:根据团队习惯选择
- 推荐:yml 格式,更现代化,可读性更好
9. 如何在 Spring Boot 中定义和读取自定义配置?
答案:
Spring Boot 提供多种方式读取自定义配置。
方式一:使用 @Value 注解
配置文件:
1app:2 name: MyApplication3 version: 1.0.04 author: John Doe读取配置:
1@Component2public class AppConfig {3 @Value("${app.name}")4 private String appName;5 6 @Value("${app.version}")7 private String version;8 9 @Value("${app.author:Unknown}") // 默认值10 private String author;11 12 @Value("${app.timeout:30}")13 private int timeout;14}方式二:使用 @ConfigurationProperties(推荐)
配置文件:
1app:2 name: MyApplication3 version: 1.0.04 author: John Doe5 email: john@example.com6 servers:7 - dev.example.com8 - prod.example.com9 database:10 host: localhost11 port: 330612 username: root配置类:
1@Component2@ConfigurationProperties(prefix = "app")3@Data // Lombok4public class AppProperties {5 private String name;6 private String version;7 private String author;8 private String email;9 private List<String> servers;10 private Database database;11 12 @Data13 public static class Database {14 private String host;15 private int port;16 private String username;17 }18}使用配置:
1@Service2public class AppService {3 @Autowired4 private AppProperties appProperties;5 6 public void printConfig() {7 System.out.println("App Name: " + appProperties.getName());8 System.out.println("Version: " + appProperties.getVersion());9 System.out.println("Servers: " + appProperties.getServers());10 System.out.println("DB Host: " + appProperties.getDatabase().getHost());11 }12}方式三:使用 Environment
1@Component2public class ConfigReader {3 @Autowired4 private Environment env;5 6 public void readConfig() {7 String appName = env.getProperty("app.name");8 String version = env.getProperty("app.version", "1.0.0"); // 默认值9 Integer port = env.getProperty("server.port", Integer.class, 8080);10 }11}方式四:使用 @PropertySource 加载自定义配置文件
自定义配置文件 custom.properties:
1custom.api.url=https://api.example.com2custom.api.key=abc1233custom.api.timeout=5000配置类:
1@Configuration2@PropertySource("classpath:custom.properties")3@ConfigurationProperties(prefix = "custom.api")4@Data5public class CustomApiProperties {6 private String url;7 private String key;8 private int timeout;9}方式五:读取复杂对象配置
配置文件:
1users:2 - name: Alice3 age: 254 email: alice@example.com5 - name: Bob6 age: 307 email: bob@example.com配置类:
1@Component2@ConfigurationProperties(prefix = "users")3@Data4public class UsersProperties {5 private List<User> users = new ArrayList<>();6 7 @Data8 public static class User {9 private String name;10 private int age;11 private String email;12 }13}启用 @ConfigurationProperties:
方式 1:在配置类上添加 @Component
1@Component2@ConfigurationProperties(prefix = "app")3public class AppProperties { }方式 2:在启动类上添加 @EnableConfigurationProperties
1@SpringBootApplication2@EnableConfigurationProperties(AppProperties.class)3public class Application { }方式 3:使用 @ConfigurationPropertiesScan
1@SpringBootApplication2@ConfigurationPropertiesScan("com.example.config")3public class Application { }最佳实践:
- 简单配置使用
@Value - 复杂配置使用
@ConfigurationProperties - 使用类型安全的配置类
- 提供默认值
- 添加配置验证(JSR-303)
配置验证示例:
1@Component2@ConfigurationProperties(prefix = "app")3@Validated4@Data5public class AppProperties {6 @NotBlank7 private String name;8 9 @Min(1)10 @Max(65535)11 private int port;12 13 @Email14 private String email;15}10. Spring Boot 配置文件加载优先级你知道吗?
答案:
Spring Boot 配置文件的加载遵循特定的优先级顺序,优先级高的配置会覆盖优先级低的配置。
1. 配置源优先级(从高到低)
11. 命令行参数2 java -jar app.jar --server.port=9090342. SPRING_APPLICATION_JSON 中的属性5 SPRING_APPLICATION_JSON='{"server.port":9090}' java -jar app.jar673. ServletConfig 初始化参数894. ServletContext 初始化参数10115. JNDI 属性(java:comp/env)12136. Java 系统属性(System.getProperties())14 java -Dserver.port=9090 -jar app.jar15167. 操作系统环境变量17 export SERVER_PORT=909018198. RandomValuePropertySource(random.* 属性)20219. jar 包外部的 application-{profile}.properties/yml222310. jar 包内部的 application-{profile}.properties/yml242511. jar 包外部的 application.properties/yml262712. jar 包内部的 application.properties/yml282913. @PropertySource 注解指定的配置文件303114. 默认属性(SpringApplication.setDefaultProperties)2. 配置文件位置优先级(从高到低)
11. file:./config/ # 当前目录的 /config 子目录22. file:./config/*/ # 当前目录的 /config 子目录的子目录33. file:./ # 当前目录44. classpath:/config/ # classpath 的 /config 目录55. classpath:/ # classpath 根目录示例目录结构:
1project/2├── config/3│ ├── application.yml # 优先级最高4│ └── dev/5│ └── application.yml # 优先级第二6├── application.yml # 优先级第三7└── target/8 └── app.jar9 └── application.yml # 优先级最低(jar 内)3. Profile 配置优先级
1# application.yml(基础配置)2server:3 port: 808045# application-dev.yml(开发环境)6server:7 port: 808189# application-prod.yml(生产环境)10server:11 port: 8082激活 Profile:
1# 方式一:命令行2java -jar app.jar --spring.profiles.active=dev34# 方式二:配置文件5spring:6 profiles:7 active: dev89# 方式三:环境变量10export SPRING_PROFILES_ACTIVE=dev优先级: application-{profile}.yml > application.yml
4. 文件格式优先级
当同时存在时:properties > yml > yaml
1application.properties # 优先级最高2application.yml # 优先级中等3application.yaml # 优先级最低5. 实际应用示例
场景: 本地开发时覆盖配置
项目中的配置(application.yml):
1server:2 port: 80803spring:4 datasource:5 url: jdbc:mysql://prod-server:3306/db本地配置文件(./config/application.yml):
1server:2 port: 80813spring:4 datasource:5 url: jdbc:mysql://localhost:3306/db命令行参数:
1java -jar app.jar --server.port=9090最终生效:
server.port=9090(命令行优先级最高)spring.datasource.url=jdbc:mysql://localhost:3306/db(本地配置覆盖)
6. 查看配置加载情况
方式一:启用 debug 日志
1logging:2 level:3 org.springframework.boot.context.config: DEBUG方式二:使用 Actuator
1<dependency>2 <groupId>org.springframework.boot</groupId>3 <artifactId>spring-boot-starter-actuator</artifactId>4</dependency>访问:GET /actuator/env
7. 最佳实践
- 基础配置:放在 jar 包内的
application.yml - 环境配置:使用
application-{profile}.yml - 敏感配置:使用环境变量或外部配置文件
- 临时配置:使用命令行参数
- 本地开发:在项目根目录创建
config/application.yml(不提交到版本控制)
11. Spring Boot 打成的 jar 和普通的 jar 有什么区别 ?
答案:
Spring Boot 打包的 JAR(Fat JAR/Uber JAR)与普通 JAR 有本质区别。
1. 结构对比
普通 JAR 结构:
1my-app.jar2├── META-INF/3│ └── MANIFEST.MF4└── com/5 └── example/6 └── MyClass.classSpring Boot JAR 结构:
1my-app.jar2├── META-INF/3│ └── MANIFEST.MF # 包含启动类信息4├── BOOT-INF/5│ ├── classes/ # 应用的 class 文件6│ │ └── com/7│ │ └── example/8│ │ └── Application.class9│ └── lib/ # 所有依赖的 jar 包10│ ├── spring-boot-*.jar11│ ├── spring-core-*.jar12│ └── ...13└── org/14 └── springframework/15 └── boot/16 └── loader/ # Spring Boot 类加载器2. MANIFEST.MF 对比
普通 JAR:
1Manifest-Version: 1.02Main-Class: com.example.MyClassSpring Boot JAR:
1Manifest-Version: 1.02Spring-Boot-Version: 3.0.03Main-Class: org.springframework.boot.loader.JarLauncher4Start-Class: com.example.Application5Spring-Boot-Classes: BOOT-INF/classes/6Spring-Boot-Lib: BOOT-INF/lib/3. 主要区别
| 特性 | 普通 JAR | Spring Boot JAR |
|---|---|---|
| 依赖包含 | 不包含依赖 | 包含所有依赖(Fat JAR) |
| 启动方式 | 需要指定 classpath | 直接运行 |
| Main-Class | 应用主类 | JarLauncher |
| 类加载器 | 默认类加载器 | 自定义类加载器 |
| 文件大小 | 小 | 大(包含所有依赖) |
| 独立运行 | 否 | 是 |
| 嵌入容器 | 无 | 有(Tomcat/Jetty/Undertow) |
4. 启动方式对比
普通 JAR:
1# 需要指定所有依赖2java -cp "app.jar:lib/*" com.example.MyClassSpring Boot JAR:
1# 直接运行2java -jar app.jar5. Spring Boot JAR 启动原理
1// JarLauncher 启动流程21. JarLauncher.main()3 ├─ 创建自定义类加载器(LaunchedURLClassLoader)4 ├─ 加载 BOOT-INF/lib/ 下的所有 jar5 ├─ 加载 BOOT-INF/classes/ 下的类6 └─ 反射调用 Start-Class(真正的主类)782. Application.main()9 └─ SpringApplication.run()6. 打包配置
Maven:
1<build>2 <plugins>3 <plugin>4 <groupId>org.springframework.boot</groupId>5 <artifactId>spring-boot-maven-plugin</artifactId>6 <configuration>7 <mainClass>com.example.Application</mainClass>8 </configuration>9 </plugin>10 </plugins>11</build>Gradle:
1plugins {2 id 'org.springframework.boot' version '3.0.0'3}45bootJar {6 mainClass = 'com.example.Application'7}7. 解压查看 Spring Boot JAR
1# 解压 jar 包2jar -xvf app.jar34# 或使用 unzip5unzip app.jar -d app-extracted8. 优缺点对比
Spring Boot JAR 优点:
- 独立运行,无需外部依赖
- 包含所有依赖,部署简单
- 内嵌 Web 容器,无需额外安装
- 一个文件即可运行
Spring Boot JAR 缺点:
- 文件体积大(通常几十 MB)
- 启动稍慢(需要解压和加载依赖)
- 更新依赖需要重新打包
9. 生成可执行 JAR
Spring Boot JAR 可以在 Linux/Unix 系统上直接执行:
1# 添加执行权限2chmod +x app.jar34# 直接运行5./app.jar这是因为 Spring Boot 在 JAR 文件开头添加了 shell 脚本:
1#!/bin/bash2exec java -jar "$0" "$@"10. 打包成 WAR
如果需要部署到外部容器:
1@SpringBootApplication2public class Application extends SpringBootServletInitializer {3 4 @Override5 protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {6 return builder.sources(Application.class);7 }8 9 public static void main(String[] args) {10 SpringApplication.run(Application.class, args);11 }12}1<packaging>war</packaging>12. Spring Boot 是否可以使用 XML 配置 ?
答案:
可以,但不推荐。Spring Boot 推荐使用 Java 配置和注解,但仍然支持 XML 配置。
方式一:使用 @ImportResource 导入 XML 配置
XML 配置文件(beans.xml):
1<?xml version="1.0" encoding="UTF-8"?>2<beans xmlns="http://www.springframework.org/schema/beans"3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"4 xsi:schemaLocation="http://www.springframework.org/schema/beans5 http://www.springframework.org/schema/beans/spring-beans.xsd">6 7 <bean id="userService" class="com.example.service.UserService">8 <property name="userRepository" ref="userRepository"/>9 </bean>10 11 <bean id="userRepository" class="com.example.repository.UserRepository"/>12</beans>启动类:
1@SpringBootApplication2@ImportResource("classpath:beans.xml")3public class Application {4 public static void main(String[] args) {5 SpringApplication.run(Application.class, args);6 }7}方式二:在配置类中导入
1@Configuration2@ImportResource({"classpath:beans.xml", "classpath:services.xml"})3public class XmlConfig {4}为什么不推荐使用 XML?
- 冗余:XML 配置比注解更冗长
- 类型安全:Java 配置有编译时检查
- 重构友好:IDE 可以更好地支持 Java 配置
- Spring Boot 理念:约定优于配置
对比示例:
XML 方式:
1<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">2 <property name="driverClassName" value="${spring.datasource.driver-class-name}"/>3 <property name="jdbcUrl" value="${spring.datasource.url}"/>4 <property name="username" value="${spring.datasource.username}"/>5 <property name="password" value="${spring.datasource.password}"/>6</bean>Java 配置方式(推荐):
1@Configuration2public class DataSourceConfig {3 @Bean4 public DataSource dataSource(5 @Value("${spring.datasource.url}") String url,6 @Value("${spring.datasource.username}") String username,7 @Value("${spring.datasource.password}") String password) {8 HikariDataSource dataSource = new HikariDataSource();9 dataSource.setJdbcUrl(url);10 dataSource.setUsername(username);11 dataSource.setPassword(password);12 return dataSource;13 }14}使用场景:
- 迁移老项目到 Spring Boot
- 集成第三方库的 XML 配置
- 团队习惯使用 XML
13. SpringBoot 默认同时可以处理的最大连接数是多少?
答案:
Spring Boot 默认使用 Tomcat 作为嵌入式容器,其默认配置如下:
Tomcat 默认配置:
- 最大线程数(max-threads):200
- 最小空闲线程数(min-spare-threads):10
- 最大连接数(max-connections):8192(BIO 模式)或 10000(NIO 模式)
- 等待队列长度(accept-count):100
配置说明:
1server:2 port: 80803 tomcat:4 threads:5 max: 200 # 最大工作线程数6 min-spare: 10 # 最小空闲线程数7 max-connections: 10000 # 最大连接数8 accept-count: 100 # 等待队列长度9 connection-timeout: 20000 # 连接超时时间(毫秒)参数详解:
1. max-threads(最大线程数)
- 默认值:200
- 含义:Tomcat 创建的最大工作线程数
- 影响:决定了同时处理请求的能力
- 建议:根据 CPU 核心数和业务特点调整
2. min-spare-threads(最小空闲线程数)
- 默认值:10
- 含义:Tomcat 始终保持的最小线程数
- 影响:减少线程创建开销
3. max-connections(最大连接数)
- 默认值:10000(NIO)
- 含义:服务器接受的最大连接数
- 影响:超过此数的连接会被拒绝或等待
4. accept-count(等待队列长度)
- 默认值:100
- 含义:当所有线程都在使用时,等待队列的长度
- 影响:超过此数的连接会被拒绝
处理流程:
11. 客户端发起连接2 ├─ 连接数 < max-connections?3 │ ├─ 是:接受连接4 │ └─ 否:进入等待队列5 │62. 等待队列7 ├─ 队列长度 < accept-count?8 │ ├─ 是:等待9 │ └─ 否:拒绝连接10 │113. 处理请求12 ├─ 空闲线程 > 0?13 │ ├─ 是:分配线程处理14 │ └─ 否:等待线程释放15 │164. 线程池17 ├─ 当前线程数 < max-threads?18 │ ├─ 是:创建新线程19 │ └─ 否:等待不同容器的默认配置:
Tomcat:
1server:2 tomcat:3 threads:4 max: 2005 max-connections: 100006 accept-count: 100Jetty:
1server:2 jetty:3 threads:4 max: 2005 min: 86 max-queue-capacity: 0 # 无限制Undertow:
1server:2 undertow:3 threads:4 io: 4 # IO 线程数(通常为 CPU 核心数)5 worker: 200 # 工作线程数6 buffer-size: 1024性能调优建议:
1. CPU 密集型应用:
1server:2 tomcat:3 threads:4 max: 100 # CPU 核心数 * 22. IO 密集型应用:
1server:2 tomcat:3 threads:4 max: 500 # 可以设置更大5 max-connections: 200003. 高并发场景:
1server:2 tomcat:3 threads:4 max: 8005 max-connections: 200006 accept-count: 500监控和测试:
1@RestController2public class MonitorController {3 @GetMapping("/thread-info")4 public Map<String, Object> getThreadInfo() {5 ThreadPoolExecutor executor = (ThreadPoolExecutor) 6 ((TomcatWebServer) ((WebServerApplicationContext) applicationContext)7 .getWebServer()).getTomcat().getConnector()8 .getProtocolHandler().getExecutor();9 10 Map<String, Object> info = new HashMap<>();11 info.put("activeCount", executor.getActiveCount());12 info.put("poolSize", executor.getPoolSize());13 info.put("corePoolSize", executor.getCorePoolSize());14 info.put("maximumPoolSize", executor.getMaximumPoolSize());15 info.put("queueSize", executor.getQueue().size());16 return info;17 }18}压力测试工具:
1# 使用 Apache Bench2ab -n 10000 -c 200 http://localhost:8080/api/test34# 使用 JMeter5# 配置线程组,模拟并发请求14. 如何理解 Spring Boot 中的 starter?
答案:
Starter 是 Spring Boot 的核心特性之一,是一组预定义的依赖描述符,用于简化项目依赖管理。
1. Starter 的作用
- 简化依赖管理:一个 Starter 包含一组相关依赖
- 版本管理:自动管理依赖版本,避免版本冲突
- 自动配置:提供默认配置,开箱即用
- 约定优于配置:遵循最佳实践
2. 常用 Starter
| Starter | 功能 | 包含的主要依赖 |
|---|---|---|
| spring-boot-starter-web | Web 开发 | Spring MVC, Tomcat, Jackson |
| spring-boot-starter-data-jpa | JPA 数据访问 | Hibernate, Spring Data JPA |
| spring-boot-starter-data-redis | Redis | Lettuce, Spring Data Redis |
| spring-boot-starter-security | 安全框架 | Spring Security |
| spring-boot-starter-test | 测试 | JUnit, Mockito, AssertJ |
| spring-boot-starter-aop | AOP | Spring AOP, AspectJ |
| spring-boot-starter-validation | 数据验证 | Hibernate Validator |
| spring-boot-starter-actuator | 监控 | Actuator |
| spring-boot-starter-logging | 日志 | Logback, SLF4J |
| spring-boot-starter-jdbc | JDBC | HikariCP, Spring JDBC |
3. Starter 示例
使用前(传统方式):
1<dependencies>2 <dependency>3 <groupId>org.springframework</groupId>4 <artifactId>spring-web</artifactId>5 <version>5.3.20</version>6 </dependency>7 <dependency>8 <groupId>org.springframework</groupId>9 <artifactId>spring-webmvc</artifactId>10 <version>5.3.20</version>11 </dependency>12 <dependency>13 <groupId>org.apache.tomcat.embed</groupId>14 <artifactId>tomcat-embed-core</artifactId>15 <version>9.0.62</version>16 </dependency>17 <dependency>18 <groupId>com.fasterxml.jackson.core</groupId>19 <artifactId>jackson-databind</artifactId>20 <version>2.13.3</version>21 </dependency>22 <!-- 还有很多其他依赖... -->23</dependencies>使用后(Starter 方式):
1<dependency>2 <groupId>org.springframework.boot</groupId>3 <artifactId>spring-boot-starter-web</artifactId>4</dependency>4. Starter 的命名规范
- 官方 Starter:
spring-boot-starter-* - 第三方 Starter:
*-spring-boot-starter
示例:
1<!-- 官方 -->2<dependency>3 <groupId>org.springframework.boot</groupId>4 <artifactId>spring-boot-starter-web</artifactId>5</dependency>67<!-- 第三方(MyBatis) -->8<dependency>9 <groupId>org.mybatis.spring.boot</groupId>10 <artifactId>mybatis-spring-boot-starter</artifactId>11 <version>2.2.2</version>12</dependency>5. 自定义 Starter
场景: 封装公司内部通用功能
步骤一:创建 Starter 项目
项目结构:
1my-spring-boot-starter/2├── pom.xml3└── src/main/java/4 └── com/example/starter/5 ├── MyAutoConfiguration.java6 ├── MyProperties.java7 └── MyService.java8└── src/main/resources/9 └── META-INF/10 └── spring.factoriespom.xml:
1<dependencies>2 <dependency>3 <groupId>org.springframework.boot</groupId>4 <artifactId>spring-boot-autoconfigure</artifactId>5 </dependency>6 <dependency>7 <groupId>org.springframework.boot</groupId>8 <artifactId>spring-boot-configuration-processor</artifactId>9 <optional>true</optional>10 </dependency>11</dependencies>配置属性类:
1@ConfigurationProperties(prefix = "my.service")2@Data3public class MyProperties {4 private String name = "default";5 private int timeout = 30;6}服务类:
1public class MyService {2 private MyProperties properties;3 4 public MyService(MyProperties properties) {5 this.properties = properties;6 }7 8 public void doSomething() {9 System.out.println("Service: " + properties.getName());10 }11}自动配置类:
1@Configuration2@EnableConfigurationProperties(MyProperties.class)3@ConditionalOnClass(MyService.class)4public class MyAutoConfiguration {5 6 @Bean7 @ConditionalOnMissingBean8 public MyService myService(MyProperties properties) {9 return new MyService(properties);10 }11}spring.factories:
1org.springframework.boot.autoconfigure.EnableAutoConfiguration=\2com.example.starter.MyAutoConfiguration步骤二:使用自定义 Starter
1<dependency>2 <groupId>com.example</groupId>3 <artifactId>my-spring-boot-starter</artifactId>4 <version>1.0.0</version>5</dependency>1# application.yml2my:3 service:4 name: MyApp5 timeout: 601@RestController2public class TestController {3 @Autowired4 private MyService myService;5 6 @GetMapping("/test")7 public String test() {8 myService.doSomething();9 return "OK";10 }11}6. Starter 的优势
- 依赖管理简化:一个依赖引入多个相关库
- 版本统一:避免版本冲突
- 自动配置:减少配置代码
- 最佳实践:遵循 Spring Boot 规范
- 可扩展:可以自定义 Starter
7. 查看 Starter 包含的依赖
1# Maven2mvn dependency:tree34# Gradle5gradle dependencies15. Spring Boot 如何处理跨域请求(CORS)?
答案:
Spring Boot 提供多种方式处理跨域请求(CORS)。
方式一:使用 @CrossOrigin 注解(局部配置)
在 Controller 上:
1@RestController2@RequestMapping("/api")3@CrossOrigin(origins = "http://localhost:3000") // 允许特定域名4public class UserController {5 6 @GetMapping("/users")7 public List<User> getUsers() {8 return userService.findAll();9 }10}在方法上:
1@RestController2@RequestMapping("/api")3public class UserController {4 5 @CrossOrigin(6 origins = {"http://localhost:3000", "https://example.com"},7 methods = {RequestMethod.GET, RequestMethod.POST},8 maxAge = 3600,9 allowedHeaders = "*",10 exposedHeaders = "Authorization",11 allowCredentials = "true"12 )13 @GetMapping("/users")14 public List<User> getUsers() {15 return userService.findAll();16 }17}方式二:全局配置(推荐)
方法 1:实现 WebMvcConfigurer
1@Configuration2public class CorsConfig implements WebMvcConfigurer {3 4 @Override5 public void addCorsMappings(CorsRegistry registry) {6 registry.addMapping("/**") // 所有接口7 .allowedOrigins("http://localhost:3000", "https://example.com")8 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")9 .allowedHeaders("*")10 .exposedHeaders("Authorization")11 .allowCredentials(true)12 .maxAge(3600);13 }14}方法 2:使用 CorsFilter
1@Configuration2public class CorsConfig {3 4 @Bean5 public CorsFilter corsFilter() {6 CorsConfiguration config = new CorsConfiguration();7 config.setAllowCredentials(true);8 config.addAllowedOrigin("http://localhost:3000");9 config.addAllowedOrigin("https://example.com");10 config.addAllowedHeader("*");11 config.addAllowedMethod("*");12 config.setMaxAge(3600L);13 14 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();15 source.registerCorsConfiguration("/**", config);16 17 return new CorsFilter(source);18 }19}方式三:配置文件方式(Spring Boot 2.4+)
1spring:2 web:3 cors:4 allowed-origins: "http://localhost:3000,https://example.com"5 allowed-methods: "GET,POST,PUT,DELETE,OPTIONS"6 allowed-headers: "*"7 exposed-headers: "Authorization"8 allow-credentials: true9 max-age: 3600方式四:使用 Spring Security 配置
1@Configuration2@EnableWebSecurity3public class SecurityConfig {4 5 @Bean6 public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {7 http.cors().configurationSource(corsConfigurationSource())8 .and()9 .csrf().disable()10 .authorizeRequests()11 .anyRequest().authenticated();12 return http.build();13 }14 15 @Bean16 public CorsConfigurationSource corsConfigurationSource() {17 CorsConfiguration configuration = new CorsConfiguration();18 configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));19 configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));20 configuration.setAllowedHeaders(Arrays.asList("*"));21 configuration.setAllowCredentials(true);22 23 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();24 source.registerCorsConfiguration("/**", configuration);25 return source;26 }27}CORS 参数说明:
| 参数 | 说明 | 示例 |
|---|---|---|
| allowedOrigins | 允许的源 | http://localhost:3000 |
| allowedMethods | 允许的 HTTP 方法 | GET, POST, PUT, DELETE |
| allowedHeaders | 允许的请求头 | * 或 Content-Type, Authorization |
| exposedHeaders | 暴露给客户端的响应头 | Authorization |
| allowCredentials | 是否允许发送 Cookie | true |
| maxAge | 预检请求缓存时间(秒) | 3600 |
CORS 工作原理:
简单请求:
1客户端 -> 服务器2GET /api/users HTTP/1.13Origin: http://localhost:300045服务器 -> 客户端6HTTP/1.1 200 OK7Access-Control-Allow-Origin: http://localhost:30008Access-Control-Allow-Credentials: true预检请求(Preflight):
1客户端 -> 服务器(OPTIONS 请求)2OPTIONS /api/users HTTP/1.13Origin: http://localhost:30004Access-Control-Request-Method: POST5Access-Control-Request-Headers: Content-Type67服务器 -> 客户端8HTTP/1.1 200 OK9Access-Control-Allow-Origin: http://localhost:300010Access-Control-Allow-Methods: GET, POST, PUT, DELETE11Access-Control-Allow-Headers: Content-Type12Access-Control-Max-Age: 36001314客户端 -> 服务器(实际请求)15POST /api/users HTTP/1.116Origin: http://localhost:300017Content-Type: application/json常见问题:
1. 允许所有域名(开发环境):
1config.addAllowedOriginPattern("*"); // 使用 Pattern 而不是 Origin2config.setAllowCredentials(true);2. 生产环境配置:
1// 只允许特定域名2config.setAllowedOrigins(Arrays.asList(3 "https://www.example.com",4 "https://app.example.com"5));3. 动态配置:
1@Configuration2public class CorsConfig implements WebMvcConfigurer {3 4 @Value("${cors.allowed-origins}")5 private String[] allowedOrigins;6 7 @Override8 public void addCorsMappings(CorsRegistry registry) {9 registry.addMapping("/**")10 .allowedOrigins(allowedOrigins)11 .allowedMethods("*")12 .allowedHeaders("*")13 .allowCredentials(true);14 }15}1# application.yml2cors:3 allowed-origins:4 - http://localhost:30005 - https://example.com16. 在 Spring Boot 中你是怎么使用拦截器的?
答案:
Spring Boot 中使用拦截器需要实现 HandlerInterceptor 接口并注册到 Spring MVC 配置中。
步骤一:创建拦截器
1@Component2public class LoginInterceptor implements HandlerInterceptor {3 4 /**5 * 在请求处理之前执行6 * 返回 true 继续执行,返回 false 中断请求7 */8 @Override9 public boolean preHandle(HttpServletRequest request, 10 HttpServletResponse response, 11 Object handler) throws Exception {12 String token = request.getHeader("Authorization");13 14 if (token == null || token.isEmpty()) {15 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);16 response.getWriter().write("Unauthorized");17 return false;18 }19 20 // 验证 token21 if (!validateToken(token)) {22 response.setStatus(HttpServletResponse.SC_FORBIDDEN);23 return false;24 }25 26 // 将用户信息存入 request27 request.setAttribute("userId", getUserIdFromToken(token));28 return true;29 }30 31 /**32 * 在请求处理之后、视图渲染之前执行33 */34 @Override35 public void postHandle(HttpServletRequest request, 36 HttpServletResponse response, 37 Object handler, 38 ModelAndView modelAndView) throws Exception {39 // 可以修改 ModelAndView40 System.out.println("postHandle executed");41 }42 43 /**44 * 在整个请求完成之后执行(视图渲染完成后)45 * 无论是否发生异常都会执行46 */47 @Override48 public void afterCompletion(HttpServletRequest request, 49 HttpServletResponse response, 50 Object handler, 51 Exception ex) throws Exception {52 // 清理资源、记录日志等53 System.out.println("afterCompletion executed");54 }55 56 private boolean validateToken(String token) {57 // 验证逻辑58 return true;59 }60 61 private String getUserIdFromToken(String token) {62 // 解析 token63 return "user123";64 }65}步骤二:注册拦截器
1@Configuration2public class WebConfig implements WebMvcConfigurer {3 4 @Autowired5 private LoginInterceptor loginInterceptor;6 7 @Override8 public void addInterceptors(InterceptorRegistry registry) {9 registry.addInterceptor(loginInterceptor)10 .addPathPatterns("/**") // 拦截所有请求11 .excludePathPatterns( // 排除路径12 "/login",13 "/register",14 "/public/**",15 "/static/**",16 "/error"17 )18 .order(1); // 执行顺序19 }20}常见拦截器示例:
1. 日志拦截器
1@Component2@Slf4j3public class LogInterceptor implements HandlerInterceptor {4 5 @Override6 public boolean preHandle(HttpServletRequest request, 7 HttpServletResponse response, 8 Object handler) {9 long startTime = System.currentTimeMillis();10 request.setAttribute("startTime", startTime);11 12 log.info("Request URL: {}, Method: {}", 13 request.getRequestURI(), 14 request.getMethod());15 return true;16 }17 18 @Override19 public void afterCompletion(HttpServletRequest request, 20 HttpServletResponse response, 21 Object handler, 22 Exception ex) {23 long startTime = (Long) request.getAttribute("startTime");24 long endTime = System.currentTimeMillis();25 26 log.info("Request completed in {} ms, Status: {}", 27 endTime - startTime, 28 response.getStatus());29 }30}2. 权限拦截器
1@Component2public class PermissionInterceptor implements HandlerInterceptor {3 4 @Autowired5 private UserService userService;6 7 @Override8 public boolean preHandle(HttpServletRequest request, 9 HttpServletResponse response, 10 Object handler) throws Exception {11 // 检查是否是 HandlerMethod12 if (!(handler instanceof HandlerMethod)) {13 return true;14 }15 16 HandlerMethod handlerMethod = (HandlerMethod) handler;17 18 // 检查方法上的权限注解19 RequirePermission annotation = handlerMethod.getMethodAnnotation(RequirePermission.class);20 if (annotation == null) {21 return true;22 }23 24 // 获取用户信息25 String userId = (String) request.getAttribute("userId");26 User user = userService.findById(userId);27 28 // 检查权限29 String requiredPermission = annotation.value();30 if (!user.hasPermission(requiredPermission)) {31 response.setStatus(HttpServletResponse.SC_FORBIDDEN);32 response.getWriter().write("Permission denied");33 return false;34 }35 36 return true;37 }38}3940// 自定义注解41@Target(ElementType.METHOD)42@Retention(RetentionPolicy.RUNTIME)43public @interface RequirePermission {44 String value();45}4647// 使用48@RestController49public class AdminController {50 51 @RequirePermission("admin:user:delete")52 @DeleteMapping("/users/{id}")53 public void deleteUser(@PathVariable String id) {54 // ...55 }56}3. 限流拦截器
1@Component2public class RateLimitInterceptor implements HandlerInterceptor {3 4 private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();5 6 @Override7 public boolean preHandle(HttpServletRequest request, 8 HttpServletResponse response, 9 Object handler) throws Exception {10 String clientIp = getClientIp(request);11 12 // 每个 IP 每秒最多 10 个请求13 RateLimiter limiter = limiters.computeIfAbsent(14 clientIp, 15 k -> RateLimiter.create(10.0)16 );17 18 if (!limiter.tryAcquire()) {19 response.setStatus(429); // Too Many Requests20 response.getWriter().write("Rate limit exceeded");21 return false;22 }23 24 return true;25 }26 27 private String getClientIp(HttpServletRequest request) {28 String ip = request.getHeader("X-Forwarded-For");29 if (ip == null || ip.isEmpty()) {30 ip = request.getRemoteAddr();31 }32 return ip;33 }34}拦截器执行流程:
1请求到达2 ↓3preHandle (拦截器1)4 ↓5preHandle (拦截器2)6 ↓7Controller 方法执行8 ↓9postHandle (拦截器2)10 ↓11postHandle (拦截器1)12 ↓13视图渲染14 ↓15afterCompletion (拦截器2)16 ↓17afterCompletion (拦截器1)18 ↓19响应返回多个拦截器配置:
1@Configuration2public class WebConfig implements WebMvcConfigurer {3 4 @Autowired5 private LoginInterceptor loginInterceptor;6 @Autowired7 private LogInterceptor logInterceptor;8 @Autowired9 private PermissionInterceptor permissionInterceptor;10 11 @Override12 public void addInterceptors(InterceptorRegistry registry) {13 // 日志拦截器 - 最先执行14 registry.addInterceptor(logInterceptor)15 .addPathPatterns("/**")16 .order(1);17 18 // 登录拦截器19 registry.addInterceptor(loginInterceptor)20 .addPathPatterns("/**")21 .excludePathPatterns("/login", "/register")22 .order(2);23 24 // 权限拦截器 - 最后执行25 registry.addInterceptor(permissionInterceptor)26 .addPathPatterns("/admin/**")27 .order(3);28 }29}拦截器 vs 过滤器:
| 特性 | 拦截器(Interceptor) | 过滤器(Filter) |
|---|---|---|
| 实现方式 | 实现 HandlerInterceptor | 实现 Filter |
| 依赖 | Spring MVC | Servlet |
| 作用范围 | Spring MVC 请求 | 所有请求 |
| 访问 Spring Bean | 可以 | 可以(需配置) |
| 执行时机 | Controller 前后 | Servlet 前后 |
| 方法数量 | 3 个 | 2 个 |
| 获取方法信息 | 可以 | 不可以 |
使用建议:
- 拦截器:处理业务逻辑相关的拦截(登录、权限、日志)
- 过滤器:处理通用的请求/响应处理(编码、压缩、CORS)
17. SpringBoot 中如何实现定时任务 ?
答案:
Spring Boot 提供多种方式实现定时任务。
方式一:使用 @Scheduled 注解(推荐)
步骤一:启用定时任务
1@SpringBootApplication2@EnableScheduling // 启用定时任务3public class Application {4 public static void main(String[] args) {5 SpringApplication.run(Application.class, args);6 }7}步骤二:创建定时任务
1@Component2@Slf4j3public class ScheduledTasks {4 5 /**6 * 固定延迟:上次执行完成后延迟指定时间再执行7 */8 @Scheduled(fixedDelay = 5000) // 5秒9 public void taskWithFixedDelay() {10 log.info("Fixed delay task: {}", LocalDateTime.now());11 }12 13 /**14 * 固定频率:按固定频率执行,不管上次是否完成15 */16 @Scheduled(fixedRate = 5000)17 public void taskWithFixedRate() {18 log.info("Fixed rate task: {}", LocalDateTime.now());19 }20 21 /**22 * 初始延迟:首次延迟指定时间后执行23 */24 @Scheduled(initialDelay = 10000, fixedRate = 5000)25 public void taskWithInitialDelay() {26 log.info("Task with initial delay: {}", LocalDateTime.now());27 }28 29 /**30 * Cron 表达式:灵活的时间配置31 */32 @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行33 public void taskWithCron() {34 log.info("Cron task: {}", LocalDateTime.now());35 }36 37 /**38 * 从配置文件读取39 */40 @Scheduled(cron = "${task.cron}")41 public void taskFromConfig() {42 log.info("Task from config: {}", LocalDateTime.now());43 }44}Cron 表达式详解:
1格式:秒 分 时 日 月 周 [年]23示例:40 0 2 * * ? # 每天凌晨2点50 0/5 * * * ? # 每5分钟60 0 9-17 * * ? # 每天9点到17点每小时70 0 12 ? * WED # 每周三中午12点80 0 12 1 * ? # 每月1号中午12点90 15 10 15 * ? # 每月15号上午10:15100 0 0 * * ? # 每天0点11*/10 * * * * ? # 每10秒1213特殊字符:14* : 所有值15? : 不指定值(日和周互斥)16- : 范围17, : 列举18/ : 步长19L : 最后(Last)20W : 工作日(Weekday)21# : 第几个配置文件:
1# application.yml2task:3 cron: "0 0 2 * * ?" # 每天凌晨2点方式二:使用 TaskScheduler
1@Configuration2public class SchedulerConfig {3 4 @Bean5 public TaskScheduler taskScheduler() {6 ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();7 scheduler.setPoolSize(10);8 scheduler.setThreadNamePrefix("scheduled-task-");9 scheduler.setWaitForTasksToCompleteOnShutdown(true);10 scheduler.setAwaitTerminationSeconds(60);11 return scheduler;12 }13}1415@Component16public class DynamicScheduledTask {17 18 @Autowired19 private TaskScheduler taskScheduler;20 21 private ScheduledFuture<?> scheduledFuture;22 23 /**24 * 动态启动定时任务25 */26 public void startTask() {27 if (scheduledFuture != null) {28 scheduledFuture.cancel(false);29 }30 31 scheduledFuture = taskScheduler.scheduleAtFixedRate(32 () -> {33 System.out.println("Dynamic task: " + LocalDateTime.now());34 },35 Duration.ofSeconds(5)36 );37 }38 39 /**40 * 停止定时任务41 */42 public void stopTask() {43 if (scheduledFuture != null) {44 scheduledFuture.cancel(false);45 }46 }47 48 /**49 * 使用 Cron 表达式50 */51 public void startCronTask(String cron) {52 scheduledFuture = taskScheduler.schedule(53 () -> {54 System.out.println("Cron task: " + LocalDateTime.now());55 },56 new CronTrigger(cron)57 );58 }59}方式三:使用 Quartz
添加依赖:
1<dependency>2 <groupId>org.springframework.boot</groupId>3 <artifactId>spring-boot-starter-quartz</artifactId>4</dependency>创建 Job:
1public class MyJob extends QuartzJobBean {2 3 @Override4 protected void executeInternal(JobExecutionContext context) {5 System.out.println("Quartz job executed: " + LocalDateTime.now());6 }7}配置 Quartz:
1@Configuration2public class QuartzConfig {3 4 @Bean5 public JobDetail jobDetail() {6 return JobBuilder.newJob(MyJob.class)7 .withIdentity("myJob")8 .storeDurably()9 .build();10 }11 12 @Bean13 public Trigger trigger() {14 return TriggerBuilder.newTrigger()15 .forJob(jobDetail())16 .withIdentity("myTrigger")17 .withSchedule(18 CronScheduleBuilder.cronSchedule("0 0 2 * * ?")19 )20 .build();21 }22}配置线程池:
1# application.yml2spring:3 task:4 scheduling:5 pool:6 size: 10 # 线程池大小7 thread-name-prefix: scheduled-task-或使用 Java 配置:
1@Configuration2public class SchedulingConfig implements SchedulingConfigurer {3 4 @Override5 public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {6 taskRegistrar.setScheduler(taskExecutor());7 }8 9 @Bean10 public Executor taskExecutor() {11 ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();12 scheduler.setPoolSize(10);13 scheduler.setThreadNamePrefix("scheduled-task-");14 scheduler.setWaitForTasksToCompleteOnShutdown(true);15 scheduler.setAwaitTerminationSeconds(60);16 scheduler.initialize();17 return scheduler;18 }19}异步定时任务:
1@SpringBootApplication2@EnableScheduling3@EnableAsync // 启用异步4public class Application {5 public static void main(String[] args) {6 SpringApplication.run(Application.class, args);7 }8}910@Component11@Slf4j12public class AsyncScheduledTasks {13 14 @Async15 @Scheduled(fixedRate = 5000)16 public void asyncTask() {17 log.info("Async task start: {}, Thread: {}", 18 LocalDateTime.now(), 19 Thread.currentThread().getName());20 21 try {22 Thread.sleep(10000); // 模拟耗时操作23 } catch (InterruptedException e) {24 e.printStackTrace();25 }26 27 log.info("Async task end: {}", LocalDateTime.now());28 }29}分布式定时任务:
对于分布式环境,推荐使用:
- XXL-JOB:轻量级分布式任务调度平台
- Elastic-Job:分布式调度解决方案
- SchedulerX:阿里云分布式任务调度
最佳实践:
- 避免长时间任务阻塞:使用异步或增加线程池大小
- 异常处理:捕获异常,避免任务中断
- 幂等性:确保任务可以重复执行
- 监控告警:记录任务执行情况
- 分布式锁:多实例环境下避免重复执行
示例:带异常处理和日志的定时任务
1@Component2@Slf4j3public class RobustScheduledTask {4 5 @Scheduled(cron = "0 0 2 * * ?")6 public void robustTask() {7 log.info("Task started at: {}", LocalDateTime.now());8 9 try {10 // 业务逻辑11 performTask();12 log.info("Task completed successfully");13 } catch (Exception e) {14 log.error("Task failed: {}", e.getMessage(), e);15 // 发送告警16 sendAlert(e);17 }18 }19 20 private void performTask() {21 // 实际业务逻辑22 }23 24 private void sendAlert(Exception e) {25 // 发送告警通知26 }27}18. 什么是 Spring Actuator?它有什么优势?
答案:
Spring Boot Actuator 是 Spring Boot 提供的生产级别的监控和管理功能模块。
1. 添加依赖
1<dependency>2 <groupId>org.springframework.boot</groupId>3 <artifactId>spring-boot-starter-actuator</artifactId>4</dependency>2. 常用端点(Endpoints)
| 端点 | 说明 | 默认启用 |
|---|---|---|
/actuator/health | 健康检查 | ✅ |
/actuator/info | 应用信息 | ✅ |
/actuator/metrics | 应用指标 | ❌ |
/actuator/env | 环境变量 | ❌ |
/actuator/beans | Spring Beans | ❌ |
/actuator/mappings | 请求映射 | ❌ |
/actuator/configprops | 配置属性 | ❌ |
/actuator/loggers | 日志配置 | ❌ |
/actuator/heapdump | 堆转储 | ❌ |
/actuator/threaddump | 线程转储 | ❌ |
/actuator/prometheus | Prometheus 指标 | ❌ |
3. 配置端点
1management:2 endpoints:3 web:4 exposure:5 include: "*" # 暴露所有端点6 # include: health,info,metrics # 暴露指定端点7 exclude: heapdump,threaddump # 排除端点8 base-path: /actuator # 端点基础路径9 endpoint:10 health:11 show-details: always # 显示详细健康信息12 shutdown:13 enabled: true # 启用关闭端点(默认禁用)14 server:15 port: 9090 # 单独的管理端口4. Health 健康检查
默认健康检查:
1GET /actuator/health23{4 "status": "UP",5 "components": {6 "diskSpace": {7 "status": "UP",8 "details": {9 "total": 500000000000,10 "free": 300000000000,11 "threshold": 1048576012 }13 },14 "db": {15 "status": "UP",16 "details": {17 "database": "MySQL",18 "validationQuery": "isValid()"19 }20 },21 "redis": {22 "status": "UP",23 "details": {24 "version": "6.2.6"25 }26 }27 }28}自定义健康检查:
1@Component2public class CustomHealthIndicator implements HealthIndicator {3 4 @Override5 public Health health() {6 // 检查自定义服务状态7 boolean serviceUp = checkService();8 9 if (serviceUp) {10 return Health.up()11 .withDetail("service", "available")12 .withDetail("version", "1.0.0")13 .build();14 } else {15 return Health.down()16 .withDetail("service", "unavailable")17 .withDetail("error", "Connection timeout")18 .build();19 }20 }21 22 private boolean checkService() {23 // 实际检查逻辑24 return true;25 }26}5. Info 信息端点
配置文件:
1info:2 app:3 name: My Application4 version: 1.0.05 description: Spring Boot Application6 company:7 name: Example CorpJava 配置:
1@Component2public class AppInfoContributor implements InfoContributor {3 4 @Override5 public void contribute(Info.Builder builder) {6 builder.withDetail("build-time", LocalDateTime.now())7 .withDetail("active-users", 1000)8 .withDetail("custom-info", "some value");9 }10}6. Metrics 指标
查看所有指标:
1GET /actuator/metrics23{4 "names": [5 "jvm.memory.used",6 "jvm.memory.max",7 "jvm.gc.pause",8 "http.server.requests",9 "system.cpu.usage",10 "process.uptime"11 ]12}查看具体指标:
1GET /actuator/metrics/jvm.memory.used23{4 "name": "jvm.memory.used",5 "measurements": [6 {7 "statistic": "VALUE",8 "value": 1234567899 }10 ],11 "availableTags": [12 {13 "tag": "area",14 "values": ["heap", "nonheap"]15 }16 ]17}自定义指标:
1@Component2public class CustomMetrics {3 4 private final Counter orderCounter;5 private final Gauge activeUsers;6 7 public CustomMetrics(MeterRegistry registry) {8 // 计数器9 this.orderCounter = Counter.builder("orders.created")10 .description("Total orders created")11 .tag("type", "online")12 .register(registry);13 14 // 仪表15 this.activeUsers = Gauge.builder("users.active", this, CustomMetrics::getActiveUserCount)16 .description("Active users count")17 .register(registry);18 }19 20 public void recordOrder() {21 orderCounter.increment();22 }23 24 private double getActiveUserCount() {25 // 返回活跃用户数26 return 100.0;27 }28}7. 动态修改日志级别
1# 查看日志配置2GET /actuator/loggers34# 查看特定 logger5GET /actuator/loggers/com.example.service67# 修改日志级别8POST /actuator/loggers/com.example.service9Content-Type: application/json1011{12 "configuredLevel": "DEBUG"13}8. 安全配置
1@Configuration2public class ActuatorSecurityConfig {3 4 @Bean5 public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {6 http.authorizeRequests()7 .requestMatchers("/actuator/health").permitAll()8 .requestMatchers("/actuator/info").permitAll()9 .requestMatchers("/actuator/**").hasRole("ADMIN")10 .and()11 .httpBasic();12 return http.build();13 }14}或使用配置文件:
1spring:2 security:3 user:4 name: admin5 password: admin1236 roles: ADMIN9. 集成 Prometheus
添加依赖:
1<dependency>2 <groupId>io.micrometer</groupId>3 <artifactId>micrometer-registry-prometheus</artifactId>4</dependency>配置:
1management:2 endpoints:3 web:4 exposure:5 include: prometheus6 metrics:7 export:8 prometheus:9 enabled: true访问:
1GET /actuator/prometheus10. Actuator 的优势
- 生产就绪:提供生产级别的监控功能
- 开箱即用:无需额外配置即可使用基本功能
- 可扩展:支持自定义健康检查和指标
- 集成友好:易于集成 Prometheus、Grafana 等监控系统
- 安全可控:支持端点级别的安全控制
- 实时监控:实时查看应用状态和指标
- 问题诊断:提供线程转储、堆转储等诊断工具
11. 最佳实践
- 生产环境限制端点访问权限
- 使用独立的管理端口
- 定期监控健康检查和指标
- 集成到监控系统(Prometheus + Grafana)
- 自定义业务相关的健康检查和指标
19. Spring Boot 2.x 与 1.x 版本有哪些主要的改进和区别?
20. Spring Boot 3.x 与 2.x 版本有哪些主要的改进和区别?
21. 说说你对 Spring Boot 事件机制的了解?
22. 在 Spring Boot 中如何实现多数据源配置?
答案:
Spring Boot 支持多种方式配置多数据源。
方式一:手动配置多数据源(推荐)
步骤一:配置文件
1spring:2 datasource:3 primary:4 jdbc-url: jdbc:mysql://localhost:3306/db15 username: root6 password: 1234567 driver-class-name: com.mysql.cj.jdbc.Driver8 secondary:9 jdbc-url: jdbc:mysql://localhost:3306/db210 username: root11 password: 12345612 driver-class-name: com.mysql.cj.jdbc.Driver步骤二:配置主数据源
1@Configuration2@MapperScan(3 basePackages = "com.example.mapper.primary",4 sqlSessionFactoryRef = "primarySqlSessionFactory"5)6public class PrimaryDataSourceConfig {7 8 @Bean(name = "primaryDataSource")9 @ConfigurationProperties(prefix = "spring.datasource.primary")10 @Primary11 public DataSource primaryDataSource() {12 return DataSourceBuilder.create().build();13 }14 15 @Bean(name = "primarySqlSessionFactory")16 @Primary17 public SqlSessionFactory primarySqlSessionFactory(18 @Qualifier("primaryDataSource") DataSource dataSource) throws Exception {19 SqlSessionFactoryBean bean = new SqlSessionFactoryBean();20 bean.setDataSource(dataSource);21 bean.setMapperLocations(22 new PathMatchingResourcePatternResolver()23 .getResources("classpath:mapper/primary/*.xml")24 );25 return bean.getObject();26 }27 28 @Bean(name = "primaryTransactionManager")29 @Primary30 public DataSourceTransactionManager primaryTransactionManager(31 @Qualifier("primaryDataSource") DataSource dataSource) {32 return new DataSourceTransactionManager(dataSource);33 }34}步骤三:配置从数据源
1@Configuration2@MapperScan(3 basePackages = "com.example.mapper.secondary",4 sqlSessionFactoryRef = "secondarySqlSessionFactory"5)6public class SecondaryDataSourceConfig {7 8 @Bean(name = "secondaryDataSource")9 @ConfigurationProperties(prefix = "spring.datasource.secondary")10 public DataSource secondaryDataSource() {11 return DataSourceBuilder.create().build();12 }13 14 @Bean(name = "secondarySqlSessionFactory")15 public SqlSessionFactory secondarySqlSessionFactory(16 @Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {17 SqlSessionFactoryBean bean = new SqlSessionFactoryBean();18 bean.setDataSource(dataSource);19 bean.setMapperLocations(20 new PathMatchingResourcePatternResolver()21 .getResources("classpath:mapper/secondary/*.xml")22 );23 return bean.getObject();24 }25 26 @Bean(name = "secondaryTransactionManager")27 public DataSourceTransactionManager secondaryTransactionManager(28 @Qualifier("secondaryDataSource") DataSource dataSource) {29 return new DataSourceTransactionManager(dataSource);30 }31}步骤四:使用
1// 主数据源 Mapper2package com.example.mapper.primary;34@Mapper5public interface UserMapper {6 User findById(Long id);7}89// 从数据源 Mapper10package com.example.mapper.secondary;1112@Mapper13public interface OrderMapper {14 Order findById(Long id);15}1617// Service18@Service19public class BusinessService {20 @Autowired21 private UserMapper userMapper; // 使用主数据源22 23 @Autowired24 private OrderMapper orderMapper; // 使用从数据源25 26 public void doSomething() {27 User user = userMapper.findById(1L);28 Order order = orderMapper.findById(1L);29 }30}方式二:使用 AbstractRoutingDataSource 动态切换
步骤一:定义数据源枚举
1public enum DataSourceType {2 PRIMARY,3 SECONDARY4}步骤二:数据源上下文
1public class DataSourceContextHolder {2 private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();3 4 public static void setDataSource(DataSourceType dataSourceType) {5 contextHolder.set(dataSourceType);6 }7 8 public static DataSourceType getDataSource() {9 return contextHolder.get();10 }11 12 public static void clearDataSource() {13 contextHolder.remove();14 }15}步骤三:动态数据源
1public class DynamicDataSource extends AbstractRoutingDataSource {2 3 @Override4 protected Object determineCurrentLookupKey() {5 return DataSourceContextHolder.getDataSource();6 }7}步骤四:配置数据源
1@Configuration2public class DataSourceConfig {3 4 @Bean5 @ConfigurationProperties(prefix = "spring.datasource.primary")6 public DataSource primaryDataSource() {7 return DataSourceBuilder.create().build();8 }9 10 @Bean11 @ConfigurationProperties(prefix = "spring.datasource.secondary")12 public DataSource secondaryDataSource() {13 return DataSourceBuilder.create().build();14 }15 16 @Bean17 @Primary18 public DataSource dynamicDataSource() {19 DynamicDataSource dynamicDataSource = new DynamicDataSource();20 21 Map<Object, Object> dataSourceMap = new HashMap<>();22 dataSourceMap.put(DataSourceType.PRIMARY, primaryDataSource());23 dataSourceMap.put(DataSourceType.SECONDARY, secondaryDataSource());24 25 dynamicDataSource.setTargetDataSources(dataSourceMap);26 dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());27 28 return dynamicDataSource;29 }30}步骤五:自定义注解
1@Target({ElementType.METHOD, ElementType.TYPE})2@Retention(RetentionPolicy.RUNTIME)3public @interface DataSource {4 DataSourceType value() default DataSourceType.PRIMARY;5}步骤六:AOP 切面
1@Aspect2@Component3@Order(1) // 确保在事务之前执行4public class DataSourceAspect {5 6 @Around("@annotation(com.example.annotation.DataSource)")7 public Object around(ProceedingJoinPoint point) throws Throwable {8 MethodSignature signature = (MethodSignature) point.getSignature();9 Method method = signature.getMethod();10 11 DataSource dataSource = method.getAnnotation(DataSource.class);12 if (dataSource != null) {13 DataSourceContextHolder.setDataSource(dataSource.value());14 }15 16 try {17 return point.proceed();18 } finally {19 DataSourceContextHolder.clearDataSource();20 }21 }22}步骤七:使用
1@Service2public class UserService {3 4 @Autowired5 private UserMapper userMapper;6 7 // 使用主数据源(默认)8 public User findById(Long id) {9 return userMapper.findById(id);10 }11 12 // 使用从数据源13 @DataSource(DataSourceType.SECONDARY)14 public User findByIdFromSecondary(Long id) {15 return userMapper.findById(id);16 }17}方式三:使用 JPA 多数据源
1@Configuration2@EnableJpaRepositories(3 basePackages = "com.example.repository.primary",4 entityManagerFactoryRef = "primaryEntityManagerFactory",5 transactionManagerRef = "primaryTransactionManager"6)7public class PrimaryJpaConfig {8 9 @Bean10 @Primary11 @ConfigurationProperties(prefix = "spring.datasource.primary")12 public DataSource primaryDataSource() {13 return DataSourceBuilder.create().build();14 }15 16 @Bean17 @Primary18 public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(19 EntityManagerFactoryBuilder builder,20 @Qualifier("primaryDataSource") DataSource dataSource) {21 return builder22 .dataSource(dataSource)23 .packages("com.example.entity.primary")24 .persistenceUnit("primary")25 .build();26 }27 28 @Bean29 @Primary30 public PlatformTransactionManager primaryTransactionManager(31 @Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {32 return new JpaTransactionManager(entityManagerFactory);33 }34}方式四:使用 Sharding-JDBC(分库分表)
1<dependency>2 <groupId>org.apache.shardingsphere</groupId>3 <artifactId>sharding-jdbc-spring-boot-starter</artifactId>4 <version>4.1.1</version>5</dependency>1spring:2 shardingsphere:3 datasource:4 names: ds0,ds15 ds0:6 type: com.zaxxer.hikari.HikariDataSource7 driver-class-name: com.mysql.cj.jdbc.Driver8 jdbc-url: jdbc:mysql://localhost:3306/db09 username: root10 password: 12345611 ds1:12 type: com.zaxxer.hikari.HikariDataSource13 driver-class-name: com.mysql.cj.jdbc.Driver14 jdbc-url: jdbc:mysql://localhost:3306/db115 username: root16 password: 12345617 sharding:18 tables:19 t_order:20 actual-data-nodes: ds$->{0..1}.t_order_$->{0..1}21 table-strategy:22 inline:23 sharding-column: order_id24 algorithm-expression: t_order_$->{order_id % 2}25 database-strategy:26 inline:27 sharding-column: user_id28 algorithm-expression: ds$->{user_id % 2}注意事项:
- 事务管理:多数据源事务需要使用分布式事务(如 Seata)
- 连接池配置:每个数据源都需要配置连接池
- 性能考虑:避免跨数据源的复杂查询
- 数据一致性:注意多数据源之间的数据一致性问题
23. Spring Boot 中如何实现异步处理?
答案:
Spring Boot 提供了 @Async 注解来实现异步处理。
步骤一:启用异步支持
1@SpringBootApplication2@EnableAsync // 启用异步支持3public class Application {4 public static void main(String[] args) {5 SpringApplication.run(Application.class, args);6 }7}步骤二:使用 @Async 注解
1@Service2@Slf4j3public class AsyncService {4 5 /**6 * 无返回值的异步方法7 */8 @Async9 public void asyncMethod() {10 log.info("Async method start, Thread: {}", Thread.currentThread().getName());11 try {12 Thread.sleep(5000); // 模拟耗时操作13 } catch (InterruptedException e) {14 e.printStackTrace();15 }16 log.info("Async method end");17 }18 19 /**20 * 有返回值的异步方法 - 使用 Future21 */22 @Async23 public Future<String> asyncMethodWithFuture() {24 log.info("Async method with Future start");25 try {26 Thread.sleep(3000);27 } catch (InterruptedException e) {28 e.printStackTrace();29 }30 return new AsyncResult<>("Result from async method");31 }32 33 /**34 * 有返回值的异步方法 - 使用 CompletableFuture(推荐)35 */36 @Async37 public CompletableFuture<String> asyncMethodWithCompletableFuture() {38 log.info("Async method with CompletableFuture start");39 try {40 Thread.sleep(3000);41 } catch (InterruptedException e) {42 e.printStackTrace();43 }44 return CompletableFuture.completedFuture("Result from async method");45 }46}步骤三:调用异步方法
1@RestController2@Slf4j3public class AsyncController {4 5 @Autowired6 private AsyncService asyncService;7 8 @GetMapping("/async")9 public String testAsync() {10 log.info("Controller start");11 12 // 调用异步方法(不会阻塞)13 asyncService.asyncMethod();14 15 log.info("Controller end");16 return "Async method called";17 }18 19 @GetMapping("/async-with-result")20 public String testAsyncWithResult() throws Exception {21 log.info("Controller start");22 23 // 调用有返回值的异步方法24 CompletableFuture<String> future = asyncService.asyncMethodWithCompletableFuture();25 26 // 等待结果27 String result = future.get(); // 阻塞等待28 29 log.info("Controller end, result: {}", result);30 return result;31 }32 33 @GetMapping("/async-multiple")34 public String testMultipleAsync() throws Exception {35 log.info("Controller start");36 37 // 并发执行多个异步任务38 CompletableFuture<String> future1 = asyncService.asyncMethodWithCompletableFuture();39 CompletableFuture<String> future2 = asyncService.asyncMethodWithCompletableFuture();40 CompletableFuture<String> future3 = asyncService.asyncMethodWithCompletableFuture();41 42 // 等待所有任务完成43 CompletableFuture.allOf(future1, future2, future3).join();44 45 log.info("All async methods completed");46 return "All completed";47 }48}配置线程池(推荐)
方式一:配置文件
1spring:2 task:3 execution:4 pool:5 core-size: 10 # 核心线程数6 max-size: 20 # 最大线程数7 queue-capacity: 100 # 队列容量8 keep-alive: 60s # 线程空闲时间9 thread-name-prefix: async-task-方式二:Java 配置(更灵活)
1@Configuration2@EnableAsync3public class AsyncConfig implements AsyncConfigurer {4 5 @Override6 public Executor getAsyncExecutor() {7 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();8 9 // 核心线程数10 executor.setCorePoolSize(10);11 12 // 最大线程数13 executor.setMaxPoolSize(20);14 15 // 队列容量16 executor.setQueueCapacity(100);17 18 // 线程名称前缀19 executor.setThreadNamePrefix("async-task-");20 21 // 拒绝策略22 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());23 24 // 等待所有任务完成后再关闭线程池25 executor.setWaitForTasksToCompleteOnShutdown(true);26 27 // 等待时间28 executor.setAwaitTerminationSeconds(60);29 30 executor.initialize();31 return executor;32 }33 34 @Override35 public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {36 return new CustomAsyncExceptionHandler();37 }38}3940// 自定义异常处理器41public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {42 43 @Override44 public void handleUncaughtException(Throwable ex, Method method, Object... params) {45 System.err.println("Async method exception: " + method.getName());46 System.err.println("Exception: " + ex.getMessage());47 }48}多个线程池配置
1@Configuration2@EnableAsync3public class MultipleAsyncConfig {4 5 /**6 * 用于 IO 密集型任务7 */8 @Bean(name = "ioTaskExecutor")9 public Executor ioTaskExecutor() {10 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();11 executor.setCorePoolSize(20);12 executor.setMaxPoolSize(50);13 executor.setQueueCapacity(200);14 executor.setThreadNamePrefix("io-task-");15 executor.initialize();16 return executor;17 }18 19 /**20 * 用于 CPU 密集型任务21 */22 @Bean(name = "cpuTaskExecutor")23 public Executor cpuTaskExecutor() {24 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();25 executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());26 executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);27 executor.setQueueCapacity(50);28 executor.setThreadNamePrefix("cpu-task-");29 executor.initialize();30 return executor;31 }32}3334// 使用指定的线程池35@Service36public class TaskService {37 38 @Async("ioTaskExecutor")39 public void ioTask() {40 // IO 密集型任务41 }42 43 @Async("cpuTaskExecutor")44 public void cpuTask() {45 // CPU 密集型任务46 }47}异步方法的注意事项
1. 必须是 public 方法
1// ✅ 正确2@Async3public void asyncMethod() { }45// ❌ 错误6@Async7private void asyncMethod() { }2. 不能在同一个类中调用
1@Service2public class MyService {3 4 // ❌ 错误:同类调用不会异步执行5 public void method1() {6 this.asyncMethod(); // 不会异步7 }8 9 @Async10 public void asyncMethod() { }11}1213// ✅ 正确:通过注入调用14@Service15public class MyService {16 @Autowired17 private MyService self; // 注入自己18 19 public void method1() {20 self.asyncMethod(); // 会异步执行21 }22 23 @Async24 public void asyncMethod() { }25}3. 异常处理
1@Service2@Slf4j3public class AsyncService {4 5 @Async6 public CompletableFuture<String> asyncMethodWithException() {7 try {8 // 业务逻辑9 if (someCondition) {10 throw new RuntimeException("Error occurred");11 }12 return CompletableFuture.completedFuture("Success");13 } catch (Exception e) {14 log.error("Async method error", e);15 return CompletableFuture.failedFuture(e);16 }17 }18}1920// 调用时处理异常21@RestController22public class AsyncController {23 24 @GetMapping("/async-with-exception")25 public String testAsyncWithException() {26 asyncService.asyncMethodWithException()27 .thenAccept(result -> {28 System.out.println("Success: " + result);29 })30 .exceptionally(ex -> {31 System.err.println("Error: " + ex.getMessage());32 return null;33 });34 35 return "Called";36 }37}实际应用场景
1. 发送邮件
1@Service2public class EmailService {3 4 @Async5 public void sendEmail(String to, String subject, String content) {6 // 发送邮件(耗时操作)7 log.info("Sending email to: {}", to);8 // ...9 }10}2. 日志记录
1@Service2public class LogService {3 4 @Async5 public void saveLog(OperationLog log) {6 // 保存日志到数据库7 logRepository.save(log);8 }9}3. 数据同步
1@Service2public class DataSyncService {3 4 @Async5 public CompletableFuture<Void> syncData() {6 // 同步数据到其他系统7 return CompletableFuture.runAsync(() -> {8 // 同步逻辑9 });10 }11}4. 批量处理
1@Service2public class BatchService {3 4 @Async5 public CompletableFuture<List<Result>> batchProcess(List<Data> dataList) {6 List<Result> results = dataList.stream()7 .map(this::process)8 .collect(Collectors.toList());9 10 return CompletableFuture.completedFuture(results);11 }12 13 private Result process(Data data) {14 // 处理单个数据15 return new Result();16 }17}24. Spring 如何在 SpringBoot 启动时执行特定代码?有哪些方式?
25. Spring SpringBoot(Spring)中为什么不推荐使用 @Autowired ?
26. @Async 什么时候会失效?
学习指南
核心要点:
- Spring Boot 自动配置原理
- Starter 机制和自定义Starter
- 配置文件和环境管理
- Spring Boot 在微服务中的应用
学习路径建议:
- 掌握 Spring Boot 自动配置机制
- 熟悉常用Starter的使用
- 学习配置文件和环境管理
- 理解Spring Boot在微服务架构中的应用
评论区 / Comments