如何使@Value注解支持类似@ConfigurationProperties的功能(@Value支持对象类型)?

本文介绍了如何在不注册Bean或不修改已有配置类的情况下,使@Value注解具备@ConfigurationProperties的功能。通过使用Binder从配置文件构建配置对象,并提供了一个自定义的String到Object的转换器,使得@Value能解析复杂类型的配置属性。解决方案包括添加一个ApplicationContextInitializer来注册转换器,以及在启动时自动处理'bind:'开头的@Value注解。

该文章为原创(转载请注明出处):如何使@Value注解支持类似@ConfigurationProperties的功能? - 简书 (jianshu.com)

真实业务场景(不希望配置类注册为Bean 或 不希望声明@ConfigurationProperties)

假设某一个jar包内封装了DataSourceProperties

@Configuration
@ConfigurationProperties(
    prefix = "my.datasource"
)
@Data
public class DataSourceProperties {
    private List<String> suffix;
    private List<DataSourceDetailProperties> db;
}

在jar包的Configuration中,某个@Bean的构造过程中引用了这个DataSourceProperties

public JdbcTemplate buildJdbcTemplate(DataSourceProperties dataSourceProperties) {
}

在某个业务场景中,同时存在两个DataSourceProperties
会造成一个问题,注入的时候会提示有多个候选的bean
但是没法去修改Jar包中的内容

自己重复写一个DataSourceProperties 不是很优雅

这时候引出了一个需求,DataSourceProperties不希望注册为Bean,但是能够从配置文件读取构建对象

解决方案一

使用org.springframework.boot.context.properties.bind.Binder 从配置文件构建配置对象

@Bean
public JdbcTemplate buildJdbcTemplate(Environment environment) {
     Binder binder = Binder.get(environment);
     DataSourceProperties
                properties1 = binder.bind("my.datasource1", Bindable.of(DataSourceProperties.class)).get(),
                properties2 = binder.bind("my.datasource2", Bindable.of(DataSourceProperties.class)).get();
}

binder.bind(“xxx”, Bindable.of(type)).get()
似乎是重复的编码方式?

解决方案二

使@Value注解能够支持自动调用这段代码 binder.bind("xxx", Bindable.of(type)).get()
例如

@Bean
public JdbcTemplate buildJdbcTemplate(@Value("my.datasource1") DataSourceProperties properties1,
                                      @Value("my.datasource2") DataSourceProperties properties2) {   
}

org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency
最后会交由converter处理

Class<?> type = descriptor.getDependencyType();
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
    if (value instanceof String) {
        String strVal = resolveEmbeddedValue((String) value);
        BeanDefinition bd = (beanName != null && containsBean(beanName) ?
                getMergedBeanDefinition(beanName) : null);
        value = evaluateBeanDefinitionString(strVal, bd);
    }
    TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
    try {
        return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
    }
    catch (UnsupportedOperationException ex) {
        // A custom TypeConverter which does not support TypeDescriptor resolution...
        return (descriptor.getField() != null ?
                converter.convertIfNecessary(value, type, descriptor.getField()) :
                converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));
    }
}

项目启动时,添加String to Object的转换器,支持@Value 并且 "bind:"开头(防止影响@Value原有功能)

package com.nuonuo.accounting.guiding.support.spring;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;

import java.util.Set;

import static java.util.Collections.singleton;

/**
 * @author uhfun
 */
public class ValuePropertiesBindableAnnotationSupport implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static final String PREFIX = "bind:";

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        Binder binder = Binder.get(context.getEnvironment());
        ((FormattingConversionService) context.getBeanFactory().getConversionService()).addConverter(new ConditionalGenericConverter() {
            @Override
            public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
                Value value = targetType.getAnnotation(Value.class);
                return value != null && value.value().startsWith(PREFIX);
            }

            @Override
            public Set<ConvertiblePair> getConvertibleTypes() {
                return singleton(new ConvertiblePair(String.class, Object.class));
            }

            @Override
            public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
                Value value = targetType.getAnnotation(Value.class);
                Class<?> type = targetType.getType();
                assert value != null;
                return binder.bind(value.value().replace(PREFIX, ""), Bindable.of(type)).get();
            }
        });
    }
}

转换后代码执行 binder.bind(value.value().replace(PREFIX, ""), Bindable.of(type)).get(); 目的就达成了

META-INF/spring.factories中添加注册的Bean

# ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer=\
com.nuonuo.accounting.guiding.support.spring.ValuePropertiesBindableAnnotationSupport,\

最终效果

@Bean
public JdbcTemplate buildJdbcTemplate(@Value("bind:my.datasource1") DataSourceProperties properties1,
                                      @Value("bind:my.datasource2") DataSourceProperties properties2) {   
}

该文章为原创(转载请注明出处):如何使@Value注解支持类似@ConfigurationProperties的功能? - 简书 (jianshu.com)

### 功能与使用方式 `@ConfigurationProperties` 和 `@Value` 是 Spring Boot 中用于从配置文件中读取属性的两种主要方式,但它们在功能和适用场景上存在显著差异。 `@ConfigurationProperties` 适用于将一组相关的配置属性映射到一个 Java Bean 中。它支持“松散绑定”(relaxed binding),即配置项的命名可以采用驼峰命名、短横线命名或下划线命名等多种形式,例如 `lastName` 可以写作 `last-name` 或 `last_name`。此外,该注解支持 JSR303 数据校验标准,可以通过 `@Email`、`@NotNull` 等注解对属性值进行校验,并且能够处理复杂的类型封装,如 Map、List 等集合类型[^4]。示例代码如下: ```java @ConfigurationProperties(prefix = "student") @Component @Data public class Student { private int id; private String name; private int age; } ``` 相比之下,`@Value` 注解更适合用于注入单个属性值,其语法更为简洁,可以直接嵌入到字段或方法参数中。然而,它不支持松散绑定,也不具备数据校验能力。同时,`@Value` 支持 SpEL(Spring Expression Language)表达式,这使得它可以执行更复杂的逻辑操作,例如条件判断和数学运算等[^2]。以下是一个典型的用法示例: ```java @Value("${student.name}") private String studentName; ``` ### 配置来源与优先级 两者都可以从全局配置文件 `application.properties` 或 `application.yml` 中读取配置信息。如果希望从其他非默认位置的配置文件获取数据,可以配合使用 `@PropertySource` 或 `@ImportResource` 注解引入额外的配置源[^3]。值得注意的是,当同一个属性在多个配置文件中出现时,`application.properties` 文件中的设置会覆盖 `application.yml` 中的对应项。 ### 应用场景 选择 `@ConfigurationProperties` 还是 `@Value` 主要取决于具体的应用需求。如果只是需要获取配置文件中的某一项特定值或者直接设定某个变量的具体内容,则推荐使用 `@Value`;而当存在大量属性需要与配置文件中的键值对建立映射关系时,采用 `@ConfigurationProperties` 更加合适,因为它提供了更好的可维护性和更强的功能支持[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值