自定义注解里写 ${test.name},反射读出来仍是这串字符,是正常行为。注解保存的是字面量,只有消费它的代码主动解析,才会取得 Spring Environment 中的配置值。

注解与使用对象

下面以 Spring Framework 5 为背景,两个类型分别放在相应 Java 文件中:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Person {
    String name();
}
@Person(name = "${test.name:demo}")
public class ExamplePerson {}

注解的 RUNTIME 保留策略让反射可以读取它;不会自动产生与 @Value 相同的处理过程。未实现消费逻辑时,name 仍是原始占位符。

在消费逻辑中解析

import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class PersonNameResolver implements EnvironmentAware {
    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    public String resolve(Class<?> type) {
        Person person = type.getAnnotation(Person.class);
        if (person == null) {
            throw new IllegalArgumentException("类型未声明 Person");
        }
        return environment.resolveRequiredPlaceholders(person.name());
    }
}

这个组件需由 Spring 创建,再由调用方注入并调用 resolve(ExamplePerson.class)。它解析并返回字符串,不改写注解对象,也不是一套扫描所有类的处理器。EnvironmentAware 契约

配置 test.name 时返回配置值,缺失时示例的 :demo 提供默认值。对于无默认值的缺失属性,resolveRequiredPlaceholders 会抛错;旧笔记使用的 resolvePlaceholders 则可把无法解析的占位符原样保留,应按需求选择。PropertyResolver API

解析、表达式和刷新不是一回事

这两个方法处理属性占位符,不会自动计算任意 SpEL 表达式,也不完成目标字段类型转换。配置值如果用于路径、标识或权限,应在消费边界继续验证格式与范围。

启动时解析并存进对象,不会在配置中心变化后自动更新。每次调用重新解析,也只有在 Environment 的属性源确实更新时才会看到新值。动态刷新、对象重建和并发可见性需要另外设计,不能只凭一个 resolvePlaceholders 方法就宣称已经热更新。