跳到主要内容

Java SpringBoot 面试题集

总题数: 26道 | 重点领域: 自动配置、Starter、微服务 | 难度分布: 中级

本文档整理了 Java Spring Boot 的完整26道面试题目,涵盖自动配置、Starter机制、微服务开发等各个方面。


面试题目列表

1. Spring Boot 工程启动以后,我希望将数据库中已有的固定内容,打入到 Redis 缓存中,请问如何处理?

答案:

有以下几种方式可以在 Spring Boot 启动后执行初始化操作:

方式一:实现 CommandLineRunner 接口

java
1@Component
2@Order(1) // 多个实现时可以指定执行顺序
3public class RedisInitRunner implements CommandLineRunner {
4 @Autowired
5 private UserRepository userRepository;
6 @Autowired
7 private RedisTemplate<String, Object> redisTemplate;
8
9 @Override
10 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 接口

java
1@Component
2public class RedisInitApplicationRunner implements ApplicationRunner {
3 @Autowired
4 private DataService dataService;
5 @Autowired
6 private RedisTemplate<String, Object> redisTemplate;
7
8 @Override
9 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 注解

java
1@Component
2public class RedisInitializer {
3 @Autowired
4 private DataRepository dataRepository;
5 @Autowired
6 private RedisTemplate<String, Object> redisTemplate;
7
8 @PostConstruct
9 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 事件

java
1@Component
2public class RedisInitListener {
3 @Autowired
4 private RedisTemplate<String, Object> redisTemplate;
5 @Autowired
6 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. 启动入口

java
1@SpringBootApplication
2public 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 ├─ 加载 ApplicationContextInitializer
4 ├─ 加载 ApplicationListener
5 └─ 推断主启动类
6
72. 执行 run() 方法
8 ├─ 创建 StopWatch(记录启动时间)
9 ├─ 配置 Headless 属性
10 ├─ 获取并启动 SpringApplicationRunListeners
11 ├─ 准备环境(Environment)
12 │ ├─ 创建环境对象
13 │ ├─ 配置环境
14 │ └─ 发布 ApplicationEnvironmentPreparedEvent
15 ├─ 打印 Banner
16 ├─ 创建 ApplicationContext
17 │ ├─ 根据应用类型创建对应的上下文
18 │ └─ AnnotationConfigServletWebServerApplicationContext
19 ├─ 准备 ApplicationContext
20 │ ├─ 设置环境
21 │ ├─ 后置处理(postProcessApplicationContext)
22 │ ├─ 应用初始化器(applyInitializers)
23 │ └─ 发布 ApplicationContextInitializedEvent
24 ├─ 加载 Bean 定义
25 │ ├─ 加载主配置类
26 │ └─ 发布 ApplicationPreparedEvent
27 ├─ 刷新上下文(refresh)
28 │ ├─ 准备刷新
29 │ ├─ 获取 BeanFactory
30 │ ├─ 准备 BeanFactory
31 │ ├─ 后置处理 BeanFactory
32 │ ├─ 调用 BeanFactoryPostProcessor
33 │ ├─ 注册 BeanPostProcessor
34 │ ├─ 初始化消息源
35 │ ├─ 初始化事件广播器
36 │ ├─ 刷新特定上下文(创建 Web 容器)
37 │ ├─ 注册监听器
38 │ ├─ 实例化所有非懒加载单例 Bean
39 │ └─ 完成刷新(发布 ContextRefreshedEvent)
40 ├─ 刷新后处理(afterRefresh)
41 ├─ 发布 ApplicationStartedEvent
42 ├─ 调用 Runners(CommandLineRunner、ApplicationRunner)
43 ├─ 发布 ApplicationReadyEvent
44 └─ 启动完成

3. 核心步骤说明

  • 推断应用类型:根据 classpath 中的类判断是 Web 应用还是普通应用
  • 加载初始化器和监听器:从 META-INF/spring.factories 加载
  • 准备环境:加载配置文件(application.properties/yml)
  • 创建上下文:根据应用类型创建对应的 ApplicationContext
  • 自动配置:通过 @EnableAutoConfiguration 加载自动配置类
  • 启动内嵌容器:如 Tomcat、Jetty、Undertow
  • 执行 Runners:执行用户自定义的初始化逻辑

4. 关键源码位置

java
1// SpringApplication.java
2public 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 容器,可以独立运行

主要特点:

  1. 简化配置

    • 无需繁琐的 XML 配置
    • 使用注解和自动配置
    • 提供默认配置值
  2. 快速开发

    • 提供大量 Starter 依赖
    • 自动配置常用框架
    • 快速搭建项目骨架
  3. 独立运行

    • 内嵌 Tomcat、Jetty、Undertow
    • 打包成可执行 JAR
    • 无需部署到外部容器
  4. 生产就绪

    • 提供健康检查、监控、指标
    • Actuator 端点
    • 外部化配置

与传统 Spring 的区别:

特性传统 SpringSpring Boot
配置方式大量 XML 配置注解 + 自动配置
依赖管理手动管理版本Starter 统一管理
容器部署需要外部容器内嵌容器
启动方式部署 WAR 包运行 JAR 包
开发效率配置繁琐快速开发

示例:

java
1// 传统 Spring 需要大量配置
2// Spring Boot 只需一个注解
3@SpringBootApplication
4public 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 排除不需要的自动配置
java
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 包
xml
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
  • 环境变量、命令行参数
  • 配置优先级机制
yaml
1# application.yml
2server:
3 port: 8080
4spring:
5 datasource:
6 url: jdbc:mysql://localhost:3306/db

6. Spring Boot CLI

  • 命令行工具
  • 快速原型开发
  • Groovy 脚本支持

7. 开发者工具(DevTools)

  • 热部署(自动重启)
  • LiveReload 支持
  • 开发时配置
xml
1<dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-devtools</artifactId>
4 <optional>true</optional>
5</dependency>

8. 简化的测试

  • @SpringBootTest 注解
  • 自动配置测试环境
  • Mock 支持
java
1@SpringBootTest
2class ApplicationTests {
3 @Test
4 void contextLoads() {
5 }
6}

5. Spring Boot 是如何通过 main 方法启动 web 项目的?

答案:

Spring Boot 通过 SpringApplication.run() 方法启动 Web 项目,核心原理如下:

1. 启动入口

java
1@SpringBootApplication
2public class Application {
3 public static void main(String[] args) {
4 SpringApplication.run(Application.class, args);
5 }
6}

2. 启动原理

第一步:创建 SpringApplication 对象

java
1public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
2 this.resourceLoader = resourceLoader;
3 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
4 // 推断应用类型:SERVLET、REACTIVE、NONE
5 this.webApplicationType = WebApplicationType.deduceFromClasspath();
6 // 加载初始化器
7 setInitializers(getSpringFactoriesInstances(ApplicationContextInitializer.class));
8 // 加载监听器
9 setListeners(getSpringFactoriesInstances(ApplicationListener.class));
10 // 推断主启动类
11 this.mainApplicationClass = deduceMainApplicationClass();
12}

第二步:推断应用类型

java
1static WebApplicationType deduceFromClasspath() {
2 // 如果存在 DispatcherHandler 且不存在 DispatcherServlet,则为 REACTIVE
3 if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
4 && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)) {
5 return WebApplicationType.REACTIVE;
6 }
7 // 如果不存在 Servlet 相关类,则为 NONE
8 for (String className : SERVLET_INDICATOR_CLASSES) {
9 if (!ClassUtils.isPresent(className, null)) {
10 return WebApplicationType.NONE;
11 }
12 }
13 // 否则为 SERVLET
14 return WebApplicationType.SERVLET;
15}

第三步:创建 ApplicationContext

java
1protected ConfigurableApplicationContext createApplicationContext() {
2 Class<?> contextClass = this.applicationContextClass;
3 if (contextClass == null) {
4 switch (this.webApplicationType) {
5 case SERVLET:
6 // Web 应用创建 AnnotationConfigServletWebServerApplicationContext
7 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 容器

java
1// ServletWebServerApplicationContext.java
2@Override
3protected 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}
12
13private 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

java
1// TomcatServletWebServerFactory.java
2@Override
3public WebServer getWebServer(ServletContextInitializer... initializers) {
4 Tomcat tomcat = new Tomcat();
5 // 配置 Tomcat
6 Connector connector = new Connector(this.protocol);
7 connector.setPort(getPort());
8 tomcat.getService().addConnector(connector);
9 // 配置 Context
10 prepareContext(tomcat.getHost(), initializers);
11 // 返回 TomcatWebServer
12 return getTomcatWebServer(tomcat);
13}
14
15// TomcatWebServer.java
16public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
17 this.tomcat = tomcat;
18 this.autoStart = autoStart;
19 // 启动 Tomcat
20 initialize();
21}
22
23private void initialize() {
24 // 启动 Tomcat 服务器
25 this.tomcat.start();
26}

3. 关键点总结

  1. 应用类型推断:根据 classpath 中的类判断是否为 Web 应用
  2. 创建专用上下文:Web 应用使用 ServletWebServerApplicationContext
  3. 自动配置 Web 容器:通过 ServletWebServerFactory 创建内嵌容器
  4. 启动容器:在 onRefresh() 阶段启动 Tomcat/Jetty/Undertow
  5. 注册 DispatcherServlet:自动配置 Spring MVC 的前端控制器

4. 为什么可以通过 main 方法启动?

  • Spring Boot 将 Web 容器(Tomcat)作为一个普通对象内嵌到应用中
  • 不需要将应用部署到外部容器
  • 通过 Java 应用的方式启动,容器随应用启动而启动
  • 打包成可执行 JAR,包含所有依赖和内嵌容器

6. SpringBoot 是如何实现自动配置的?

答案:

Spring Boot 自动配置是通过 @EnableAutoConfiguration 注解实现的,核心原理是条件化配置和 SPI 机制。

1. 核心注解

java
1@SpringBootApplication
2 └─ @EnableAutoConfiguration
3 └─ @Import(AutoConfigurationImportSelector.class)
java
1@Target(ElementType.TYPE)
2@Retention(RetentionPolicy.RUNTIME)
3@Documented
4@Inherited
5@SpringBootConfiguration
6@EnableAutoConfiguration // 核心注解
7@ComponentScan
8public @interface SpringBootApplication {
9}

2. 自动配置原理

第一步:加载自动配置类

java
1// AutoConfigurationImportSelector.java
2protected 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 文件

properties
1# spring-boot-autoconfigure.jar 中的 META-INF/spring.factories
2org.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 注解进行条件判断:

java
1@Configuration
2@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
3@ConditionalOnMissingBean(DataSource.class)
4@EnableConfigurationProperties(DataSourceProperties.class)
5public class DataSourceAutoConfiguration {
6
7 @Bean
8 @ConditionalOnMissingBean
9 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 自动配置:

java
1@Configuration
2@ConditionalOnClass(RedisOperations.class)
3@EnableConfigurationProperties(RedisProperties.class)
4@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
5public class RedisAutoConfiguration {
6
7 @Bean
8 @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 @Bean
17 @ConditionalOnMissingBean
18 public StringRedisTemplate stringRedisTemplate(
19 RedisConnectionFactory redisConnectionFactory) {
20 StringRedisTemplate template = new StringRedisTemplate();
21 template.setConnectionFactory(redisConnectionFactory);
22 return template;
23 }
24}

5. 配置属性绑定

java
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 setters
8}
yaml
1# application.yml
2spring:
3 datasource:
4 url: jdbc:mysql://localhost:3306/db
5 username: root
6 password: 123456
7 driver-class-name: com.mysql.cj.jdbc.Driver

6. 自动配置流程总结

11. @SpringBootApplication
2 └─ @EnableAutoConfiguration
3 └─ @Import(AutoConfigurationImportSelector.class)
4
52. AutoConfigurationImportSelector
6 └─ 读取 META-INF/spring.factories
7 └─ 加载所有自动配置类
8
93. 条件判断
10 └─ @ConditionalOnXxx 注解
11 └─ 满足条件则生效
12
134. 属性绑定
14 └─ @ConfigurationProperties
15 └─ 绑定配置文件属性
16
175. 创建 Bean
18 └─ 注册到 Spring 容器

7. 如何排除自动配置

java
1// 方式一:使用 exclude 属性
2@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
3
4// 方式二:配置文件
5spring:
6 autoconfigure:
7 exclude:
8 - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

8. 查看生效的自动配置

yaml
1# application.yml
2debug: true # 启动时打印自动配置报告

或使用 Actuator:

1GET /actuator/conditions

7. Spring Boot 支持哪些嵌入 Web 容器?

答案:

Spring Boot 支持以下三种主流的嵌入式 Web 容器:

1. Tomcat(默认)

xml
1<dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-web</artifactId>
4 <!-- 默认包含 Tomcat -->
5</dependency>

配置示例:

yaml
1server:
2 port: 8080
3 tomcat:
4 max-threads: 200
5 min-spare-threads: 10
6 max-connections: 10000
7 accept-count: 100

2. Jetty

xml
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>

配置示例:

yaml
1server:
2 port: 8080
3 jetty:
4 threads:
5 max: 200
6 min: 10

3. Undertow

xml
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>

配置示例:

yaml
1server:
2 port: 8080
3 undertow:
4 threads:
5 io: 4
6 worker: 200
7 buffer-size: 1024
8 direct-buffers: true

容器对比:

特性TomcatJettyUndertow
默认容器
性能中等中等
内存占用中等较小最小
并发处理优秀
Servlet 支持完整完整完整
WebSocket支持支持支持
社区活跃度最高中等
适用场景通用轻量级高并发

选择建议:

  • Tomcat:默认选择,社区支持最好,适合大多数场景
  • Jetty:轻量级,适合资源受限环境
  • Undertow:高性能,适合高并发场景,内存占用最小

8. Spring Boot 中 application.properties 和 application.yml 的区别是什么?

答案:

两者都是 Spring Boot 的配置文件,主要区别在于格式和可读性。

1. 格式对比

application.properties:

properties
1server.port=8080
2server.servlet.context-path=/api
3
4spring.datasource.url=jdbc:mysql://localhost:3306/db
5spring.datasource.username=root
6spring.datasource.password=123456
7spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
8
9spring.jpa.hibernate.ddl-auto=update
10spring.jpa.show-sql=true

application.yml:

yaml
1server:
2 port: 8080
3 servlet:
4 context-path: /api
5
6spring:
7 datasource:
8 url: jdbc:mysql://localhost:3306/db
9 username: root
10 password: 123456
11 driver-class-name: com.mysql.cj.jdbc.Driver
12 jpa:
13 hibernate:
14 ddl-auto: update
15 show-sql: true

2. 主要区别

特性application.propertiesapplication.yml
格式key=value层级结构
可读性较差(重复前缀多)好(层级清晰)
配置复杂度简单配置友好复杂配置友好
数组/列表不直观直观
多行文本不支持支持
注释# 或 !#
文件大小较大较小
学习成本稍高

3. 数组/列表配置对比

properties:

properties
1spring.profiles.active=dev,test
2my.servers[0]=dev.example.com
3my.servers[1]=prod.example.com

yml:

yaml
1spring:
2 profiles:
3 active: dev,test
4
5my:
6 servers:
7 - dev.example.com
8 - prod.example.com

4. 多行文本配置

properties:

properties
1# 不支持多行,需要使用 \n
2my.description=This is a long \
3description that spans \
4multiple lines

yml:

yaml
1# 支持多行
2my:
3 description: |
4 This is a long
5 description that spans
6 multiple lines

5. 加载优先级

当两个文件同时存在时:

  • application.properties 优先级高于 application.yml
  • 相同配置项,properties 会覆盖 yml

6. 使用建议

  • 简单项目:使用 properties,配置少,简单直观
  • 复杂项目:使用 yml,层级清晰,可读性好
  • 团队习惯:根据团队习惯选择
  • 推荐:yml 格式,更现代化,可读性更好

9. 如何在 Spring Boot 中定义和读取自定义配置?

答案:

Spring Boot 提供多种方式读取自定义配置。

方式一:使用 @Value 注解

配置文件:

yaml
1app:
2 name: MyApplication
3 version: 1.0.0
4 author: John Doe

读取配置:

java
1@Component
2public 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(推荐)

配置文件:

yaml
1app:
2 name: MyApplication
3 version: 1.0.0
4 author: John Doe
5 email: john@example.com
6 servers:
7 - dev.example.com
8 - prod.example.com
9 database:
10 host: localhost
11 port: 3306
12 username: root

配置类:

java
1@Component
2@ConfigurationProperties(prefix = "app")
3@Data // Lombok
4public 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 @Data
13 public static class Database {
14 private String host;
15 private int port;
16 private String username;
17 }
18}

使用配置:

java
1@Service
2public class AppService {
3 @Autowired
4 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

java
1@Component
2public class ConfigReader {
3 @Autowired
4 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:

properties
1custom.api.url=https://api.example.com
2custom.api.key=abc123
3custom.api.timeout=5000

配置类:

java
1@Configuration
2@PropertySource("classpath:custom.properties")
3@ConfigurationProperties(prefix = "custom.api")
4@Data
5public class CustomApiProperties {
6 private String url;
7 private String key;
8 private int timeout;
9}

方式五:读取复杂对象配置

配置文件:

yaml
1users:
2 - name: Alice
3 age: 25
4 email: alice@example.com
5 - name: Bob
6 age: 30
7 email: bob@example.com

配置类:

java
1@Component
2@ConfigurationProperties(prefix = "users")
3@Data
4public class UsersProperties {
5 private List<User> users = new ArrayList<>();
6
7 @Data
8 public static class User {
9 private String name;
10 private int age;
11 private String email;
12 }
13}

启用 @ConfigurationProperties:

方式 1:在配置类上添加 @Component

java
1@Component
2@ConfigurationProperties(prefix = "app")
3public class AppProperties { }

方式 2:在启动类上添加 @EnableConfigurationProperties

java
1@SpringBootApplication
2@EnableConfigurationProperties(AppProperties.class)
3public class Application { }

方式 3:使用 @ConfigurationPropertiesScan

java
1@SpringBootApplication
2@ConfigurationPropertiesScan("com.example.config")
3public class Application { }

最佳实践:

  • 简单配置使用 @Value
  • 复杂配置使用 @ConfigurationProperties
  • 使用类型安全的配置类
  • 提供默认值
  • 添加配置验证(JSR-303)

配置验证示例:

java
1@Component
2@ConfigurationProperties(prefix = "app")
3@Validated
4@Data
5public class AppProperties {
6 @NotBlank
7 private String name;
8
9 @Min(1)
10 @Max(65535)
11 private int port;
12
13 @Email
14 private String email;
15}

10. Spring Boot 配置文件加载优先级你知道吗?

答案:

Spring Boot 配置文件的加载遵循特定的优先级顺序,优先级高的配置会覆盖优先级低的配置。

1. 配置源优先级(从高到低)

11. 命令行参数
2 java -jar app.jar --server.port=9090
3
42. SPRING_APPLICATION_JSON 中的属性
5 SPRING_APPLICATION_JSON='{"server.port":9090}' java -jar app.jar
6
73. ServletConfig 初始化参数
8
94. ServletContext 初始化参数
10
115. JNDI 属性(java:comp/env)
12
136. Java 系统属性(System.getProperties())
14 java -Dserver.port=9090 -jar app.jar
15
167. 操作系统环境变量
17 export SERVER_PORT=9090
18
198. RandomValuePropertySource(random.* 属性)
20
219. jar 包外部的 application-{profile}.properties/yml
22
2310. jar 包内部的 application-{profile}.properties/yml
24
2511. jar 包外部的 application.properties/yml
26
2712. jar 包内部的 application.properties/yml
28
2913. @PropertySource 注解指定的配置文件
30
3114. 默认属性(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.jar
9 └── application.yml # 优先级最低(jar 内)

3. Profile 配置优先级

yaml
1# application.yml(基础配置)
2server:
3 port: 8080
4
5# application-dev.yml(开发环境)
6server:
7 port: 8081
8
9# application-prod.yml(生产环境)
10server:
11 port: 8082

激活 Profile:

bash
1# 方式一:命令行
2java -jar app.jar --spring.profiles.active=dev
3
4# 方式二:配置文件
5spring:
6 profiles:
7 active: dev
8
9# 方式三:环境变量
10export SPRING_PROFILES_ACTIVE=dev

优先级: application-{profile}.yml > application.yml

4. 文件格式优先级

当同时存在时:properties > yml > yaml

1application.properties # 优先级最高
2application.yml # 优先级中等
3application.yaml # 优先级最低

5. 实际应用示例

场景: 本地开发时覆盖配置

项目中的配置(application.yml):

yaml
1server:
2 port: 8080
3spring:
4 datasource:
5 url: jdbc:mysql://prod-server:3306/db

本地配置文件(./config/application.yml):

yaml
1server:
2 port: 8081
3spring:
4 datasource:
5 url: jdbc:mysql://localhost:3306/db

命令行参数:

bash
1java -jar app.jar --server.port=9090

最终生效:

  • server.port=9090(命令行优先级最高)
  • spring.datasource.url=jdbc:mysql://localhost:3306/db(本地配置覆盖)

6. 查看配置加载情况

方式一:启用 debug 日志

yaml
1logging:
2 level:
3 org.springframework.boot.context.config: DEBUG

方式二:使用 Actuator

xml
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.jar
2├── META-INF/
3│ └── MANIFEST.MF
4└── com/
5 └── example/
6 └── MyClass.class

Spring Boot JAR 结构:

1my-app.jar
2├── META-INF/
3│ └── MANIFEST.MF # 包含启动类信息
4├── BOOT-INF/
5│ ├── classes/ # 应用的 class 文件
6│ │ └── com/
7│ │ └── example/
8│ │ └── Application.class
9│ └── lib/ # 所有依赖的 jar 包
10│ ├── spring-boot-*.jar
11│ ├── spring-core-*.jar
12│ └── ...
13└── org/
14 └── springframework/
15 └── boot/
16 └── loader/ # Spring Boot 类加载器

2. MANIFEST.MF 对比

普通 JAR:

1Manifest-Version: 1.0
2Main-Class: com.example.MyClass

Spring Boot JAR:

1Manifest-Version: 1.0
2Spring-Boot-Version: 3.0.0
3Main-Class: org.springframework.boot.loader.JarLauncher
4Start-Class: com.example.Application
5Spring-Boot-Classes: BOOT-INF/classes/
6Spring-Boot-Lib: BOOT-INF/lib/

3. 主要区别

特性普通 JARSpring Boot JAR
依赖包含不包含依赖包含所有依赖(Fat JAR)
启动方式需要指定 classpath直接运行
Main-Class应用主类JarLauncher
类加载器默认类加载器自定义类加载器
文件大小大(包含所有依赖)
独立运行
嵌入容器有(Tomcat/Jetty/Undertow)

4. 启动方式对比

普通 JAR:

bash
1# 需要指定所有依赖
2java -cp "app.jar:lib/*" com.example.MyClass

Spring Boot JAR:

bash
1# 直接运行
2java -jar app.jar

5. Spring Boot JAR 启动原理

java
1// JarLauncher 启动流程
21. JarLauncher.main()
3 ├─ 创建自定义类加载器(LaunchedURLClassLoader
4 ├─ 加载 BOOT-INF/lib/ 下的所有 jar
5 ├─ 加载 BOOT-INF/classes/ 下的类
6 └─ 反射调用 Start-Class(真正的主类)
7
82. Application.main()
9 └─ SpringApplication.run()

6. 打包配置

Maven:

xml
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:

groovy
1plugins {
2 id 'org.springframework.boot' version '3.0.0'
3}
4
5bootJar {
6 mainClass = 'com.example.Application'
7}

7. 解压查看 Spring Boot JAR

bash
1# 解压 jar 包
2jar -xvf app.jar
3
4# 或使用 unzip
5unzip app.jar -d app-extracted

8. 优缺点对比

Spring Boot JAR 优点:

  • 独立运行,无需外部依赖
  • 包含所有依赖,部署简单
  • 内嵌 Web 容器,无需额外安装
  • 一个文件即可运行

Spring Boot JAR 缺点:

  • 文件体积大(通常几十 MB)
  • 启动稍慢(需要解压和加载依赖)
  • 更新依赖需要重新打包

9. 生成可执行 JAR

Spring Boot JAR 可以在 Linux/Unix 系统上直接执行:

bash
1# 添加执行权限
2chmod +x app.jar
3
4# 直接运行
5./app.jar

这是因为 Spring Boot 在 JAR 文件开头添加了 shell 脚本:

bash
1#!/bin/bash
2exec java -jar "$0" "$@"

10. 打包成 WAR

如果需要部署到外部容器:

java
1@SpringBootApplication
2public class Application extends SpringBootServletInitializer {
3
4 @Override
5 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}
xml
1<packaging>war</packaging>

12. Spring Boot 是否可以使用 XML 配置 ?

答案:

可以,但不推荐。Spring Boot 推荐使用 Java 配置和注解,但仍然支持 XML 配置。

方式一:使用 @ImportResource 导入 XML 配置

XML 配置文件(beans.xml):

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/beans
5 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>

启动类:

java
1@SpringBootApplication
2@ImportResource("classpath:beans.xml")
3public class Application {
4 public static void main(String[] args) {
5 SpringApplication.run(Application.class, args);
6 }
7}

方式二:在配置类中导入

java
1@Configuration
2@ImportResource({"classpath:beans.xml", "classpath:services.xml"})
3public class XmlConfig {
4}

为什么不推荐使用 XML?

  1. 冗余:XML 配置比注解更冗长
  2. 类型安全:Java 配置有编译时检查
  3. 重构友好:IDE 可以更好地支持 Java 配置
  4. Spring Boot 理念:约定优于配置

对比示例:

XML 方式:

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 配置方式(推荐):

java
1@Configuration
2public class DataSourceConfig {
3 @Bean
4 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

配置说明:

yaml
1server:
2 port: 8080
3 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:

yaml
1server:
2 tomcat:
3 threads:
4 max: 200
5 max-connections: 10000
6 accept-count: 100

Jetty:

yaml
1server:
2 jetty:
3 threads:
4 max: 200
5 min: 8
6 max-queue-capacity: 0 # 无限制

Undertow:

yaml
1server:
2 undertow:
3 threads:
4 io: 4 # IO 线程数(通常为 CPU 核心数)
5 worker: 200 # 工作线程数
6 buffer-size: 1024

性能调优建议:

1. CPU 密集型应用:

yaml
1server:
2 tomcat:
3 threads:
4 max: 100 # CPU 核心数 * 2

2. IO 密集型应用:

yaml
1server:
2 tomcat:
3 threads:
4 max: 500 # 可以设置更大
5 max-connections: 20000

3. 高并发场景:

yaml
1server:
2 tomcat:
3 threads:
4 max: 800
5 max-connections: 20000
6 accept-count: 500

监控和测试:

java
1@RestController
2public 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}

压力测试工具:

bash
1# 使用 Apache Bench
2ab -n 10000 -c 200 http://localhost:8080/api/test
3
4# 使用 JMeter
5# 配置线程组,模拟并发请求

14. 如何理解 Spring Boot 中的 starter?

答案:

Starter 是 Spring Boot 的核心特性之一,是一组预定义的依赖描述符,用于简化项目依赖管理。

1. Starter 的作用

  • 简化依赖管理:一个 Starter 包含一组相关依赖
  • 版本管理:自动管理依赖版本,避免版本冲突
  • 自动配置:提供默认配置,开箱即用
  • 约定优于配置:遵循最佳实践

2. 常用 Starter

Starter功能包含的主要依赖
spring-boot-starter-webWeb 开发Spring MVC, Tomcat, Jackson
spring-boot-starter-data-jpaJPA 数据访问Hibernate, Spring Data JPA
spring-boot-starter-data-redisRedisLettuce, Spring Data Redis
spring-boot-starter-security安全框架Spring Security
spring-boot-starter-test测试JUnit, Mockito, AssertJ
spring-boot-starter-aopAOPSpring AOP, AspectJ
spring-boot-starter-validation数据验证Hibernate Validator
spring-boot-starter-actuator监控Actuator
spring-boot-starter-logging日志Logback, SLF4J
spring-boot-starter-jdbcJDBCHikariCP, Spring JDBC

3. Starter 示例

使用前(传统方式):

xml
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 方式):

xml
1<dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-web</artifactId>
4</dependency>

4. Starter 的命名规范

  • 官方 Starterspring-boot-starter-*
  • 第三方 Starter*-spring-boot-starter

示例:

xml
1<!-- 官方 -->
2<dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-web</artifactId>
5</dependency>
6
7<!-- 第三方(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.xml
3└── src/main/java/
4 └── com/example/starter/
5 ├── MyAutoConfiguration.java
6 ├── MyProperties.java
7 └── MyService.java
8└── src/main/resources/
9 └── META-INF/
10 └── spring.factories

pom.xml:

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>

配置属性类:

java
1@ConfigurationProperties(prefix = "my.service")
2@Data
3public class MyProperties {
4 private String name = "default";
5 private int timeout = 30;
6}

服务类:

java
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}

自动配置类:

java
1@Configuration
2@EnableConfigurationProperties(MyProperties.class)
3@ConditionalOnClass(MyService.class)
4public class MyAutoConfiguration {
5
6 @Bean
7 @ConditionalOnMissingBean
8 public MyService myService(MyProperties properties) {
9 return new MyService(properties);
10 }
11}

spring.factories:

properties
1org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
2com.example.starter.MyAutoConfiguration

步骤二:使用自定义 Starter

xml
1<dependency>
2 <groupId>com.example</groupId>
3 <artifactId>my-spring-boot-starter</artifactId>
4 <version>1.0.0</version>
5</dependency>
yaml
1# application.yml
2my:
3 service:
4 name: MyApp
5 timeout: 60
java
1@RestController
2public class TestController {
3 @Autowired
4 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 包含的依赖

bash
1# Maven
2mvn dependency:tree
3
4# Gradle
5gradle dependencies

15. Spring Boot 如何处理跨域请求(CORS)?

答案:

Spring Boot 提供多种方式处理跨域请求(CORS)。

方式一:使用 @CrossOrigin 注解(局部配置)

在 Controller 上:

java
1@RestController
2@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}

在方法上:

java
1@RestController
2@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

java
1@Configuration
2public class CorsConfig implements WebMvcConfigurer {
3
4 @Override
5 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

java
1@Configuration
2public class CorsConfig {
3
4 @Bean
5 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+)

yaml
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: true
9 max-age: 3600

方式四:使用 Spring Security 配置

java
1@Configuration
2@EnableWebSecurity
3public class SecurityConfig {
4
5 @Bean
6 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 @Bean
16 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是否允许发送 Cookietrue
maxAge预检请求缓存时间(秒)3600

CORS 工作原理:

简单请求:

1客户端 -> 服务器
2GET /api/users HTTP/1.1
3Origin: http://localhost:3000
4
5服务器 -> 客户端
6HTTP/1.1 200 OK
7Access-Control-Allow-Origin: http://localhost:3000
8Access-Control-Allow-Credentials: true

预检请求(Preflight):

1客户端 -> 服务器(OPTIONS 请求)
2OPTIONS /api/users HTTP/1.1
3Origin: http://localhost:3000
4Access-Control-Request-Method: POST
5Access-Control-Request-Headers: Content-Type
6
7服务器 -> 客户端
8HTTP/1.1 200 OK
9Access-Control-Allow-Origin: http://localhost:3000
10Access-Control-Allow-Methods: GET, POST, PUT, DELETE
11Access-Control-Allow-Headers: Content-Type
12Access-Control-Max-Age: 3600
13
14客户端 -> 服务器(实际请求)
15POST /api/users HTTP/1.1
16Origin: http://localhost:3000
17Content-Type: application/json

常见问题:

1. 允许所有域名(开发环境):

java
1config.addAllowedOriginPattern("*"); // 使用 Pattern 而不是 Origin
2config.setAllowCredentials(true);

2. 生产环境配置:

java
1// 只允许特定域名
2config.setAllowedOrigins(Arrays.asList(
3 "https://www.example.com",
4 "https://app.example.com"
5));

3. 动态配置:

java
1@Configuration
2public class CorsConfig implements WebMvcConfigurer {
3
4 @Value("${cors.allowed-origins}")
5 private String[] allowedOrigins;
6
7 @Override
8 public void addCorsMappings(CorsRegistry registry) {
9 registry.addMapping("/**")
10 .allowedOrigins(allowedOrigins)
11 .allowedMethods("*")
12 .allowedHeaders("*")
13 .allowCredentials(true);
14 }
15}
yaml
1# application.yml
2cors:
3 allowed-origins:
4 - http://localhost:3000
5 - https://example.com

16. 在 Spring Boot 中你是怎么使用拦截器的?

答案:

Spring Boot 中使用拦截器需要实现 HandlerInterceptor 接口并注册到 Spring MVC 配置中。

步骤一:创建拦截器

java
1@Component
2public class LoginInterceptor implements HandlerInterceptor {
3
4 /**
5 * 在请求处理之前执行
6 * 返回 true 继续执行,返回 false 中断请求
7 */
8 @Override
9 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 // 验证 token
21 if (!validateToken(token)) {
22 response.setStatus(HttpServletResponse.SC_FORBIDDEN);
23 return false;
24 }
25
26 // 将用户信息存入 request
27 request.setAttribute("userId", getUserIdFromToken(token));
28 return true;
29 }
30
31 /**
32 * 在请求处理之后、视图渲染之前执行
33 */
34 @Override
35 public void postHandle(HttpServletRequest request,
36 HttpServletResponse response,
37 Object handler,
38 ModelAndView modelAndView) throws Exception {
39 // 可以修改 ModelAndView
40 System.out.println("postHandle executed");
41 }
42
43 /**
44 * 在整个请求完成之后执行(视图渲染完成后)
45 * 无论是否发生异常都会执行
46 */
47 @Override
48 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 // 解析 token
63 return "user123";
64 }
65}

步骤二:注册拦截器

java
1@Configuration
2public class WebConfig implements WebMvcConfigurer {
3
4 @Autowired
5 private LoginInterceptor loginInterceptor;
6
7 @Override
8 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. 日志拦截器

java
1@Component
2@Slf4j
3public class LogInterceptor implements HandlerInterceptor {
4
5 @Override
6 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 @Override
19 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. 权限拦截器

java
1@Component
2public class PermissionInterceptor implements HandlerInterceptor {
3
4 @Autowired
5 private UserService userService;
6
7 @Override
8 public boolean preHandle(HttpServletRequest request,
9 HttpServletResponse response,
10 Object handler) throws Exception {
11 // 检查是否是 HandlerMethod
12 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}
39
40// 自定义注解
41@Target(ElementType.METHOD)
42@Retention(RetentionPolicy.RUNTIME)
43public @interface RequirePermission {
44 String value();
45}
46
47// 使用
48@RestController
49public class AdminController {
50
51 @RequirePermission("admin:user:delete")
52 @DeleteMapping("/users/{id}")
53 public void deleteUser(@PathVariable String id) {
54 // ...
55 }
56}

3. 限流拦截器

java
1@Component
2public class RateLimitInterceptor implements HandlerInterceptor {
3
4 private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
5
6 @Override
7 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 Requests
20 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响应返回

多个拦截器配置:

java
1@Configuration
2public class WebConfig implements WebMvcConfigurer {
3
4 @Autowired
5 private LoginInterceptor loginInterceptor;
6 @Autowired
7 private LogInterceptor logInterceptor;
8 @Autowired
9 private PermissionInterceptor permissionInterceptor;
10
11 @Override
12 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 MVCServlet
作用范围Spring MVC 请求所有请求
访问 Spring Bean可以可以(需配置)
执行时机Controller 前后Servlet 前后
方法数量3 个2 个
获取方法信息可以不可以

使用建议:

  • 拦截器:处理业务逻辑相关的拦截(登录、权限、日志)
  • 过滤器:处理通用的请求/响应处理(编码、压缩、CORS)

17. SpringBoot 中如何实现定时任务 ?

答案:

Spring Boot 提供多种方式实现定时任务。

方式一:使用 @Scheduled 注解(推荐)

步骤一:启用定时任务

java
1@SpringBootApplication
2@EnableScheduling // 启用定时任务
3public class Application {
4 public static void main(String[] args) {
5 SpringApplication.run(Application.class, args);
6 }
7}

步骤二:创建定时任务

java
1@Component
2@Slf4j
3public 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格式:秒 分 时 日 月 周 [年]
2
3示例:
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:15
100 0 0 * * ? # 每天0点
11*/10 * * * * ? # 每10秒
12
13特殊字符:
14* : 所有值
15? : 不指定值(日和周互斥)
16- : 范围
17, : 列举
18/ : 步长
19L : 最后(Last)
20W : 工作日(Weekday)
21# : 第几个

配置文件:

yaml
1# application.yml
2task:
3 cron: "0 0 2 * * ?" # 每天凌晨2点

方式二:使用 TaskScheduler

java
1@Configuration
2public class SchedulerConfig {
3
4 @Bean
5 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}
14
15@Component
16public class DynamicScheduledTask {
17
18 @Autowired
19 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

添加依赖:

xml
1<dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-starter-quartz</artifactId>
4</dependency>

创建 Job:

java
1public class MyJob extends QuartzJobBean {
2
3 @Override
4 protected void executeInternal(JobExecutionContext context) {
5 System.out.println("Quartz job executed: " + LocalDateTime.now());
6 }
7}

配置 Quartz:

java
1@Configuration
2public class QuartzConfig {
3
4 @Bean
5 public JobDetail jobDetail() {
6 return JobBuilder.newJob(MyJob.class)
7 .withIdentity("myJob")
8 .storeDurably()
9 .build();
10 }
11
12 @Bean
13 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}

配置线程池:

yaml
1# application.yml
2spring:
3 task:
4 scheduling:
5 pool:
6 size: 10 # 线程池大小
7 thread-name-prefix: scheduled-task-

或使用 Java 配置:

java
1@Configuration
2public class SchedulingConfig implements SchedulingConfigurer {
3
4 @Override
5 public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
6 taskRegistrar.setScheduler(taskExecutor());
7 }
8
9 @Bean
10 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}

异步定时任务:

java
1@SpringBootApplication
2@EnableScheduling
3@EnableAsync // 启用异步
4public class Application {
5 public static void main(String[] args) {
6 SpringApplication.run(Application.class, args);
7 }
8}
9
10@Component
11@Slf4j
12public class AsyncScheduledTasks {
13
14 @Async
15 @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. 避免长时间任务阻塞:使用异步或增加线程池大小
  2. 异常处理:捕获异常,避免任务中断
  3. 幂等性:确保任务可以重复执行
  4. 监控告警:记录任务执行情况
  5. 分布式锁:多实例环境下避免重复执行

示例:带异常处理和日志的定时任务

java
1@Component
2@Slf4j
3public 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. 添加依赖

xml
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/beansSpring Beans
/actuator/mappings请求映射
/actuator/configprops配置属性
/actuator/loggers日志配置
/actuator/heapdump堆转储
/actuator/threaddump线程转储
/actuator/prometheusPrometheus 指标

3. 配置端点

yaml
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 健康检查

默认健康检查:

json
1GET /actuator/health
2
3{
4 "status": "UP",
5 "components": {
6 "diskSpace": {
7 "status": "UP",
8 "details": {
9 "total": 500000000000,
10 "free": 300000000000,
11 "threshold": 10485760
12 }
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}

自定义健康检查:

java
1@Component
2public class CustomHealthIndicator implements HealthIndicator {
3
4 @Override
5 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 信息端点

配置文件:

yaml
1info:
2 app:
3 name: My Application
4 version: 1.0.0
5 description: Spring Boot Application
6 company:
7 name: Example Corp

Java 配置:

java
1@Component
2public class AppInfoContributor implements InfoContributor {
3
4 @Override
5 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/metrics
2
3{
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.used
2
3{
4 "name": "jvm.memory.used",
5 "measurements": [
6 {
7 "statistic": "VALUE",
8 "value": 123456789
9 }
10 ],
11 "availableTags": [
12 {
13 "tag": "area",
14 "values": ["heap", "nonheap"]
15 }
16 ]
17}

自定义指标:

java
1@Component
2public 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. 动态修改日志级别

bash
1# 查看日志配置
2GET /actuator/loggers
3
4# 查看特定 logger
5GET /actuator/loggers/com.example.service
6
7# 修改日志级别
8POST /actuator/loggers/com.example.service
9Content-Type: application/json
10
11{
12 "configuredLevel": "DEBUG"
13}

8. 安全配置

java
1@Configuration
2public class ActuatorSecurityConfig {
3
4 @Bean
5 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}

或使用配置文件:

yaml
1spring:
2 security:
3 user:
4 name: admin
5 password: admin123
6 roles: ADMIN

9. 集成 Prometheus

添加依赖:

xml
1<dependency>
2 <groupId>io.micrometer</groupId>
3 <artifactId>micrometer-registry-prometheus</artifactId>
4</dependency>

配置:

yaml
1management:
2 endpoints:
3 web:
4 exposure:
5 include: prometheus
6 metrics:
7 export:
8 prometheus:
9 enabled: true

访问:

1GET /actuator/prometheus

10. 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 支持多种方式配置多数据源。

方式一:手动配置多数据源(推荐)

步骤一:配置文件

yaml
1spring:
2 datasource:
3 primary:
4 jdbc-url: jdbc:mysql://localhost:3306/db1
5 username: root
6 password: 123456
7 driver-class-name: com.mysql.cj.jdbc.Driver
8 secondary:
9 jdbc-url: jdbc:mysql://localhost:3306/db2
10 username: root
11 password: 123456
12 driver-class-name: com.mysql.cj.jdbc.Driver

步骤二:配置主数据源

java
1@Configuration
2@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 @Primary
11 public DataSource primaryDataSource() {
12 return DataSourceBuilder.create().build();
13 }
14
15 @Bean(name = "primarySqlSessionFactory")
16 @Primary
17 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 @Primary
30 public DataSourceTransactionManager primaryTransactionManager(
31 @Qualifier("primaryDataSource") DataSource dataSource) {
32 return new DataSourceTransactionManager(dataSource);
33 }
34}

步骤三:配置从数据源

java
1@Configuration
2@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}

步骤四:使用

java
1// 主数据源 Mapper
2package com.example.mapper.primary;
3
4@Mapper
5public interface UserMapper {
6 User findById(Long id);
7}
8
9// 从数据源 Mapper
10package com.example.mapper.secondary;
11
12@Mapper
13public interface OrderMapper {
14 Order findById(Long id);
15}
16
17// Service
18@Service
19public class BusinessService {
20 @Autowired
21 private UserMapper userMapper; // 使用主数据源
22
23 @Autowired
24 private OrderMapper orderMapper; // 使用从数据源
25
26 public void doSomething() {
27 User user = userMapper.findById(1L);
28 Order order = orderMapper.findById(1L);
29 }
30}

方式二:使用 AbstractRoutingDataSource 动态切换

步骤一:定义数据源枚举

java
1public enum DataSourceType {
2 PRIMARY,
3 SECONDARY
4}

步骤二:数据源上下文

java
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}

步骤三:动态数据源

java
1public class DynamicDataSource extends AbstractRoutingDataSource {
2
3 @Override
4 protected Object determineCurrentLookupKey() {
5 return DataSourceContextHolder.getDataSource();
6 }
7}

步骤四:配置数据源

java
1@Configuration
2public class DataSourceConfig {
3
4 @Bean
5 @ConfigurationProperties(prefix = "spring.datasource.primary")
6 public DataSource primaryDataSource() {
7 return DataSourceBuilder.create().build();
8 }
9
10 @Bean
11 @ConfigurationProperties(prefix = "spring.datasource.secondary")
12 public DataSource secondaryDataSource() {
13 return DataSourceBuilder.create().build();
14 }
15
16 @Bean
17 @Primary
18 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}

步骤五:自定义注解

java
1@Target({ElementType.METHOD, ElementType.TYPE})
2@Retention(RetentionPolicy.RUNTIME)
3public @interface DataSource {
4 DataSourceType value() default DataSourceType.PRIMARY;
5}

步骤六:AOP 切面

java
1@Aspect
2@Component
3@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}

步骤七:使用

java
1@Service
2public class UserService {
3
4 @Autowired
5 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 多数据源

java
1@Configuration
2@EnableJpaRepositories(
3 basePackages = "com.example.repository.primary",
4 entityManagerFactoryRef = "primaryEntityManagerFactory",
5 transactionManagerRef = "primaryTransactionManager"
6)
7public class PrimaryJpaConfig {
8
9 @Bean
10 @Primary
11 @ConfigurationProperties(prefix = "spring.datasource.primary")
12 public DataSource primaryDataSource() {
13 return DataSourceBuilder.create().build();
14 }
15
16 @Bean
17 @Primary
18 public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
19 EntityManagerFactoryBuilder builder,
20 @Qualifier("primaryDataSource") DataSource dataSource) {
21 return builder
22 .dataSource(dataSource)
23 .packages("com.example.entity.primary")
24 .persistenceUnit("primary")
25 .build();
26 }
27
28 @Bean
29 @Primary
30 public PlatformTransactionManager primaryTransactionManager(
31 @Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
32 return new JpaTransactionManager(entityManagerFactory);
33 }
34}

方式四:使用 Sharding-JDBC(分库分表)

xml
1<dependency>
2 <groupId>org.apache.shardingsphere</groupId>
3 <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
4 <version>4.1.1</version>
5</dependency>
yaml
1spring:
2 shardingsphere:
3 datasource:
4 names: ds0,ds1
5 ds0:
6 type: com.zaxxer.hikari.HikariDataSource
7 driver-class-name: com.mysql.cj.jdbc.Driver
8 jdbc-url: jdbc:mysql://localhost:3306/db0
9 username: root
10 password: 123456
11 ds1:
12 type: com.zaxxer.hikari.HikariDataSource
13 driver-class-name: com.mysql.cj.jdbc.Driver
14 jdbc-url: jdbc:mysql://localhost:3306/db1
15 username: root
16 password: 123456
17 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_id
24 algorithm-expression: t_order_$->{order_id % 2}
25 database-strategy:
26 inline:
27 sharding-column: user_id
28 algorithm-expression: ds$->{user_id % 2}

注意事项:

  1. 事务管理:多数据源事务需要使用分布式事务(如 Seata)
  2. 连接池配置:每个数据源都需要配置连接池
  3. 性能考虑:避免跨数据源的复杂查询
  4. 数据一致性:注意多数据源之间的数据一致性问题

23. Spring Boot 中如何实现异步处理?

答案:

Spring Boot 提供了 @Async 注解来实现异步处理。

步骤一:启用异步支持

java
1@SpringBootApplication
2@EnableAsync // 启用异步支持
3public class Application {
4 public static void main(String[] args) {
5 SpringApplication.run(Application.class, args);
6 }
7}

步骤二:使用 @Async 注解

java
1@Service
2@Slf4j
3public class AsyncService {
4
5 /**
6 * 无返回值的异步方法
7 */
8 @Async
9 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 * 有返回值的异步方法 - 使用 Future
21 */
22 @Async
23 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 @Async
37 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}

步骤三:调用异步方法

java
1@RestController
2@Slf4j
3public class AsyncController {
4
5 @Autowired
6 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}

配置线程池(推荐)

方式一:配置文件

yaml
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 配置(更灵活)

java
1@Configuration
2@EnableAsync
3public class AsyncConfig implements AsyncConfigurer {
4
5 @Override
6 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 @Override
35 public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
36 return new CustomAsyncExceptionHandler();
37 }
38}
39
40// 自定义异常处理器
41public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
42
43 @Override
44 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}

多个线程池配置

java
1@Configuration
2@EnableAsync
3public 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}
33
34// 使用指定的线程池
35@Service
36public 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 方法

java
1// ✅ 正确
2@Async
3public void asyncMethod() { }
4
5// ❌ 错误
6@Async
7private void asyncMethod() { }

2. 不能在同一个类中调用

java
1@Service
2public class MyService {
3
4 // ❌ 错误:同类调用不会异步执行
5 public void method1() {
6 this.asyncMethod(); // 不会异步
7 }
8
9 @Async
10 public void asyncMethod() { }
11}
12
13// ✅ 正确:通过注入调用
14@Service
15public class MyService {
16 @Autowired
17 private MyService self; // 注入自己
18
19 public void method1() {
20 self.asyncMethod(); // 会异步执行
21 }
22
23 @Async
24 public void asyncMethod() { }
25}

3. 异常处理

java
1@Service
2@Slf4j
3public class AsyncService {
4
5 @Async
6 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}
19
20// 调用时处理异常
21@RestController
22public 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. 发送邮件

java
1@Service
2public class EmailService {
3
4 @Async
5 public void sendEmail(String to, String subject, String content) {
6 // 发送邮件(耗时操作)
7 log.info("Sending email to: {}", to);
8 // ...
9 }
10}

2. 日志记录

java
1@Service
2public class LogService {
3
4 @Async
5 public void saveLog(OperationLog log) {
6 // 保存日志到数据库
7 logRepository.save(log);
8 }
9}

3. 数据同步

java
1@Service
2public class DataSyncService {
3
4 @Async
5 public CompletableFuture<Void> syncData() {
6 // 同步数据到其他系统
7 return CompletableFuture.runAsync(() -> {
8 // 同步逻辑
9 });
10 }
11}

4. 批量处理

java
1@Service
2public class BatchService {
3
4 @Async
5 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 在微服务中的应用

学习路径建议:

  1. 掌握 Spring Boot 自动配置机制
  2. 熟悉常用Starter的使用
  3. 学习配置文件和环境管理
  4. 理解Spring Boot在微服务架构中的应用
forum

评论区 / Comments