在一次程序员的沙龙上,难得悠闲的技术探讨时光,喝着快乐水,吃着小零食,大家每人说了一个 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 # 全大写
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐