Springboot 实用技巧 查缺补漏
Spring Boot 各种面试的八股文背了不少,但是在实际开发中这20 个实用技巧是经常用到的,2025希望程序猿们努力过得愉快。
1. @ConfigurationProperties 管理复杂配置
在复杂项目中,配置项众多,分散在各处的配置不利于管理。这时,@ConfigurationProperties
注解就能派上用场。它能将多个相关配置映射到一个类中,使代码更简洁。
定义一个配置类:
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String name;
private int version;
// getters and setters
}
配置文件中:
app:
name: mySpringApp
version: 1
在其他组件中,通过@Autowired
注入AppProperties
,就可以方便地获取配置信息:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class MyComponent {
@Autowired
private AppProperties appProperties;
public void doSomething() {
String appName = appProperties.getName();
int appVersion = appProperties.getVersion();
// 使用配置信息进行业务逻辑处理
}
}
2. 自定义启动 Banner
每次启动 Spring Boot 应用,看到默认的启动 Banner 是不是觉得有点单调?其实,我们可以自定义这个 Banner,让启动界面充满个性。只需在src/main/resources
目录下创建一个banner.txt
文件,在里面写入你想要展示的内容,比如公司 logo、项目名称、版本号等。
例如:
____ _ _ _
| _ \| | (_) | | |
| |_) | __ _ ___| |__ _ _ __ ___ __ _| |_
| _ < / _` |/ __| '_ \| | '_ ` _ \ / _` | __|
| |_) | (_| | (__| | | | | | | | | | (_| | |_
|____/ \__,_|\___|_| |_|_|_| |_| |_|\__,_|\__|
这样,下次启动应用时,就能看到自定义的 Banner 。
3. 排除不必要的自动配置
Spring Boot 的自动配置功能十分强大,但有时我们并不需要加载所有的自动配置组件,这时候可以使用@SpringBootApplication
的exclude
属性来排除不需要的模块,从而加快启动速度,减少内存占用。
比如,若项目中不使用数据库相关的自动配置,可以这样写:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
通过排除DataSourceAutoConfiguration
,Spring Boot 在启动时就不会尝试加载数据库相关的配置和组件,启动过程更加轻量化。
4. CommandLineRunner 执行启动任务
当 Spring Boot 应用启动完成后,有时我们需要执行一些初始化任务,比如初始化数据库、加载默认数据等。这时,CommandLineRunner
接口就能派上用场。
创建一个实现CommandLineRunner
接口的组件:
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class StartupRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("Application started, running initial tasks...");
// 在这里编写具体的初始化任务逻辑,比如数据库初始化操作
}
}
Spring Boot 在启动过程中,会检测到实现了CommandLineRunner
接口的组件,并在应用启动完成后,按顺序执行它们的run
方法。如果有多个CommandLineRunner
实现类,可以通过实现org.springframework.core.Ordered
接口或使用@Order
注解来指定执行顺序。
5. SpringApplicationBuilder 自定义启动方式
SpringApplicationBuilder
为我们提供了更多灵活的启动配置方式,通过链式调用,可以在代码层面方便地设置应用的各种属性。
例如,设置应用的运行环境为开发环境,并指定服务器端口为 8081:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
public class MyApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(MyApplication.class)
.profiles("dev")
.properties("server.port=8081")
.run(args);
}
}
这种方式特别适合一些需要根据不同条件灵活配置启动参数的场景,相比在配置文件中设置,在代码中控制更加直观和便捷。
6. @Profile 切换不同环境配置
在开发、测试、生产等不同环境中,应用的配置往往有所不同,比如数据库连接信息、日志级别等。Spring Boot 的@Profile
注解可以轻松实现不同环境配置的切换。
首先,定义不同环境的配置类:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
@Configuration
public class DataSourceConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/devdb");
dataSource.setUsername("devuser");
dataSource.setPassword("devpassword");
return dataSource;
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/proddb");
dataSource.setUsername("produser");
dataSource.setPassword("prodpassword");
return dataSource;
}
}
然后,在application.yaml
中指定当前激活的环境:
spring:
profiles:
active: dev
这样,Spring Boot 会根据spring.profiles.active
的值,自动加载对应的环境配置类,方便我们在不同环境下快速切换配置。
7. @ConditionalOnProperty 控制 Bean 加载
有时,我们希望根据配置文件中的某个属性值来决定是否加载某个 Bean,@ConditionalOnProperty
注解就可以满足这个需求,实现按需加载 Bean。
例如,假设有一个功能开关featureX.enabled
,只有当该开关为true
时,才加载FeatureX
这个 Bean:
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeatureConfig {
@Bean
@ConditionalOnProperty(name = "featureX.enabled", havingValue = "true")
public FeatureX featureX() {
return new FeatureX();
}
}
在application.properties
中配置:
featureX.enabled=true
当featureX.enabled
为true
时,Spring Boot 会创建FeatureX
的 Bean;若为false
,则不会创建,可以根据条件动态控制Bean的加载。
8. 使用 DevTools 加快开发效率
Spring Boot DevTools 是一个专门为开发过程提供便利的工具,它包含了代码热重载、缓存禁用等功能,能大大加快开发调试的速度。
只需要在pom.xml
文件中引入 DevTools 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
引入后,当我们修改代码保存时,应用会自动重启,无需手动重启,节省了大量开发时间。
9. 整合 Actuator 监控应用
Spring Boot Actuator 是一个强大的监控和管理工具,通过它,我们可以轻松了解应用的运行状态、性能指标等信息。
首先,在pom.xml
中引入 Actuator 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
引入后,应用会自动暴露一些内置的端点,比如:
/health
:用于查看应用的健康状况,返回UP
表示应用正常运行。
/metrics
:可以获取应用的各种指标数据,如内存使用情况、HTTP 请求数、CPU 使用率等。
/info
:可以展示应用的一些自定义信息,比如版本号、构建时间等,需要在application.yaml
中配置相关信息:
info:
app:
name: MySpringApp
version: 1.0.0
build:
time: 2024-10-01T12:00:00Z
通过这些端点,我们能更好地监控和管理 Spring Boot 应用,及时发现和解决潜在问题。
10. 数据校验 @Validated
在接收用户输入或处理业务数据时,数据校验不可或缺。Spring Boot 整合了 Java Validation API,借助@Validated
注解,我们能轻松实现数据校验功能。通过在方法参数前添加@Validated
,并结合各种校验注解(如@NotNull
、@Size
、@Pattern
等),Spring Boot 会自动对输入数据进行校验,校验不通过时会抛出异常,便于我们统一处理。
比如,我们有一个用户注册的 DTO 类:
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
public class UserRegistrationDTO {
@NotBlank(message = "Username cannot be blank")
private String username;
@Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$", message = "Invalid email format")
private String email;
// getters and setters
}
在控制器方法中使用@Validated
进行校验:
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@PostMapping("/register")
public String registerUser(@Validated @RequestBody UserRegistrationDTO userDTO) {
// 业务逻辑,处理注册
return "User registered successfully";
}
}
11. 优雅处理异常
在 Spring Boot 应用中,统一处理异常是非常重要的,它可以提高应用的健壮性和用户体验。我们可以通过创建一个全局异常处理器来捕获并处理应用中抛出的各种异常。
创建一个全局异常处理类,使用@ControllerAdvice
注解:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<String> handleNullPointerException(NullPointerException ex) {
return new ResponseEntity<>("A null pointer exception occurred: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
}
// 可以继续添加其他类型异常的处理方法
}
通过这种方式,当应用中抛出异常时,会被全局异常处理器捕获,并根据异常类型返回相应的 HTTP 状态码和错误信息,使前端能更好地处理异常情况,同时也方便开发人员定位问题。
12. 利用 AOP 进行日志记录和性能监控
AOP(面向切面编程)在 Spring Boot 中是一个非常强大的功能,我们可以利用它来进行日志记录、性能监控等横切关注点的处理,避免在业务代码中大量重复编写相关逻辑。
首先,在pom.xml
中引入 AOP 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后,创建一个切面类:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAndPerformanceAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAndPerformanceAspect.class);
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object logAndMeasurePerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
logger.info("Start executing method: {}", joinPoint.getSignature().getName());
try {
return joinPoint.proceed();
} finally {
long endTime = System.currentTimeMillis();
logger.info("Method {} executed in {} ms", joinPoint.getSignature().getName(), endTime - startTime);
}
}
}
上述切面类通过@Around
注解,对所有被@RequestMapping
注解标记的方法进行环绕增强,在方法执行前后记录日志,并统计方法执行的时间,方便我们对应用的性能进行监控和分析,同时也能更好地了解方法的调用情况。
13. 配置嵌入式 Servlet 容器
Spring Boot 默认使用嵌入式 Servlet 容器(如 Tomcat)来运行应用,我们可以通过配置文件或编程方式对其进行自定义配置,以优化性能或满足特定需求。
在application.yaml
中配置 Tomcat 的最大线程数和连接数:
server:
tomcat:
max-threads: 200
max-connections: 1000
如果需要更复杂的配置,也可以通过编程方式来实现
import org.apache.catalina.connector.Connector;
import org.apache.coyote.http11.Http11NioProtocol;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TomcatConfig {
@Bean
public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
Connector connector = new Connector(Http11NioProtocol.class.getName());
connector.setPort(8080);
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
protocol.setMaxThreads(200);
protocol.setMaxConnections(1000);
factory.addAdditionalTomcatConnectors(connector);
return factory;
}
}
通过这种方式,可以根据项目的实际情况,灵活调整 Servlet 容器的参数。
14. 缓存数据提升性能
在 Spring Boot 应用中,合理使用缓存可以显著提升应用的性能,减少数据库查询次数,提高响应速度。Spring Boot 提供了强大的缓存支持,通过@EnableCaching
注解开启缓存功能,并使用@Cacheable
等注解来标记需要缓存的方法。
首先,在启动类或配置类上添加@EnableCaching
注解:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
然后,在需要缓存结果的方法上使用@Cacheable
注解:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
// 这里执行查询数据库等操作获取用户信息
User user = new User();
user.setId(id);
user.setName("John Doe");
return user;
}
}
上述代码中,@Cacheable
注解表示当getUserById
方法被调用时,如果缓存中已经存在对应id
的用户信息,则直接从缓存中返回,不再执行方法内部的数据库查询操作。value
属性指定缓存的名称,key
属性指定缓存的键。
15. 异步任务处理
在 Spring Boot 应用中,有些任务可能比较耗时,如果在主线程中执行,会影响应用的响应速度。通过@Async
注解,我们可以将这些任务异步执行,使主线程能够迅速返回,提升用户体验。
首先,在启动类或配置类上添加@EnableAsync
注解,开启异步任务支持:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
然后,在需要异步执行的方法上使用@Async
注解:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class TaskService {
@Async
public void processLongTask() {
// 模拟一个耗时任务,比如复杂的数据处理、远程调用等
try {
Thread.sleep(5000);
System.out.println("Long task completed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
当调用processLongTask
方法时,它会在一个新的线程中执行,不会阻塞主线程,应用可以继续处理其他请求。
16. 配置文件外部化
在生产环境中,我们常常需要在不重新打包应用的情况下修改配置。Spring Boot 支持将配置文件外部化,这样可以方便地在不同环境中调整配置。常见的方式是将配置文件放置在应用运行目录的config
文件夹下,或者通过命令行参数指定配置文件路径。
假设我们有一个application.yaml
文件,内容如下:
app:
message: Hello, World!
在应用启动时,可以通过以下命令指定外部配置文件路径:
java -jar your-application.jar --spring.config.location=file:/path/to/your/config/
这样,即使应用已经打包成jar
文件,也能轻松修改配置,无需重新构建和部署应用。另外,还可以使用 Spring Cloud Config 实现集中化的配置管理,在分布式系统中更方便地管理各个服务的配置。
17. 动态数据源切换
在某些业务场景下,一个应用可能需要连接多个数据源,根据不同的业务需求动态切换数据源。Spring Boot 提供了灵活的机制来实现这一点。首先,配置多个数据源:
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.first")
public DataSource firstDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource secondDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public JdbcTemplate firstJdbcTemplate(@Qualifier("firstDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public JdbcTemplate secondJdbcTemplate(@Qualifier("secondDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
然后,通过 AOP(面向切面编程)实现动态数据源切换。创建一个切面类,根据方法上的自定义注解决定使用哪个数据源:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(com.example.DataSourceAnnotation)")
public Object switchDataSource(ProceedingJoinPoint joinPoint) throws Throwable {
DataSourceAnnotation annotation = joinPoint.getSignature().getDeclaringType().getAnnotation(DataSourceAnnotation.class);
if (annotation != null) {
String dataSourceName = annotation.value();
AbstractRoutingDataSource dataSource = (AbstractRoutingDataSource) dataSourceResolver.resolveDataSource();
dataSource.setCurrentLookupKey(dataSourceName);
}
try {
return joinPoint.proceed();
} finally {
// 清除数据源标识,恢复默认数据源
AbstractRoutingDataSource dataSource = (AbstractRoutingDataSource) dataSourceResolver.resolveDataSource();
dataSource.setCurrentLookupKey(null);
}
}
}
通过这种方式,应用可以在运行时根据业务需求灵活切换数据源,满足复杂业务场景下的数据访问需求。
18. 使用 Testcontainers 进行测试
在编写单元测试和集成测试时,模拟真实的数据库、消息队列等环境是很有必要的。Testcontainers 是一个开源库,它允许我们在测试中轻松创建和管理容器化的测试环境,如 MySQL、Redis、Kafka 等。
以测试一个使用 MySQL 数据库的 Spring Boot 应用为例,先在pom.xml
中添加 Testcontainers 和相关数据库驱动依赖:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<scope>test</scope>
</dependency>
然后编写测试类:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Testcontainers
@SpringBootTest
public class DatabaseTest {
@Container
public static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0.26")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpassword");
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testDatabaseInsert() {
jdbcTemplate.execute("INSERT INTO users (name, age) VALUES ('John', 30)");
int count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class);
assertEquals(1, count);
}
}
上述测试类中,MySQLContainer
会在测试启动时创建一个 MySQL 容器实例,并且自动配置好数据源连接信息供 Spring Boot 应用使用。测试完成后,容器会自动销毁,保证每次测试环境的一致性和独立性,极大提升了测试的可靠性和可重复性。
19. 定制 Jackson 数据格式
Spring Boot 默认使用 Jackson 库来处理 JSON 数据的序列化和反序列化。在实际开发中,我们可能需要根据业务需求定制 Jackson 的行为,比如修改日期格式、忽略某些属性等。
要定制日期格式,可以创建一个Jackson2ObjectMapperBuilderCustomizer
的 Bean:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).modules(module);
};
}
}
上述代码中,我们创建了一个JavaTimeModule
,并为LocalDateTime
类型定制了序列化格式,然后将其添加到Jackson2ObjectMapperBuilder
中。这样,在将LocalDateTime
类型的数据序列化为 JSON 时,就会按照指定的格式输出。此外,还可以通过@JsonIgnore
注解忽略某些属性,通过@JsonProperty
注解重命名属性等,灵活定制 JSON 数据的处理方式,满足各种复杂的业务需求。
20. 任务调度 @Scheduled
在 Spring Boot 应用里,我们常常会遇到定时任务的需求,像是定时清理过期数据、定时发送提醒邮件等。Spring Boot 借助@Scheduled
注解,能轻松实现任务调度功能。只要在方法上添加该注解,并设置好调度规则,Spring Boot 就会按设定的时间间隔或具体时间点执行任务。
例如,我们要实现一个每天凌晨 1 点执行的任务:
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class ScheduledTask {
@Scheduled(cron = "0 0 1 * * ?")
public void cleanExpiredData() {
// 执行清理过期数据的业务逻辑
System.out.println("Executing clean expired data task at " + System.currentTimeMillis());
}
}
上述代码里,cron
表达式"0 0 1 * * ?"
代表每天凌晨 1 点触发任务。当然,@Scheduled
注解还支持fixedRate
、fixedDelay
等属性,能满足不同场景下的任务调度需求。
好文分享,一起加油