You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring中注入通用@ConfigurationProperties以减少样板代码

简化多Rest客户端样板代码的方案

针对重复编写Rest客户端配置和初始化代码的问题,以下几种方案可有效减少样板代码:

1. 自定义注解+BeanPostProcessor实现自动配置

通过自定义注解标记客户端类,配合BeanPostProcessor自动完成配置绑定和RestTemplate初始化:

1.1 创建自定义注解

用来指定客户端对应的配置前缀:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Service
public @interface RestClient {
    String prefix(); // 对应application.properties中的配置前缀,如"kasittelyservice"
}

1.2 实现BeanPostProcessor处理逻辑

自动绑定配置并初始化RestTemplate,注入到客户端类:

@Component
public class RestClientProcessor implements BeanPostProcessor {

    private final RestTemplateBuilder templateBuilder;
    private final Environment environment;

    public RestClientProcessor(RestTemplateBuilder templateBuilder, Environment environment) {
        this.templateBuilder = templateBuilder;
        this.environment = environment;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        RestClient clientAnnotation = bean.getClass().getAnnotation(RestClient.class);
        if (clientAnnotation != null) {
            // 绑定配置属性
            ClientProperties properties = Binder.get(environment)
                    .bind(clientAnnotation.prefix(), ClientProperties.class)
                    .orElseThrow(() -> new IllegalArgumentException("未找到配置前缀:" + clientAnnotation.prefix()));
            
            // 构建RestTemplate并添加日志拦截器
            RestTemplate restTemplate = templateBuilder.rootUri(properties.getUrl()).build();
            if (properties.getLogging().isEnabled()) {
                restTemplate.getInterceptors().add(new LoggingInterceptor()); // 自定义日志拦截器
            }
            
            // 注入RestTemplate到客户端类的字段
            try {
                Field templateField = bean.getClass().getDeclaredField("restTemplate");
                templateField.setAccessible(true);
                templateField.set(bean, restTemplate);
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException("注入RestTemplate失败", e);
            }
        }
        return bean;
    }
}

1.3 简化客户端类

只需标注自定义注解,无需内部配置类和重复构造逻辑:

@RestClient(prefix = "kasittelyservice")
public class KasittelyServiceClient {

    private RestTemplate restTemplate;

    public VastausDto kasittelePyynto(PyyntoDto pyyntoDto) {
        return restTemplate.postForObject("/kasittelePyynto", pyyntoDto, VastausDto.class);
    }
}

2. 泛型基类+批量配置扫描

通过泛型基类封装RestTemplate初始化逻辑,配合@ConfigurationPropertiesScan批量注册配置:

2.1 批量注册配置类

为每个客户端创建继承自ClientProperties的配置类,主启动类添加@ConfigurationPropertiesScan:

// 主启动类
@SpringBootApplication
@ConfigurationPropertiesScan
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// Kasittely服务配置类
@ConfigurationProperties(prefix = "kasittelyservice")
public class KasittelyServiceProps extends ClientProperties {
}

// 其他服务配置类
@ConfigurationProperties(prefix = "otherservice")
public class OtherServiceProps extends ClientProperties {
}

2.2 泛型基类封装初始化逻辑

public abstract class BaseRestClient<T extends ClientProperties> {

    protected final RestTemplate restTemplate;

    protected BaseRestClient(RestTemplateBuilder builder, T properties) {
        RestTemplateBuilder templateBuilder = builder.rootUri(properties.getUrl());
        if (properties.getLogging().isEnabled()) {
            templateBuilder.additionalInterceptors(new LoggingInterceptor());
        }
        this.restTemplate = templateBuilder.build();
    }
}

2.3 客户端类继承基类

@Service
public class KasittelyServiceClient extends BaseRestClient<KasittelyServiceProps> {

    public KasittelyServiceClient(RestTemplateBuilder builder, KasittelyServiceProps properties) {
        super(builder, properties);
    }

    public VastausDto kasittelePyynto(PyyntoDto pyyntoDto) {
        return restTemplate.postForObject("/kasittelePyynto", pyyntoDto, VastausDto.class);
    }
}

3. 最优方案:使用Spring Cloud OpenFeign

如果项目允许引入Spring Cloud依赖,OpenFeign可以彻底消除RestTemplate的样板代码,直接通过接口定义远程调用:

3.1 引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

3.2 启用Feign客户端

主启动类添加@EnableFeignClients注解:

@SpringBootApplication
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3.3 定义Feign客户端接口

@FeignClient(
    name = "kasittelyservice",
    url = "${kasittelyservice.url}",
    configuration = FeignClientConfig.class
)
public interface KasittelyServiceClient {

    @PostMapping("/kasittelePyynto")
    VastausDto kasittelePyynto(@RequestBody PyyntoDto pyyntoDto);
}

// Feign日志配置类
class FeignClientConfig {
    @Bean
    public Logger.Level feignLoggerLevel(@Qualifier("kasittelyservice") ClientProperties properties) {
        return properties.getLogging().isEnabled() ? Logger.Level.FULL : Logger.Level.NONE;
    }
}

这种方式无需手动管理RestTemplate,Feign会自动处理请求发送、日志配置等逻辑,代码最简洁。

内容的提问来源于stack exchange,提问作者Tuomas Toivonen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 23:05:13