深入解析 Spring 中的 @Value 注解(含源码级剖析 + 自定义实现)
在 Spring 开发中,我们经常使用 @Value
注解将配置文件中的值注入到 Bean 的属性中。本文将深入探讨 @Value
的使用方式、默认值支持、底层原理以及自定义实现方式。
一、@Value 的常见用法
@Value("${server.port}")
private int port;
@Value("${user.name:defaultUser}")
private String userName;
上面的用法展示了两种典型场景:
- 从配置文件中读取值:
${server.port}
- 设置默认值:
${user.name:defaultUser}
,当user.name
不存在时使用defaultUser
默认值语法说明:
${property:defaultValue}
若 ${property}
不存在或为 null,就使用 defaultValue
二、常见使用场景示例
-
注入配置值
@Value("${spring.datasource.url}") private String datasourceUrl;
-
注入 SpEL 表达式
@Value("#{2 * 10}") private int result; // 20
-
注入集合类型
@Value("#{'${my.list}'.split(',')}") private List<String> list;
三、@Value 的源码原理解析
注解定义
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
String value();
}
其本身是一个标准注解,用于描述需要注入的值。
核心解析类:AutowiredAnnotationBeanPostProcessor
Spring 中处理 @Value
注入的核心类是 AutowiredAnnotationBeanPostProcessor
,其在初始化阶段对属性字段进行处理。
内部依赖了 ValueAnnotationProcessor
和 Environment
,其关键逻辑:
PropertyResolver resolver = beanFactory.getBean(Environment.class);
String resolvedValue = resolver.resolvePlaceholders("${user.name:defaultUser}");
如果我们设置了默认值 ${user.name:defaultUser}
,那么当 user.name
不存在时,Spring 使用 defaultUser
。
Spring 会在容器启动时对这些带有 @Value
的字段进行反射注入,核心逻辑类似:
Field field = clazz.getDeclaredField("userName");
field.setAccessible(true);
field.set(beanInstance, resolvedValue);
四、自定义一个简化版 @MyValue 注解
下面我们模拟实现一个简单版的 @Value
注解,读取配置并注入到 Bean 中。
1. 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyValue {
String value();
}
2. 简单配置文件解析器
public class MyPropertyResolver {
private static final Properties props = new Properties();
static {
try {
props.load(MyPropertyResolver.class.getResourceAsStream("/application.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static String resolve(String keyExpression) {
if (keyExpression.startsWith("${") && keyExpression.endsWith("}")) {
String expr = keyExpression.substring(2, keyExpression.length() - 1);
String[] parts = expr.split(":", 2);
String key = parts[0];
String defaultValue = parts.length > 1 ? parts[1] : null;
return props.getProperty(key, defaultValue);
}
return keyExpression;
}
}
3. 注入处理器
public class MyValueProcessor {
public static void inject(Object bean) {
Class<?> clazz = bean.getClass();
for (Field field : clazz.getDeclaredFields()) {
MyValue annotation = field.getAnnotation(MyValue.class);
if (annotation != null) {
String resolved = MyPropertyResolver.resolve(annotation.value());
try {
field.setAccessible(true);
Object value = convert(field.getType(), resolved);
field.set(bean, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
private static Object convert(Class<?> type, String value) {
if (type == int.class) return Integer.parseInt(value);
if (type == long.class) return Long.parseLong(value);
if (type == boolean.class) return Boolean.parseBoolean(value);
return value; // default to String
}
}
4. 使用方式
public class UserConfig {
@MyValue("${user.age:18}")
private int age;
@MyValue("${user.name:anonymous}")
private String name;
public void print() {
System.out.println("Name: " + name + ", Age: " + age);
}
}
public class Main {
public static void main(String[] args) {
UserConfig config = new UserConfig();
MyValueProcessor.inject(config);
config.print();
}
}
五、总结
@Value
注解是 Spring 中用于注入配置的强大工具,支持 SpEL 和默认值语法。- 底层依赖的是 Spring 的环境抽象和 BeanPostProcessor 扩展点。
- 我们可以通过自定义注解 + 属性解析器 + 反射机制,简单实现类似功能。