如何在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
相关产品推荐
相关产品推荐

