程序猿沙龙,大家一人说一个 springboot 技巧。
在一次程序员的沙龙上,难得悠闲的技术探讨时光,喝着快乐水,吃着小零食,大家每人说了一个 Springboot 的 使用中比较使用的技巧。而多数 Java 开发者使用 Spring Boot 时,常停留在@RestController输出 JSON、@Autowired注入依赖的基础层面。但 Spring Boot 的真正价值,藏在其灵活的配置机制、自动化装配、生命周期管控等高阶特性中。掌握以下高阶技巧,能让你的应用从 “能用” 升级为 “健壮、可扩展、易维护” 的工业级系统。
一、组件通信:基于事件机制解耦
核心价值:替代组件间直接依赖,通过 “发布-订阅” 模式实现解耦,尤其适合跨模块、跨层级的通信场景(如用户注册后同步触发短信通知、日志记录、积分发放等操作)。Spring 事件机制基于观察者模式,支持同步/异步执行,无需引入额外中间件。
1. 基础用法:定义事件、发布者与监听器
步骤 1:定义自定义事件(继承ApplicationEvent)
// 用户注册事件(携带用户信息)
public class UserRegisteredEvent extends ApplicationEvent {
// 待传递的事件数据(用户ID、用户名)
private final Long userId;
private final String username;
// 构造函数(必须调用父类构造器)
public UserRegisteredEvent(Object source, Long userId, String username) {
super(source);
this.userId = userId;
this.username = username;
}
// Getter方法(供监听器获取数据)
public Long getUserId() {
return userId;
}
public String getUsername() {
return username;
}
}
步骤 2:定义事件发布者(注入ApplicationEventPublisher)
@Service
public class UserService {
// 注入Spring提供的事件发布器
private final ApplicationEventPublisher eventPublisher;
// 构造注入(推荐)
public UserService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
// 用户注册方法(业务逻辑执行后发布事件)
public User register(UserCreateDTO dto) {
// 1. 核心业务逻辑:保存用户到数据库
User user = new User();
user.setUsername(dto.getUsername());
user.setEmail(dto.getEmail());
user.setAge(dto.getAge());
user = userRepository.save(user); // 假设已注入UserRepository
// 2. 发布用户注册事件(无需关心谁会处理)
eventPublisher.publishEvent(new UserRegisteredEvent(this, user.getId(), user.getUsername()));
return user;
}
}
步骤 3:定义事件监听器(3种实现方式)
// 方式1:@EventListener注解(最简洁,推荐)
@Component
public class UserEventListeners {
// 监听UserRegisteredEvent事件
@EventListener
public void handleUserRegisteredEvent(UserRegisteredEvent event) {
Long userId = event.getUserId();
String username = event.getUsername();
// 执行短信通知逻辑(示例)
smsService.sendWelcomeSms(userId, username);
log.info("用户{}(ID:{})注册成功,已发送欢迎短信", username, userId);
}
// 监听UserRegisteredEvent事件,同时指定执行顺序(值越小越先执行)
@EventListener
@Order(1)
public void handleUserRegisteredLogEvent(UserRegisteredEvent event) {
// 执行日志记录逻辑
operationLogService.record("USER_REGISTER", "用户" + event.getUsername() + "注册");
}
}
// 方式2:实现ApplicationListener接口(传统方式)
@Component
public class UserRegisterSmsListener implements ApplicationListener<UserRegisteredEvent> {
@Override
public void onApplicationEvent(UserRegisteredEvent event) {
// 逻辑同方式1,适合需要继承其他类的场景
}
}
// 方式3:实现SmartApplicationListener接口(支持多事件、顺序控制)
@Component
public class UserRegisterSmartListener implements SmartApplicationListener {
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
// 只支持UserRegisteredEvent事件
return UserRegisteredEvent.class.isAssignableFrom(eventType);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
UserRegisteredEvent registerEvent = (UserRegisteredEvent) event;
// 执行积分发放逻辑
pointService.addPoint(registerEvent.getUserId(), 100); // 注册送100积分
}
// 指定执行顺序(在@Order(1)的监听器之后)
@Override
public int getOrder() {
return 2;
}
}
2. 进阶拓展:异步事件与事务绑定
- 异步事件:默认事件为同步执行,若监听器逻辑耗时(如调用第三方API),会阻塞主线程。通过
@Async实现异步执行:
// 1. 启动类添加@EnableAsync开启异步支持
@SpringBootApplication
@EnableAsync
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
// 2. 监听器方法添加@Async
@Component
public class UserEventListeners {
// 异步处理短信通知(不阻塞用户注册主线程)
@EventListener
@Async
public void handleUserRegisteredSmsEvent(UserRegisteredEvent event) {
try {
// 模拟耗时操作(调用第三方短信API)
Thread.sleep(2000);
smsService.sendWelcomeSms(event.getUserId(), event.getUsername());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("发送欢迎短信失败", e);
}
}
}
- 事务绑定事件:需确保事件在业务事务提交后执行(避免事务回滚但事件已处理),使用
@TransactionalEventListener:
@Component
public class UserEventListeners {
// 事务提交后执行(默认),支持phase参数配置时机
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleUserRegisteredAfterCommit(UserRegisteredEvent event) {
// 执行需在用户注册事务提交后的操作(如同步数据到ES)
esUserService.syncUserToEs(event.getUserId());
}
// 事务回滚后执行(可选,用于补偿操作)
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleUserRegisteredAfterRollback(UserRegisteredEvent event) {
log.error("用户{}注册事务回滚,执行补偿逻辑", event.getUsername());
}
}
二、自定义 Starter:封装企业级公共能力
核心价值:当企业内多个微服务需要重复集成 Kafka、日志框架、安全校验时,将这些逻辑封装为 Starter,实现 “引入依赖即生效”,减少重复劳动。
1. 自定义 Starter 完整步骤
步骤 1:创建 Maven 工程,定义依赖
<!-- pom.xml -->
<groupId>com.company.framework</groupId>
<artifactId>company-log-starter</artifactId>
<version>1.0.0</version>
<dependencies>
<!-- Spring Boot自动配置基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.7.10</version>
</dependency>
<!-- 配置属性校验(可选,提升用户体验) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.7.10</version>
<optional>true</optional>
</dependency>
<!-- 日志核心依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.8</version>
</dependency>
</dependencies>
步骤 2:定义配置属性类(接收用户配置)
// 日志配置属性类
@ConfigurationProperties(prefix = "company.log")
public class CompanyLogProperties {
// 默认日志输出格式
private String pattern = "[%d{yyyy-MM-dd HH:mm:ss}] [%level] [%thread] %logger{50} - %msg%n";
// 是否开启控制台输出(默认开启)
private boolean consoleEnable = true;
// 日志文件路径(默认null,不输出到文件)
private String filePath;
// Getter + Setter
}
步骤 3:编写自动配置类
// 日志自动配置类
@Configuration
@EnableConfigurationProperties(CompanyLogProperties.class) // 启用配置属性
@ConditionalOnClass(LoggerContext.class) // 仅当引入logback依赖时生效
public class CompanyLogAutoConfiguration {
private final CompanyLogProperties logProperties;
// 构造注入配置属性
public CompanyLogAutoConfiguration(CompanyLogProperties logProperties) {
this.logProperties = logProperties;
}
// 配置logback上下文(用户未自定义时生效)
@Bean
@ConditionalOnMissingBean(LoggerContext.class) // 用户自定义优先
public LoggerContext loggerContext() {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<>();
PatternLayout layout = new PatternLayout();
layout.setPattern(logProperties.getPattern());
layout.setContext(context);
layout.start();
encoder.setLayout(layout);
// 控制台输出配置
if (logProperties.isConsoleEnable()) {
ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
consoleAppender.setEncoder(encoder);
consoleAppender.setContext(context);
consoleAppender.start();
context.getLogger("ROOT").addAppender(consoleAppender);
}
// 文件输出配置(用户配置了路径才生效)
if (StringUtils.hasText(logProperties.getFilePath())) {
RollingFileAppender<ILoggingEvent> fileAppender = new RollingFileAppender<>();
fileAppender.setEncoder(encoder);
fileAppender.setContext(context);
fileAppender.setFile(logProperties.getFilePath());
// 滚动策略:按天分割日志
TimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new TimeBasedRollingPolicy<>();
rollingPolicy.setFileNamePattern(logProperties.getFilePath() + ".%d{yyyy-MM-dd}");
rollingPolicy.setContext(context);
rollingPolicy.setParent(fileAppender);
rollingPolicy.start();
fileAppender.setRollingPolicy(rollingPolicy);
fileAppender.start();
context.getLogger("ROOT").addAppender(fileAppender);
}
return context;
}
}
步骤 4:注册自动配置
在src/main/resources/META-INF下创建spring.factories文件,让 Spring Boot 扫描到自动配置类:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.company.framework.autoconfigure.CompanyLogAutoConfiguration
2. 使用自定义 Starter
其他项目只需引入依赖,即可使用预配置的日志能力:
<!-- 项目pom.xml -->
<dependency>
<groupId>com.company.framework</groupId>
<artifactId>company-log-starter</artifactId>
<version>1.0.0</version>
</dependency>
用户可通过配置文件覆盖默认值:
# 项目application.yml
company:
log:
pattern: "[%d{HH:mm:ss}] [%level] %msg%n" # 自定义日志格式
file-path: /var/log/app.log # 输出日志到文件
三、切片测试:高效测试减少资源消耗
核心价值:替代@SpringBootTest加载完整上下文,仅加载测试所需的组件(如控制器层、Repository 层),大幅提升测试速度。
1. 控制器层测试:@WebMvcTest
专注测试 Controller 的请求映射、参数绑定、响应处理:
// 仅加载UserController及相关Web组件(如DispatcherServlet)
@WebMvcTest(UserController.class)
class UserControllerTest {
// 模拟MVC请求客户端
@Autowired
private MockMvc mockMvc;
// 模拟依赖的Service(避免依赖真实Service)
@MockBean
private UserService userService;
// 测试“查询用户”接口
@Test
void shouldReturnUserWhenGetUserById() throws Exception {
// 1. 准备测试数据:模拟Service返回结果
User mockUser = new User(1L, "张三", 25, "zhangsan@example.com");
when(userService.getUserById(1L)).thenReturn(Optional.of(mockUser));
// 2. 发送GET请求,验证响应
mockMvc.perform(get("/api/users/{id}", 1L) // 请求路径:/api/users/1
.contentType(MediaType.APPLICATION_JSON) // 请求类型
.header("Authorization", "Bearer test-token")) // 请求头
.andExpect(status().isOk()) // 验证响应状态码200
.andExpect(jsonPath("$.id").value(1L)) // 验证响应体字段
.andExpect(jsonPath("$.name").value("张三"))
.andExpect(jsonPath("$.email").value("zhangsan@example.com"))
.andDo(print()); // 打印请求/响应详情(调试用)
}
// 测试“创建用户”接口(参数校验失败场景)
@Test
void shouldReturnBadRequestWhenCreateUserWithInvalidParam() throws Exception {
// 1. 准备测试数据:无效用户(姓名为空)
String invalidUserJson = "{\"name\":\"\",\"age\":25,\"email\":\"invalid-email\"}";
// 2. 发送POST请求,验证参数校验结果
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(invalidUserJson))
.andExpect(status().isBadRequest()) // 验证响应状态码400
.andExpect(jsonPath("$.code").value("PARAM_ERROR")) // 验证错误码
.andExpect(jsonPath("$.message").contains("姓名不能为空")) // 验证错误信息
.andExpect(jsonPath("$.details[0].field").value("name")); // 验证错误字段
}
}
2. Repository 层测试:@DataJpaTest
专注测试 Repository 的 CRUD 操作,自动配置内存数据库(如 H2):
// 仅加载Repository层组件及JPA相关配置
@DataJpaTest
class UserRepositoryTest {
// 注入真实的Repository(测试JPA操作)
@Autowired
private UserRepository userRepository;
// 注入实体管理器(可选,用于手动控制事务)
@Autowired
private EntityManager entityManager;
// 测试“保存用户”并“查询用户”
@Test
void shouldSaveAndFindUser() {
// 1. 保存用户
User user = new User(null, "李四", 30, "lisi@example.com");
User savedUser = userRepository.save(user);
// 2. 验证保存结果
assertNotNull(savedUser.getId());
assertEquals("李四", savedUser.getName());
// 3. 查询用户,验证结果
Optional<User> foundUser = userRepository.findByEmail("lisi@example.com");
assertTrue(foundUser.isPresent());
assertEquals(savedUser.getId(), foundUser.get().getId());
}
// 测试“分页查询用户”
@Test
void shouldFindUsersByPage() {
// 1. 准备测试数据(插入3条数据)
userRepository.saveAll(Arrays.asList(
new User(null, "用户1", 20, "user1@example.com"),
new User(null, "用户2", 22, "user2@example.com"),
new User(null, "用户3", 24, "user3@example.com")
));
// 2. 分页查询(第1页,每页2条)
Pageable pageable = PageRequest.of(0, 2, Sort.by("age").ascending());
Page<User> userPage = userRepository.findAll(pageable);
// 3. 验证分页结果
assertEquals(2, userPage.getSize()); // 每页条数
assertEquals(3, userPage.getTotalElements()); // 总数据量
assertEquals(2, userPage.getTotalPages()); // 总页数
assertEquals("用户1", userPage.getContent().get(0).getName()); // 排序正确
}
}
3. 服务层测试:@ExtendWith (MockitoExtension)
服务层依赖较少时,无需加载 Spring 上下文,直接用 Mockito 模拟依赖:
// 不加载Spring上下文,纯单元测试
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
// 模拟依赖的Repository
@Mock
private UserRepository userRepository;
// 注入真实的Service(依赖用Mock替代)
@InjectMocks
private UserServiceImpl userService;
// 测试“用户年龄校验”逻辑
@Test
void shouldThrowExceptionWhenUserAgeLessThan18() {
// 1. 准备测试数据
UserCreateDTO dto = new UserCreateDTO("王五", 17, "wangwu@example.com");
// 2. 执行方法,验证异常
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> userService.createUser(dto));
assertEquals("用户年龄必须大于等于18", exception.getMessage());
// 3. 验证Repository未被调用(避免无效保存)
verify(userRepository, never()).save(any(User.class));
}
}
四、生命周期 Hook:精准管控应用启停逻辑
核心价值:替代在main方法中写初始化 / 清理逻辑,通过 Spring 提供的 Hook 函数,在 Bean 初始化后、应用启动后、关闭前执行特定操作。
1. Bean 级生命周期:@PostConstruct 与 @PreDestroy
用于单个 Bean 的初始化和资源释放:
@Service
public class FileStorageService {
private FileSystemResource storageDir;
// Bean初始化后执行(依赖注入完成后)
@PostConstruct
public void initStorageDir() {
// 初始化文件存储目录,不存在则创建
String dirPath = "/var/app/storage";
storageDir = new FileSystemResource(dirPath);
File dir = storageDir.getFile();
if (!dir.exists()) {
boolean created = dir.mkdirs();
if (created) {
log.info("文件存储目录创建成功:{}", dirPath);
} else {
throw new RuntimeException("文件存储目录创建失败:" + dirPath);
}
}
}
// Bean销毁前执行(应用关闭时)
@PreDestroy
public void cleanTempFiles() {
// 清理临时文件
File tempDir = new File(storageDir.getFile(), "temp");
if (tempDir.exists()) {
File[] tempFiles = tempDir.listFiles();
if (tempFiles != null) {
for (File file : tempFiles) {
boolean deleted = file.delete();
if (deleted) {
log.info("临时文件删除成功:{}", file.getName());
}
}
}
}
}
}
2. 应用级生命周期:ApplicationRunner 与 CommandLineRunner
用于应用启动后执行全局初始化逻辑(如加载字典数据、预热缓存),两者区别:
-
ApplicationRunner:接收ApplicationArguments,支持解析命令行参数 -
CommandLineRunner:接收字符串数组,直接获取命令行参数
// 应用启动后初始化字典数据
@Component
@Order(1) // 多个Runner时指定执行顺序(值越小越先执行)
public class DictDataInitializer implements ApplicationRunner {
private final DictService dictService;
@Autowired
public DictDataInitializer(DictService dictService) {
this.dictService = dictService;
}
@Override
public void run(ApplicationArguments args) throws Exception {
// 从命令行参数判断是否需要强制刷新字典(--refresh-dict=true)
boolean refreshDict = args.containsOption("refresh-dict")
&& Boolean.parseBoolean(args.getOptionValues("refresh-dict").get(0));
if (refreshDict || dictService.getDictCache().isEmpty()) {
log.info("开始初始化字典数据...");
dictService.loadDictToCache(); // 加载字典到缓存
log.info("字典数据初始化完成,共加载{}条数据", dictService.getDictCache().size());
}
}
}
// 应用启动后打印环境信息
@Component
@Order(2)
public class EnvInfoPrinter implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
String activeEnv = System.getProperty("spring.profiles.active", "dev");
log.info("当前应用环境:{},命令行参数:{}", activeEnv, Arrays.toString(args));
}
}
3. 进阶:SmartLifecycle 实现细粒度管控
如需更灵活的生命周期控制(如指定启动顺序、判断是否需要启动),可实现SmartLifecycle:
@Component
public class MqConsumerLifecycle implements SmartLifecycle {
private boolean isRunning = false;
private final RabbitMqConsumer consumer;
@Autowired
public MqConsumerLifecycle(RabbitMqConsumer consumer) {
this.consumer = consumer;
}
// 启动逻辑(应用就绪后执行)
@Override
public void start() {
consumer.startListening(); // 启动MQ消费者
isRunning = true;
log.info("MQ消费者启动成功");
}
// 停止逻辑(应用关闭前执行)
@Override
public void stop() {
consumer.stopListening(); // 停止MQ消费者
isRunning = false;
log.info("MQ消费者停止成功");
}
// 判断是否正在运行
@Override
public boolean isRunning() {
return isRunning;
}
// 启动顺序(值越小越先启动)
@Override
public int getPhase() {
return 10; // 在字典初始化(phase=1)之后启动
}
}
五、Lambda 风格 DSL:简化复杂配置
核心价值:替代传统链式调用,通过 Lambda 表达式让配置代码更紧凑、可读性更高,典型场景如 Spring Security、Spring Cloud Gateway。
1. Spring Security 配置(Lambda 风格)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
@Autowired
public SecurityConfig(JwtAuthenticationFilter jwtAuthFilter, AuthenticationProvider authenticationProvider) {
this.jwtAuthFilter = jwtAuthFilter;
this.authenticationProvider = authenticationProvider;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// 禁用CSRF(前后端分离项目常用)
.csrf(csrf -> csrf.disable())
// 配置请求授权规则
.authorizeHttpRequests(auth -> auth
// 忽略认证的URL(从配置中获取)
.requestMatchers("/api/public/**", "/api/auth/login").permitAll()
// 管理员接口需ADMIN角色
.requestMatchers("/api/admin/**").hasRole("ADMIN")
// 其他请求需认证
.anyRequest().authenticated()
)
// 配置登录逻辑(前后端分离用无状态登录,禁用默认表单登录)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 配置认证器
.authenticationProvider(authenticationProvider)
// 添加JWT过滤器(在UsernamePasswordAuthenticationFilter之前执行)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
// 配置异常处理(自定义认证失败响应)
.exceptionHandling(ex -> ex
.authenticationEntryPoint((request, response, ex) -> {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
Map<String, Object> error = new HashMap<>();
error.put("code", "UNAUTHORIZED");
error.put("message", "请先登录");
new ObjectMapper().writeValue(response.getOutputStream(), error);
})
);
return http.build();
}
}
2. Spring Cloud Gateway 路由配置(Lambda 风格)
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 路由1:用户服务路由
.route("user-service", r -> r
.path("/api/users/**") // 匹配路径
.filters(f -> f
.rewritePath("/api/users/(?<segment>.*)", "/users/${segment}") // 路径重写
.addResponseHeader("X-Service-Name", "user-service") // 添加响应头
.requestRateLimiter(c -> c // 限流配置
.setRateLimiter(redisRateLimiter())
.setKeyResolver(userKeyResolver())
)
)
.uri("lb://user-service") // 转发到用户服务(负载均衡)
)
// 路由2:订单服务路由
.route("order-service", r -> r
.path("/api/orders/**")
.filters(f -> f
.circuitBreaker(c -> c // 熔断配置
.setName("orderServiceCircuitBreaker")
.setFallbackUri("forward:/fallback/orders")
)
)
.uri("lb://order-service")
)
.build();
}
// 基于用户ID的限流Key解析器
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("X-User-ID") != null
? exchange.getRequest().getHeaders().getFirst("X-User-ID")
: "anonymous"
);
}
// Redis限流器
@Bean
public RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(10, 20); // 令牌桶容量20,每秒生成10个令牌
}
}
六、条件化 Bean:按需装配适配多场景
核心价值:根据环境、依赖、配置等条件动态创建 Bean,避免 “一刀切” 的配置(如生产用 Redis 集群,开发用本地缓存)。
1. 基础条件装配:按配置属性判断
// Redis缓存配置(生产环境用)
@Configuration
@ConditionalOnProperty(
value = "cache.engine",
havingValue = "redis",
matchIfMissing = false // 必须显式配置为redis才生效
)
@ConditionalOnClass(RedisTemplate.class) // 确保引入了Redis依赖
public class RedisCacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
// 配置Redis缓存序列化方式(避免默认JDK序列化导致的乱码)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofHours(1)); // 默认缓存1小时
// 初始化Redis缓存管理器
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.withCacheConfiguration("userCache", config.entryTtl(Duration.ofMinutes(30))) // 自定义缓存过期时间
.build();
}
}
// 本地缓存配置(开发环境用,默认生效)
@Configuration
@ConditionalOnProperty(
value = "cache.engine",
havingValue = "local",
matchIfMissing = true // 未配置时默认生效
)
public class LocalCacheConfig {
@Bean
public CacheManager cacheManager() {
// 使用ConcurrentMap实现本地缓存
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
// 预定义缓存名称(避免动态创建缓存时的性能损耗)
cacheManager.setCacheNames(Arrays.asList("userCache", "orderCache"));
return cacheManager;
}
}
2. 进阶条件:组合多条件与自定义条件
- 组合条件:例如 “引入 RabbitMQ 依赖且配置了队列名” 才装配 Bean
@Configuration
@ConditionalOnClass(RabbitTemplate.class) // 有RabbitMQ依赖
@ConditionalOnProperty(value = "rabbitmq.queue.name") // 配置了队列名
public class RabbitMqConfig {
@Bean
public Queue orderQueue(@Value("${rabbitmq.queue.name}") String queueName) {
return QueueBuilder.durable(queueName).build(); // 持久化队列
}
}
- 自定义条件:例如 “操作系统为 Linux 时才生效”
// 自定义条件类
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String osName = context.getEnvironment().getProperty("os.name");
return osName != null && osName.toLowerCase().contains("linux");
}
}
// 使用自定义条件
@Configuration
@Conditional(LinuxCondition.class)
public class LinuxSpecificConfig {
@Bean
public SystemMonitor linuxMonitor() {
return new LinuxSystemMonitor(); // Linux专属系统监控
}
}
七、应用健康检查:实时监控系统状态
核心价值:通过 Spring Boot Actuator 暴露应用内部状态(健康、指标、Bean 等),对接监控系统(Prometheus、Grafana),及时发现问题。
1. 基础配置:启用 Actuator
<!-- pom.xml引入依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.7.10</version>
</dependency>
# application.yml配置
management:
# 配置Actuator端口(建议与应用端口分离,避免暴露)
server:
port: 9090
# 暴露端点(生产环境仅暴露必要端点)
endpoints:
web:
exposure:
include: health,metrics,info,beans # 暴露的端点列表
base-path: /actuator # 端点基础路径(默认/actuator)
# 健康检查详情(生产环境可设为summary,避免暴露敏感信息)
endpoint:
health:
show-details: when_authorized # 仅授权用户可见详情
# 配置健康检查指示器(如Redis、数据库)
probes:
enabled: true
metrics:
enabled: true
# 自定义Metrics标签(如应用名称、环境)
metrics:
tags:
application: ${spring.application.name}
environment: ${spring.profiles.active}
2. 访问核心端点
- 健康状态:
GET http://localhost:9090/actuator/health
{
"status": "UP",
"components": {
"db": { "status": "UP", "details": { "database": "MySQL", "validationQuery": "isValid()" } },
"redis": { "status": "UP", "details": { "version": "6.2.6" } },
"diskSpace": { "status": "UP", "details": { "total": 107374182400, "free": 53687091200, "threshold": 1073741824 } }
}
}
- JVM 指标:
GET http://localhost:9090/actuator/metrics/jvm.memory.used
{
"name": "jvm.memory.used",
"description": "The amount of used memory",
"baseUnit": "bytes",
"measurements": [ { "statistic": "VALUE", "value": 204800000 } ],
"availableTags": [
{ "tag": "area", "values": [ "heap", "nonheap" ] },
{ "tag": "id", "values": [ "Eden Space", "Survivor Space", "Metaspace" ] }
]
}
3. 进阶:自定义健康指标
当应用依赖自定义服务(如第三方 API、消息队列)时,可实现HealthIndicator添加自定义健康检查:
// 自定义“支付网关”健康检查
@Component
public class PaymentGatewayHealthIndicator implements HealthIndicator {
private final PaymentGatewayClient gatewayClient;
@Autowired
public PaymentGatewayHealthIndicator(PaymentGatewayClient gatewayClient) {
this.gatewayClient = gatewayClient;
}
@Override
public Health health() {
try {
// 调用支付网关的健康检查接口
GatewayHealthResponse response = gatewayClient.checkHealth();
if (response.isAvailable()) {
// 健康:返回详情(如响应时间)
return Health.up()
.withDetail("version", response.getVersion())
.withDetail("responseTime", response.getResponseTimeMs() + "ms")
.build();
} else {
// 不健康:返回原因
return Health.down()
.withDetail("reason", response.getUnavailableReason())
.build();
}
} catch (Exception e) {
// 异常:返回错误信息
return Health.down(e)
.withDetail("error", "调用支付网关健康检查失败:" + e.getMessage())
.build();
}
}
}
此时访问/actuator/health会包含paymentGateway组件的健康状态。
八、@ConfigurationProperties:优雅管理配置
核心价值:替代零散的@Value注解,将相关配置分组到 Java 类中,提升代码可读性、可维护性,并支持配置校验、自动补全。
1. 基础用法:分组配置属性
// 安全相关配置分组
@ConfigurationProperties(prefix = "app.security")
@Data // Lombok注解,自动生成Getter/Setter/toString
public class SecurityProperties {
// JWT过期时间(默认2小时)
private Duration jwtExpiration = Duration.ofHours(2);
// JWT密钥(必填,无默认值)
private String jwtSecret;
// 忽略认证的URL列表(默认空)
private List<String> ignoreUrls = new ArrayList<>();
// 密码策略配置(嵌套属性)
private PasswordPolicy passwordPolicy = new PasswordPolicy();
// 嵌套配置类:密码策略
@Data
public static class PasswordPolicy {
// 密码最小长度(默认8)
private int minLength = 8;
// 是否需要包含数字(默认true)
private boolean needDigit = true;
// 是否需要包含特殊字符(默认true)
private boolean needSpecialChar = true;
}
}
2. 启用配置属性与使用
// 启用配置属性(可在启动类或配置类上添加)
@SpringBootApplication
@EnableConfigurationProperties(SecurityProperties.class)
public class AppApplication {
public static void main(String[] args) {
SpringApplication.run(AppApplication.class, args);
}
}
// 在Service中使用配置
@Service
public class JwtTokenService {
private final SecurityProperties securityProperties;
// 构造注入(推荐,避免@Autowired)
public JwtTokenService(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
// 生成JWT令牌
public String generateToken(String username) {
// 使用配置中的过期时间和密钥
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + securityProperties.getJwtExpiration().toMillis()))
.signWith(SignatureAlgorithm.HS256, securityProperties.getJwtSecret())
.compact();
}
}
3. 进阶:配置校验与宽松绑定
- 配置校验:添加
@Validated和 JSR-380 注解(如@NotBlank、@Min),确保配置合法:
@ConfigurationProperties(prefix = "app.security")
@Data
@Validated // 启用校验
public class SecurityProperties {
@NotBlank(message = "JWT密钥不能为空") // 校验必填
private String jwtSecret;
@Valid // 嵌套属性校验
private PasswordPolicy passwordPolicy = new PasswordPolicy();
@Data
public static class PasswordPolicy {
@Min(value = 6, message = "密码最小长度不能小于6") // 校验最小值
private int minLength = 8;
}
}
若配置缺失或不合法,应用启动时会抛出BindValidationException,快速发现问题。
- 宽松绑定:配置文件中的属性名可灵活匹配(如下均等价于
jwtExpiration):
app:
security:
jwt-expiration: 3h # 中划线分隔(推荐)
jwt_expiration: 3h # 下划线分隔
JWT_EXPIRATION: 3h # 全大写
更多推荐

所有评论(0)