@Value在单例中不会自动刷新,因其仅在Bean初始化时通过PropertyPlaceholderHelper做一次字符串替换,不监听配置变更事件,也不持有Config引用,故config:reload后字段值仍为初始快照。
Hyperf 默认的
注解不支持运行时自动刷新属性值,哪怕 Config 已通过
命令或热更新机制变更,单例 Bean 中的
-注入字段也不会同步更新——这是由其设计决定的:注入发生在 Bean 初始化阶段,之后不再重执行。
为什么
在单例中不会自动刷新
本质是依赖于
在容器创建 Bean 时做一次字符串替换,它不持有对 Config 组件的引用,也不监听配置变更事件。即使你把 Bean 声明为
,字段值也只初始化一次。
常见错误现象:
注入后,改了
并执行
,但日志里打印的仍是旧值
根本原因:Hyperf 的
是编译期/初始化期行为,非响应式
影响范围:所有使用
的单例服务、Command、Listener 等,只要生命周期长于配置变更周期,就会 stale
用
手动读取 + 缓存控制实现“准实时”刷新
绕过
,直接在方法内调用
获取值,并根据需要加一层轻量缓存(如 TTL 控制),是最可控、无侵入的做法。
适用场景:超时、开关、降级阈值等允许秒级延迟的动态参数
关键点:避免每次调用都穿透到 Config 组件底层(虽然它本身很快,但高频下仍有开销)
示例写法:
注意:不要用
变量缓存,会污染协程上下文;必须用协程安全的 Cache 组件
若不需要缓存,直接调用
即可,性能足够好
配合
主动触发刷新逻辑
当业务强依赖“立即生效”(比如开关类配置),可监听
,在事件中更新单例内部状态,而非等待下次调用。
Hyperf 3.1.66
本页面提供企业级 PHP 协程框架 Hyperf 3.1.66 版本的官方源码下载与完整更新日志。重点解析 v3.1.66 版本中新增的 gRPC 多客户端负载均衡支持、Pool 连接池全量刷新、Guzzle 持久化 Cookie 以及数据库 JSON 包含键查询等核心优化特性。
下载
使用场景:灰度开关、熔断开关、敏感策略开关等不允许延迟的配置项
需手动注册监听器,且确保监听器在 ConfigProvider 之后加载(靠
控制)
示例:
需自行实现,通常是更新一个
属性
该方式耦合略高,但语义清晰、可控性强,适合关键路径
注意:监听器内不要做耗时操作,否则阻塞事件循环
别踩
+
/ 协程并发的坑
如果在异步任务或协程中反复 new 或 get 同一个单例 Bean,又用了
,容易误以为“刷新了”,其实只是新建了实例——这并非刷新,而是绕过了单例。
典型错误:在
方法里
,以为能拿到新配置,实际只是新实例 + 旧注入值
更隐蔽的问题:协程间共享单例对象,但
字段仍为初始化时的快照,没有跨协程同步机制
正确思路:单例就该有稳定生命周期;要动态,就别依赖初始化注入,改用按需读取或事件驱动更新
真正难处理的是既要单例又要毫秒级响应的场景——这时就得放弃纯 Config 方案,转向 etcd/Nacos 等外部配置中心 + Watch 机制,Hyperf 官方插件已有支持,但复杂度和运维成本会明显上升。
@Valueconfig:reload@Value@Value@ValuePropertyPlaceholderHelper@Singleton@Value("${app.timeout:3000}")config/autoload/app.phpphp bin/hyperf.php config:reload@Value@ValueConfigInterface@ValueConfigInterfaceuse Hyperf\Config\ConfigInterface;
use Psr\Container\ContainerInterface;
class PaymentService
{
public function __construct(
private ContainerInterface $container
) {}
public function getTimeout(): int
{
// 每 10 秒重新读一次,避免每次都查 Config
$key = 'payment.timeout';
$cacheKey = 'config:'.md5($key);
$timeout = $this->container->get(CacheInterface::class)->get($cacheKey);
if ($timeout === null) {
$timeout = $this->container->get(ConfigInterface::class)->get($key, 5000);
$this->container->get(CacheInterface::class)->set($cacheKey, $timeout, 10);
}
return (int) $timeout;
}
}static$container->get(ConfigInterface::class)->get(...)ConfigListenerConfigChangedEventpriorityuse Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Config\Event\ConfigChangedEvent;
#[Listener]
class ConfigChangeListener implements ListenerInterface
{
public function listen(): array
{
return [ConfigChangedEvent::class];
}
public function process(object $event): void
{
if ($event->key === 'feature.enable_payment') {
$paymentService = $this->container->get(PaymentService::class);
$paymentService->refreshFeatureFlag($event->value);
}
}
}PaymentService::refreshFeatureFlag()private bool $paymentEnabled@Value@Async@Value@Async$container->get(MyService::class)@Value