关键在三点:注解必须带@Retention(RetentionPolicy.RUNTIME)、反射用getDeclaredFields()并setAccessible(true)、配置注入需类型转换与容错;全程不改业务代码,通过“打标签+扫描+赋值”实现动态装配。

关键在三点:注解必须能被反射读到、反射要精准定位目标、配置值注入得安全可控。整个过程不改业务代码,只靠“打标签+扫描+赋值”完成动态装配。
注解定义必须带 RUNTIME 保留策略
自定义注解若想在运行期被读取,@Retention(RetentionPolicy.RUNTIME) 是硬性要求。缺了它,反射调用 getAnnotation() 永远返回 null。同时用 @Target 明确作用位置:
- 标注在字段上(如配置项注入)→
@Target(ElementType.FIELD) - 标注在类上(如组件注册)→
@Target(ElementType.TYPE) - 属性尽量用 String、int、boolean、Class 或 String[],避免嵌套结构,降低解析复杂度和出错概率
反射扫描要避开常见陷阱
拿到目标对象后,不能依赖 getFields() 或 getMethods() ——它们只返回 public 成员,且无法访问私有字段上的注解。正确做法是:
- 用
getClass().getDeclaredFields()获取本类所有字段(含 private) - 对每个字段调用
field.setAccessible(true)才能读取其上的注解,也才能后续设值 - 先用
field.isAnnotationPresent(ConfigValue.class)判断是否存在,再调用field.getAnnotation(ConfigValue.class)提取实例,避免空指针 - 若字段为 static 或 final,反射设值会失败,需提前跳过或做特殊处理
配置值注入需兼顾类型与容错
从配置源(如 Properties、环境变量、配置中心)读到的值默认是字符串,赋值前必须做类型转换:
- String 字段直接赋值
- int/Integer 字段用
Integer.parseInt(),并捕获NumberFormatException - boolean 字段支持
"true"/"false"和"1"/"0"多种写法 - 未命中配置时,优先使用注解自带的
defaultValue(),而不是抛异常 - 高频调用场景下,把已解析的
Field对象缓存起来,避免重复反射开销
集成到 Spring 时推荐用 BeanPostProcessor
裸写反射逻辑容易遗漏生命周期和类型转换细节。更稳妥的方式是借力 Spring 生态:
- 实现
BeanPostProcessor,在postProcessAfterInitialization阶段处理目标 Bean - 利用 Spring 的
ConversionService自动完成字符串到目标类型的转换 - 配合
@ConditionalOnProperty或自定义条件注解,实现按环境启用/禁用注入逻辑 - 避免在
postProcessBeforeInitialization中操作,此时字段可能还未初始化,易出 NPE

















