
想象一下这个场景:你负责的电商平台正在进行一年一度的大促活动,突然收到运营团队的紧急通知,需要临时调整优惠券的使用门槛。你修改了配置中心的参数,然后... 发现应用程序并没有读取到新的配置。为了让新配置生效,你不得不重启服务 —— 但这会导致正在进行的支付流程中断,直接影响销售额。
这不是虚构的场景,而是许多开发者在生产环境中都遇到过的真实困境。根据 Spring Cloud 官方文档(https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#refresh-scope),在传统 Spring 应用中,配置信息通常在应用启动时加载,并且在整个生命周期内保持不变。这意味着任何配置变更都需要重启应用才能生效,这在高可用要求的生产环境中是难以接受的。
SpringBoot 的 @RefreshScope 注解正是为解决这个问题而生。它能够让 Bean 在不重启应用的情况下刷新配置,实现配置的动态更新。作为一名资深 Java 技术专家,我将在本文中深入解析 @RefreshScope 的底层原理、使用方法和最佳实践,让你彻底掌握这一 "配置热更新" 的利器。
在微服务和云原生架构日益普及的今天,配置动态更新的需求变得越来越迫切:
根据 DORA(DevOps Research and Assessment)的《2023 年 DevOps 状态报告》,高绩效组织的服务变更失败率比低绩效组织低 7 倍,而恢复服务的速度快 2,604 倍。实现配置动态更新是达到这种高绩效的关键能力之一。
@RefreshScope 是 Spring Cloud Commons 提供的一个注解,它的核心作用是:
简单来说,@RefreshScope 让 Bean 具备了 "热更新" 的能力,能够感知配置的变化并及时应用新配置。
在没有 @RefreshScope 的情况下,Spring 容器中的 Bean 通常是单例的,并且在初始化时就绑定了配置信息。即使后续配置发生变化,这些 Bean 也不会重新读取配置,因此无法感知到变化。
例如,下面这个服务类依赖于一个配置参数:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class PromotionService {
@Value("${promotion.discount:0.9}")
private double discount;
public double calculateDiscountPrice(double originalPrice) {
log.info("使用折扣 {} 计算价格", discount);
return originalPrice * discount;
}
}
当promotion.discount配置发生变化时,PromotionService中的discount字段不会自动更新,因为它是在 Bean 初始化时注入的固定值。要让新配置生效,必须重启应用。
这就是 @RefreshScope 要解决的核心问题。
要真正掌握 @RefreshScope,必须理解其背后的实现原理。这不仅能帮助我们正确使用它,还能在出现问题时快速定位原因。
@RefreshScope 的实现依赖于 Spring 的 Scope 机制。在 Spring 中,Scope 定义了 Bean 的生命周期和创建方式。常见的 Scope 包括:
@RefreshScope 本质上是一个自定义的 Scope,它扩展了 Spring 的 Scope 机制,实现了 Bean 的动态刷新能力。
@RefreshScope 的工作流程可以概括为以下几个步骤:
流程图如下:

@RefreshScope 通常与 @ConfigurationProperties 配合使用,以实现配置类的整体刷新。@ConfigurationProperties 用于将配置信息绑定到一个 POJO 类,而 @RefreshScope 则确保在配置变更时,这个 POJO 类会被重新创建并绑定最新的配置。
它们的配合流程如下:

配置变更的感知和刷新事件的传播是 @RefreshScope 工作的关键环节。在 Spring Cloud 中,这通常通过以下组件实现:
当配置发生变更时,事件传播流程如下:

这种机制确保了所有服务实例都能及时感知配置变更并应用新配置。
接下来,我们通过一个完整的示例来演示 @RefreshScope 的基本使用方法。这个示例将创建一个 SpringBoot 应用,实现配置的动态更新。
首先,我们需要在 pom.xml 中添加必要的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.jam</groupId>
<artifactId>refresh-scope-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>refresh-scope-demo</name>
<description>Demo project for Spring Boot @RefreshScope</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Config Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- Spring Cloud Starter Bus AMQP (用于配置刷新事件传播) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!-- Spring Boot Actuator (提供/actuator/refresh端点) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<optional>true</optional>
</dependency>
<!-- Swagger3 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
<!-- Commons Lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
创建 bootstrap.yml 配置文件(注意是 bootstrap 而不是 application,因为配置客户端需要在应用启动早期加载配置):
spring:
application:
name: refresh-scope-demo
cloud:
config:
uri: http://localhost:8888 # 配置服务器地址
fail-fast: true # 配置失败时快速失败
bus:
enabled: true
trace:
enabled: true
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
# 暴露刷新端点
management:
endpoints:
web:
exposure:
include: refresh,bus-refresh,health,info
endpoint:
health:
show-details: always
创建一个配置类,用于绑定促销相关的配置:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import lombok.Data;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* 促销活动配置
*
* @author 果酱
*/
@Data
@Component
@RefreshScope
@ConfigurationProperties(prefix = "promotion")
@Schema(description = "促销活动配置")
public class PromotionConfig {
/**
* 折扣比例,如0.9表示9折
*/
@Schema(description = "折扣比例,如0.9表示9折")
private double discount = 0.9;
/**
* 满减门槛,单位:元
*/
@Schema(description = "满减门槛,单位:元")
private int fullReductionThreshold = 100;
/**
* 满减金额,单位:元
*/
@Schema(description = "满减金额,单位:元")
private int fullReductionAmount = 10;
/**
* 活动是否开启
*/
@Schema(description = "活动是否开启")
private boolean enabled = true;
}
创建一个促销服务,使用上述配置:
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.math.BigDecimal;
import java.math.RoundingMode;
import lombok.extern.slf4j.Slf4j;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
/**
* 促销服务
*
* @author 果酱
*/
@Service
@Slf4j
@Tag(name = "促销服务", description = "提供促销相关的计算服务")
public class PromotionService {
private final PromotionConfig promotionConfig;
/**
* 构造函数注入
*/
@Autowired
public PromotionService(PromotionConfig promotionConfig) {
this.promotionConfig = promotionConfig;
log.info("PromotionService初始化完成");
}
/**
* 计算促销后的价格
*
* @param originalPrice 原价
* @return 促销后的价格
*/
@Operation(summary = "计算促销后价格", description = "根据当前促销规则计算商品的最终价格")
public BigDecimal calculatePromotionPrice(BigDecimal originalPrice) {
log.info("开始计算促销价格,原价:{}", originalPrice);
// 检查活动是否开启
if (!promotionConfig.isEnabled()) {
log.info("促销活动未开启,返回原价");
return originalPrice;
}
BigDecimal discountPrice = calculateDiscountPrice(originalPrice);
BigDecimal fullReductionPrice = calculateFullReductionPrice(originalPrice);
// 返回两种促销中价格更低的一种
BigDecimal finalPrice = discountPrice.compareTo(fullReductionPrice) < 0
? discountPrice
: fullReductionPrice;
log.info("促销计算完成,最终价格:{}", finalPrice);
return finalPrice;
}
/**
* 计算折扣价格
*/
private BigDecimal calculateDiscountPrice(BigDecimal originalPrice) {
double discount = promotionConfig.getDiscount();
log.info("应用折扣:{}", discount);
return originalPrice.multiply(BigDecimal.valueOf(discount))
.setScale(2, RoundingMode.HALF_UP);
}
/**
* 计算满减价格
*/
private BigDecimal calculateFullReductionPrice(BigDecimal originalPrice) {
int threshold = promotionConfig.getFullReductionThreshold();
int amount = promotionConfig.getFullReductionAmount();
log.info("应用满减:满{}减{}", threshold, amount);
if (originalPrice.compareTo(BigDecimal.valueOf(threshold)) >= 0) {
return originalPrice.subtract(BigDecimal.valueOf(amount))
.setScale(2, RoundingMode.HALF_UP);
}
return originalPrice;
}
}
创建一个 REST 控制器,提供 API 接口来测试配置的动态更新:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import java.math.BigDecimal;
import lombok.extern.slf4j.Slf4j;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* 促销控制器
*
* @author 果酱
*/
@RestController
@Slf4j
@Tag(name = "促销控制器", description = "提供促销相关的API接口")
public class PromotionController {
private final PromotionService promotionService;
private final PromotionConfig promotionConfig;
/**
* 构造函数注入
*/
@Autowired
public PromotionController(PromotionService promotionService, PromotionConfig promotionConfig) {
this.promotionService = promotionService;
this.promotionConfig = promotionConfig;
}
/**
* 计算促销价格
*/
@GetMapping("/promotion/price")
@Operation(summary = "计算促销价格", description = "根据原价计算促销后的价格")
public BigDecimal calculatePrice(
@Parameter(description = "商品原价", required = true)
@RequestParam BigDecimal originalPrice) {
log.info("收到计算促销价格请求,原价:{}", originalPrice);
return promotionService.calculatePromotionPrice(originalPrice);
}
/**
* 获取当前促销配置
*/
@GetMapping("/promotion/config")
@Operation(summary = "获取当前促销配置", description = "返回当前生效的促销配置信息")
public PromotionConfig getPromotionConfig() {
log.info("收到获取促销配置请求");
return promotionConfig;
}
}
创建应用启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
/**
* 应用启动类
*
* @author 果酱
*/
@SpringBootApplication
@RefreshScope
@OpenAPIDefinition(info = @Info(title = "RefreshScope Demo API", version = "1.0", description = "SpringBoot @RefreshScope示例项目API文档"))
public class RefreshScopeDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RefreshScopeDemoApplication.class, args);
}
}
promotion:
discount: 0.9
fullReductionThreshold: 100
fullReductionAmount: 10
enabled: true
通过这个简单的示例,我们可以看到 @RefreshScope 确实能够实现配置的动态更新,而无需重启应用。
掌握了 @RefreshScope 的基本用法后,我们需要了解它的一些高级特性和使用陷阱,以便在实际项目中更好地应用。
@RefreshScope 可以标注在类上,也可以标注在方法上:
例如,我们可以在配置类的 @Bean 方法上使用 @RefreshScope:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import java.time.format.DateTimeFormatter;
/**
* 日期配置类
*
* @author 果酱
*/
@Configuration
public class DateConfig {
/**
* 创建日期格式化器,支持动态刷新
*/
@Bean
@RefreshScope
public DateTimeFormatter dateTimeFormatter(
@Value("${date.format:yyyy-MM-dd HH:mm:ss}") String pattern) {
return DateTimeFormatter.ofPattern(pattern);
}
}
这种方式可以更精确地控制哪些 Bean 需要支持动态刷新。
当 @RefreshScope 标注的 Bean(我们称之为 "刷新 Bean")被单例 Bean 依赖时,需要特别注意:单例 Bean 在初始化时会获取刷新 Bean 的代理对象,而不是实际的目标对象。这确保了在刷新发生后,单例 Bean 仍然能通过代理对象访问到新的目标对象。
但是,如果单例 Bean 缓存了刷新 Bean 的状态或方法返回值,就会导致配置更新后无法获取新值。例如:
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class OrderService {
private final PromotionService promotionService;
private final BigDecimal discountCache; // 缓存折扣值
@Autowired
public OrderService(PromotionService promotionService) {
this.promotionService = promotionService;
// 错误:在构造函数中缓存了折扣值
this.discountCache = calculateDiscount(new BigDecimal("100"));
}
public BigDecimal calculateDiscount(BigDecimal price) {
return promotionService.calculatePromotionPrice(price);
}
// 错误:使用缓存的折扣值,而不是实时计算
public BigDecimal getCachedDiscount() {
return discountCache;
}
}
在这个例子中,OrderService是一个单例 Bean,它在构造函数中缓存了PromotionService(刷新 Bean)的计算结果。当PromotionService的配置更新后,OrderService的getCachedDiscount()方法仍然会返回旧的缓存值,而不是新的计算结果。
解决这个问题的方法是:避免在单例 Bean 中缓存刷新 Bean 的状态或结果,每次需要时都直接调用刷新 Bean 的方法。
@RefreshScope 只能管理实例字段,不能管理静态字段。如果在刷新 Bean 中使用静态字段存储配置信息,这些字段的值不会在配置变更时更新。
错误示例:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@RefreshScope
@Slf4j
public class StaticFieldDemo {
// 错误:静态字段无法被@RefreshScope管理
@Value("${demo.value:default}")
private static String staticValue;
public String getStaticValue() {
return staticValue;
}
}
正确的做法是使用实例字段:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Component
@RefreshScope
@Slf4j
public class InstanceFieldDemo {
@Getter
@Value("${demo.value:default}")
private String instanceValue; // 实例字段,可以被刷新
}
当刷新 Bean 在构造函数中依赖配置信息时,需要注意:构造函数只会在 Bean 首次创建时执行一次,当配置变更导致 Bean 刷新时,构造函数不会重新执行。因此,不应在构造函数中执行依赖配置的初始化逻辑。
错误示例:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Service
@RefreshScope
@Slf4j
public class ConstructorDependencyDemo {
private final String configValue;
private final String derivedValue;
// 错误:derivedValue在构造函数中基于configValue计算,刷新时不会重新计算
public ConstructorDependencyDemo(@Value("${demo.config:default}") String configValue) {
this.configValue = configValue;
this.derivedValue = deriveValue(configValue); // 依赖配置的初始化逻辑
log.info("ConstructorDependencyDemo初始化,configValue: {}", configValue);
}
private String deriveValue(String config) {
return "Derived: " + config;
}
public String getDerivedValue() {
return derivedValue;
}
}
在这个例子中,当demo.config配置变更时,configValue会被更新(因为它直接绑定到 @Value),但derivedValue不会更新,因为它是在构造函数中计算的,而构造函数不会重新执行。
解决这个问题的方法是:将依赖配置的初始化逻辑移到方法中,每次使用时计算,而不是在构造函数中计算一次。
正确示例:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Service
@RefreshScope
@Slf4j
public class MethodDependencyDemo {
@Getter
@Value("${demo.config:default}")
private String configValue;
/**
* 每次调用时基于当前configValue计算
*/
public String getDerivedValue() {
return deriveValue(configValue);
}
private String deriveValue(String config) {
return "Derived: " + config;
}
}
当 @RefreshScope 标注的 Bean 中包含 @Scheduled 定时任务时,可能会出现定时任务执行异常的情况。这是因为当 Bean 被刷新时,旧的实例会被销毁,而定时任务可能仍然持有对旧实例的引用。
错误示例:
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Service
@RefreshScope
@Slf4j
public class ScheduledRefreshDemo {
@Value("${schedule.message:default}")
private String message;
// 潜在问题:刷新后可能导致定时任务执行异常
@Scheduled(fixedRate = 5000)
public void scheduledTask() {
log.info("Scheduled task executed: {}", message);
}
}
解决这个问题的方法是:将定时任务和需要刷新的逻辑分离到不同的 Bean 中,定时任务所在的 Bean 不使用 @RefreshScope。
正确示例:
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.extern.slf4j.Slf4j;
/**
* 定时任务服务(不使用@RefreshScope)
*/
@Service
@Slf4j
public class ScheduledService {
private final RefreshableService refreshableService;
@Autowired
public ScheduledService(RefreshableService refreshableService) {
this.refreshableService = refreshableService;
}
@Scheduled(fixedRate = 5000)
public void scheduledTask() {
log.info("Scheduled task executed: {}", refreshableService.getMessage());
}
}
/**
* 可刷新的服务(使用@RefreshScope)
*/
@Service
@RefreshScope
@Slf4j
class RefreshableService {
@Value("${schedule.message:default}")
private String message;
public String getMessage() {
return message;
}
}
这种方式既保证了配置可以动态刷新,又避免了定时任务出现异常。
虽然 @RefreshScope 带来了极大的便利性,但它也不是没有代价的。在使用过程中,我们需要了解它的性能开销并进行适当的优化。
@RefreshScope 的性能开销主要来自以下几个方面:
根据 Spring 官方的性能测试(https://spring.io/blog/2018/04/17/spring-cloud-greenwich-m1-released),在正常情况下,@RefreshScope 带来的性能开销是可以接受的,但在高频刷新或资源受限的环境中,可能需要进行优化。
只对真正需要动态更新的 Bean 使用 @RefreshScope,避免过度使用。例如,对于那些配置很少变更的 Bean,就没有必要使用 @RefreshScope。
优化被 @RefreshScope 标注的 Bean 的初始化过程,避免在初始化时执行耗时操作。可以将耗时操作延迟到第一次使用时执行,而不是在构造函数中执行。
优化前:
@Service
@RefreshScope
@Slf4j
public class ExpensiveInitService {
private final Map<String, Object> largeCache;
public ExpensiveInitService() {
// 初始化成本高的操作
largeCache = loadLargeCache();
}
private Map<String, Object> loadLargeCache() {
log.info("加载大型缓存...");
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new HashMap<>();
}
}
优化后:
@Service
@RefreshScope
@Slf4j
public class LazyInitService {
private Map<String, Object> largeCache;
public LazyInitService() {
// 构造函数中不执行耗时操作
log.info("LazyInitService初始化");
}
/**
* 延迟加载缓存,第一次使用时才加载
*/
private Map<String, Object> getLargeCache() {
if (largeCache == null) {
synchronized (this) {
if (largeCache == null) {
largeCache = loadLargeCache();
}
}
}
return largeCache;
}
private Map<String, Object> loadLargeCache() {
log.info("加载大型缓存...");
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new HashMap<>();
}
}
如果有多个配置需要变更,尽量将它们批量处理,而不是频繁地单个刷新。可以使用 Spring Cloud Bus 的 /bus-refresh 端点一次性刷新所有服务实例的配置。
# 批量刷新所有服务实例的配置
curl -X POST http://localhost:8080/actuator/bus-refresh尽量在业务低峰期执行配置刷新操作,避免在高负载时段进行,以减少对正常业务的影响。
通过监控工具(如 Prometheus + Grafana)监控刷新操作对系统性能的影响,包括响应时间、吞吐量、内存使用等指标,及时发现并解决问题。
结合前面的内容,我们总结出 @RefreshScope 在生产环境中的最佳实践,帮助你在实际项目中更好地应用这一特性。
在生产环境中,配置变更应该遵循一个规范的流程,而不仅仅是修改配置然后调用刷新接口。一个完整的配置变更流程包括:
流程图如下:

为了确保配置变更的安全性,需要采取以下措施:
示例:使用 Spring Security 控制刷新端点的访问权限
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
/**
* 安全配置
*
* @author 果酱
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
// 允许匿名访问健康检查和API文档
.requestMatchers("/actuator/health", "/v3/api-docs/**", "/swagger-ui/**").permitAll()
// 刷新端点需要ADMIN角色
.requestMatchers("/actuator/refresh", "/actuator/bus-refresh").hasRole("ADMIN")
// 其他请求需要认证
.anyRequest().authenticated()
)
.httpBasic();
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails admin = User.withUsername("admin")
.password("{noop}admin123") // 实际生产环境应使用加密密码
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(admin);
}
}
为 @RefreshScope 相关的操作添加监控和告警,及时发现并解决问题:
示例:使用 Micrometer 监控刷新操作
import org.springframework.cloud.context.refresh.event.RefreshEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;
/**
* 刷新事件监听器,用于监控刷新操作
*
* @author 果酱
*/
@Component
@Slf4j
public class RefreshMetricsListener {
private final Counter refreshCounter;
public RefreshMetricsListener(MeterRegistry meterRegistry) {
// 创建刷新计数器
this.refreshCounter = meterRegistry.counter("refresh.scope.events");
}
@EventListener
public void onRefreshEvent(RefreshEvent event) {
log.info("收到刷新事件: {}", event);
// 每次刷新事件发生时,计数器加1
refreshCounter.increment();
}
}
在实际项目中,@RefreshScope 通常与配置中心(如 Spring Cloud Config、Apollo、Nacos 等)配合使用,实现更强大的配置管理能力。
以 Nacos 为例,集成步骤如下:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2022.0.0.0-RC2</version>
</dependency>
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
namespace: your-namespace
group: your-group
Nacos 会自动推送配置变更通知,无需手动调用 /actuator/refresh 端点,进一步简化了配置动态更新的流程。
在使用 @RefreshScope 的过程中,可能会遇到各种问题。下面列举一些常见问题及其解决方案。
问题现象:调用了 /actuator/refresh 端点,配置也确实更新了,但 @RefreshScope 标注的 Bean 没有被重新创建,仍然使用旧的配置。
可能原因及解决方案:
问题现象:配置刷新后,访问某个 Bean 时出现 NoSuchBeanDefinitionException 异常。
可能原因及解决方案:
问题现象:配置刷新后,Bean 的状态(如实例变量的值)丢失。
原因分析:@RefreshScope 的工作原理是销毁旧的 Bean 实例并创建新的实例,因此旧实例中的状态自然会丢失。
解决方案:
示例:使用 Redis 存储状态
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.Duration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.context.config.annotation.RefreshScope;
@Service
@RefreshScope
@Slf4j
public class StatefulService {
private final RedisTemplate<String, Object> redisTemplate;
// 状态存储在Redis中,而不是实例变量中
private static final String COUNTER_KEY = "service:counter";
@Autowired
public StatefulService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 增加计数器
*/
public Long incrementCounter() {
log.info("增加计数器");
return redisTemplate.opsForValue().increment(COUNTER_KEY, 1);
}
/**
* 获取当前计数器值
*/
public Long getCounter() {
Object value = redisTemplate.opsForValue().get(COUNTER_KEY);
return value != null ? (Long) value : 0L;
}
}
问题现象:频繁执行配置刷新导致系统性能下降,响应时间变长。
解决方案:
随着云原生技术的发展,配置动态更新的需求会越来越强烈,相关的技术也在不断演进: