@Value 注入必须用反射,因需向 private 字段设值:先 getDeclaredField() 获取字段,再 setAccessible(true) 绕过访问控制,最后 field.set(instance, value) 赋值;其反射受 Spring 生命周期管控,非暴力反射。

@Value 注解本身不执行反射,但它的值注入过程依赖 Spring 容器在 Bean 实例化后,用反射对字段或方法进行设值——这属于“受控反射”,不是开发者手动写的暴力反射,但底层机制确实绕不开反射操作。
为什么必须用反射?
Spring 管理的是运行时创建的对象实例,而 @Value 标注的字段通常是 private 的,且没有显式 setter。要将解析好的配置值(比如 ${db.url} 解析成 jdbc:mysql://localhost:3306/test)写入该字段,容器必须:
- 通过 Class.getDeclaredField() 获取私有字段引用
- 调用 field.setAccessible(true) 绕过 Java 访问控制
- 调用 field.set(instance, value) 完成赋值
@Value 不是“暴力”,而是“按流程反射”
所谓“暴力反射”通常指脱离框架约束、手动强设字段、忽略生命周期、甚至篡改 final 字段等行为。而 @Value 的反射发生在标准生命周期中:
- 只在 populateBean() 阶段触发,此时 Bean 已实例化、构造完成
- 由 AutowiredAnnotationBeanPostProcessor 统一处理,和 @Autowired 共享同一套后置逻辑
- 字段必须是非 static 的——因为反射操作面向的是具体实例,static 字段无实例上下文,Spring 直接跳过
反射前的关键:值是怎么“算出来”的?
反射只是最后一步“写入”,真正决定填什么的,是两套解析引擎:
- ${...}:走 PropertySourcesPlaceholderConfigurer 流程,从 Environment 中逐层查找(application.yml → 系统属性 → 环境变量),支持默认值如 ${timeout:5000}
- #{...}:交给 SpelExpressionParser 执行 SpEL 表达式,可调用静态方法、访问 Bean、做数学运算,例如 #{T(java.lang.Math).random() * 100}
你不能绕过反射,但可以避开风险
如果担心反射带来的可维护性或安全性问题,可考虑替代方案:
- 用 @ConfigurationProperties + record 或构造器绑定,避免字段反射,靠 setter 或构造参数注入
- 把关键配置封装为独立 @Bean,用 @Autowired 注入,而非 @Value 字符串拼接
- 禁用 SpEL(通过设置 spring.expression.enabled=false)防止表达式注入类安全风险

















