Constructing RestTemplates with Spring #Configuration - java

Say I have some application-wide configuration:
#Configuration
public class RestTemplateConfiguration {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public HttpClient anHttpClient() {
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
// set some properties
// ...
return HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager).build();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ClientHttpRequestInterceptor aRequestInterceptor() {
....
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ClientHttpRequestInterceptor anotherRequestInterceptor() {
....
}
}
Then I have some particular service that looks like:
#Service
public class MyService {
private final RestTemplate myParticularRestTemplate;
#Autowired
public MyService(RestTemplate myParticularRestTemplate) {
this.myParticularRestTemplate = myParticularRestTemplate;
}
/***
* Some incredible application logic
***/
#Configuration
public static class Config {
private int SOME_READ_TIMEOUT;
private int SOME_CONNECT_TIMEOUT;
#Bean
public RestTemplate myParticularRestTemplate(HttpClient anHttpClient, List<ClientHttpRequestInterceptor> interceptors) {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(anHttpClient);
clientHttpRequestFactory.setReadTimeout(SOME_READ_TIMEOUT);
clientHttpRequestFactory.setConnectTimeout(SOME_CONNECT_TIMEOUT);
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
}
I want the interceptors to be injected into RestTemplates system-wide, but, as it is, because I've got to access nested objects of the RestTemplate to set particular configuration, I need to instantiate the RestTemplate myself -- at least as I understand it.
Is there a nicer way to address this that doesn't require configuration for specific RestTemplates to know about the List<ClientHttpRequestInterceptor>?

Related

How to do constructor injection of the feign client interface?

Can feign client in spring boot be injected via constructor injection?
#AllArgsConstructor
class ApiPortImpl implements ApiPort {
private final ApiClient feignClient;
#Override
public String getAuthToken() {
return feignClient.getToken();
}
}
interface ApiPort {
String getAuthToken();
}
#FeignClient(name = "api-client", url = "${some_url}", path = "/", configuration = RestConfiguration.class)
interface ApiClient {
#PostMapping(value = "/identity/token", consumes = "application/x-www-form-urlencoded")
String getToken();
}
#Configuration
class RestConfiguration {
#Bean
ApiPort apiPort(){
return new ApiPortImpl(<how to do constructor injection of the feign client ??>);
}
}
I do not want to use #Component annotation.
In the above mentioned code block, how can I instantiate the ApiPortImpl bean?
You can inject ApiClient as a parameter to method annotated with #Bean:
#Configuration
class RestConfiguration {
#Bean
ApiPort apiPort(#Autowired ApiClient apiClient){
return new ApiPortImpl(apiClient);
}
}
This should work even without #Autowired annotation.
Docs Reference: Declaring a Bean

Cannot inject two fields of same interface when using #Profile annotation on bean configuration method

I use Spring 5.1.4.RELEASE and have a trouble injecting two fields of same interface via constructor when using #Profile annotation on bean configuration methods. I have a simple Publisher component like follows:
#Component
public class Publisher {
private final MyClient prodClient;
private final MyClient testClient;
#java.beans.ConstructorProperties({"prodClient", "testClient"})
public Publisher(MyClient prodClient, MyClient testClient) {
this.prodClient = prodClient;
this.testClient = testClient;
}
}
When I mark whole configuration with #Profile annotation, then it works as expected:
#Profile(Profiles.MY_CLIENT)
#Configuration
public class ClientConfig {
#Bean
public MyClient prodClient() {
return new HttpClient("prod.client.channel");
}
#Bean
public MyClient testClient() {
return new HttpClient("test.client.channel");
}
}
The above configuration is OK, but the problem occurs when I want to have the #Profile annotation only on some methods inside a configuration class:
#Configuration
public class ClientConfig {
#Profile(Profiles.MY_CLIENT)
#Bean
public MyClient prodClient() {
return new HttpClient();
}
#Profile(Profiles.MY_CLIENT)
#Bean
public MyClient testClient() {
return new HttpClient();
}
// some other beans...
}
Then I get an error during startup:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.test.Publisher required a bean of type 'com.test.MyClient' that could not be found.
UPDATE:
It's solved. It was my mistake. I had two more bean methods annotated with different #Profile for integration tests, but they had the same name for production code (annotated with Profiles.MY_CLIENT profile):
#Configuration
public class ClientConfig {
#Profile(Profiles.MY_CLIENT)
#Bean
public MyClient prodClient() {
return new HttpClient();
}
#Profile(Profiles.MY_CLIENT)
#Bean
public MyClient testClient() {
return new HttpClient();
}
// ... other beans
#Profile(Profiles.MOCK_MY_CLIENT)
#Bean
public MyClient prodClient() {
return new MockClient();
}
#Profile(Profiles.MOCK_MY_CLIENT)
#Bean
public MyClient testClient() {
return new MockClient();
}
}
Mmm, If you try to inject a list of this components?
Something like
public Publisher(List<MyClient> clients) {
}
and in the client implementations you set a flag that could be useful to know when you should use it.
In this code here:
#java.beans.ConstructorProperties({"prodClient", "testClient"})
public Publisher(MyClient prodClient, MyClient testClient) {
this.prodClient = prodClient;
this.testClient = testClient;
}
Try using the #Autowired annotation on the parameters instead:
public Publisher(#Autowired MyClient prodClient, #Autowired MyClient testClient) {
this.prodClient = prodClient;
this.testClient = testClient;
}

Intercept the RestTemplate by getting already created RestTemplate

I have logic to intercept the RestTemplate and I am adding/registering that RestTemplate in a configuration file (SecurityConfiguration.java)
but I want to add that interceptor from another configuration file by getting RestTemplate object which is already registered:
public class TranslogRestTemplateCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
boolean isRestTemplate = false;
try {
if (context.getBeanFactory() != null) {
isRestTemplate = (context.getBeanFactory().getBean(RestTemplate.class) != null);
}
} catch (BeansException e) {
return false;
}
return isRestTemplate;
}
}
Configuration class:
#Configuration
public class RestTemplateConfig {
private final MyInterceptor myInterceptor;
#Value("${com.pqr.you.rest.enabled:true}")
private boolean transEnabled;
#Autowired
public RestTemplateConfig(MyInterceptor myInterceptor) {
this.myInterceptor = myInterceptor;
}
#Autowired
private ApplicationContext appContext;
// The logic added below is not working for me
#Bean
#Conditional(TranslogRestTemplateCondition.class)
public RestTemplate addInterceptor(ApplicationContext appContext) {
RestTemplate restTemplate = appContext.getBean(RestTemplate.class);
if (transEnabled) {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
interceptors.add(myInterceptor);
restTemplate.setInterceptors(interceptors);
}
return restTemplate;
}
}
Actual logic for RestTemplate, which is going to return with required interceptors, and some other values (at the time of returning this restTemplate, my interceptor also need to be add here, without over riding existing values)
OR
by taking the below restTempalte object and add MyInterceptor to restTemplate.
#Configuration
public class SecurityConfiguration {
#Bean
public AbcInterceptor abcRequestInterceptor(XyzService xyzService) {
return new AbcInterceptor("abc-app", null, xyzService);
}
// I dont want to create bean here
/*#Bean
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}*/
#Bean
public RestTemplate restTemplate(AbcRequestInterceptor abcRequestInterceptor) {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(abcRequestInterceptor);
//interceptors.add(myInterceptor); // I dont want to add this interceptor here
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
I think what you need in this case is a BeanPostProcessor not a Condition class. It will allow you to modify the bean if it exists. you can create one like below instead of the RestTemplateConfiguration class and the Condition:
#Configuration
public class RestTemplatePostProcessor implements BeanPostProcessor {
#Value("${com.pqr.you.rest.enabled:true}")
private boolean transEnabled;
#Bean
MyInterceptor myInterceptor() {
return new MyInterceptor();
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RestTemplate) { // or you can check by beanName if you like
final RestTemplate restTemplate = (RestTemplate) bean;
if (transEnabled) {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
interceptors.add(myInterceptor());
restTemplate.setInterceptors(interceptors);
}
return restTemplate;
}
return bean;
}
}
Note that in your code you are trying to create another RestTemplate instance (named "addInterceptor"), which seems to be undesired.

Spring: how to get rootUri from RestTempalte

I'm using this configuration class to initialize RestTemplate:
#Configuration
public class RestTemplateConfig {
#Value("${endpoint-url}")
private String endpointUrl;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.rootUri(endpointUrl)
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
}
And in one of my service's method I use the code:
RootUriTemplateHandler handler = (RootUriTemplateHandler) restTemplate.getUriTemplateHandler();
String uri = handler.getRootUri();
restTemplate.postForLocation(uri, request);
To get this URI. Is there an easier method to get this rootUri (without casting)? Or to execute the post request directly to rootUri?
restTemplate.getUriTemplateHandler().expand("/")
You can execute the post request directly to rootUri, as long as the uri you provide to the restTemplate.postForLocation starts with "/". In that case, Spring will automatically add the baseURI provided in the restTemplate constructor.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder,
#Value("${endpoint-url}") String endpointUrl) {
return builder
.rootUri(endpointUrl)
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
}
In your service's method:
// Make a POST call to ${endpoint-url}/foobar
String uri = "/foobar"; // The leading "/" is important!
restTemplate.postForLocation(uri, request);
Sounds like you are trying to use RestTemplate to pass along the value of ${endpoint-url}. That slightly awkward looking cast works but you could perhaps consider one of these alternatives:
Create a provider which encapsulates the endpointUrl and your restTemplate and inject this provider wherever you need either the endpointUrl or the restTemplate. For example:
#Component
public class RestTemplateProvider {
#Value("${endpoint-url}")
private String endpointUrl;
private final RestTemplate restTemplate;
#Autowired
public RestTemplateProvider(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.rootUri(endpointUrl)
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
}
public RestTemplate provide() {
return restTemplate;
}
public String getEndpointUrl() {
return endpointUrl;
}
}
Inject #Value("${endpoint-url}") private String endpointUrl; into which ever service class needs it.

Spring-Boot RestClientTest not correctly auto-configuring MockRestServiceServer due to unbound RestTemplate

EDIT: This question is specifically pertaining to the #RestClientTest annotation introduced in spring-boot 1.4.0 which is intended to replace the factory method.
Problem:
According to the documentation the #RestClientTest should correctly configure a MockRestServiceServer to use when testing a REST client. However when running a test I am getting an IllegalStateException saying the MockServerRestTemplateCustomizer has not been bound to a RestTemplate.
Its worth noting that I'm using Gson for deserialization and not Jackson, hence the exclude.
Does anyone know how to correctly use this new annotation? I haven't found any examples that require more configuration then when I have already.
Configuration:
#SpringBootConfiguration
#ComponentScan
#EnableAutoConfiguration(exclude = {JacksonAutoConfiguration.class})
public class ClientConfiguration {
...
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
}
Client:
#Service
public class ComponentsClientImpl implements ComponentsClient {
private RestTemplate restTemplate;
#Autowired
public ComponentsClientImpl(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
public ResponseDTO getComponentDetails(RequestDTO requestDTO) {
HttpEntity<RequestDTO> entity = new HttpEntity<>(requestDTO);
ResponseEntity<ResponseDTO> response =
restTemplate.postForEntity("/api", entity, ResponseDTO.class);
return response.getBody();
}
}
Test
#RunWith(SpringRunner.class)
#RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {
#Autowired
private ComponentsClient client;
#Autowired
private MockRestServiceServer server;
#Test
public void getComponentDetailsWhenResultIsSuccessShouldReturnComponentDetails() throws Exception {
server.expect(requestTo("/api"))
.andRespond(withSuccess(getResponseJson(), APPLICATION_JSON));
ResponseDTO response = client.getComponentDetails(requestDto);
ResponseDTO expected = responseFromJson(getResponseJson());
assertThat(response, is(expectedResponse));
}
}
And the Exception:
java.lang.IllegalStateException: Unable to use auto-configured MockRestServiceServer since MockServerRestTemplateCustomizer has not been bound to a RestTemplate
Answer:
As per the answer below there is no need to declare a RestTemplateBuilder bean into the context as it is already provided by the spring-boot auto-configuration.
If the project is a spring-boot application (it has #SpringBootApplication annotation) this will work as intended. In the above case however the project was a client-library and thus had no main application.
In order to ensure the RestTemplateBuilder was injected correctly in the main application context (the bean having been removed) the component scan needs a CUSTOM filter (the one used by #SpringBootApplication)
#ComponentScan(excludeFilters = {
#ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)
})
The MockRestServiceServer instance should be constructed from the static factory, using a RestTemplate. See this article for a detailed description of the testing process.
In your example, you can do:
#RunWith(SpringRunner.class)
#RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {
#Autowired
private ComponentsClient client;
#Autowired
private RestTemplate template;
private MockRestServiceServer server;
#Before
public void setUp() {
server= MockRestServiceServer.createServer(restTemplate);
}
/*Do your test*/
}
You have RestTemplateBuilder at two places. At ClientConfiguration class and at ComponentsClientImpl class. Spring boot 1.4.0 auto-configure a RestTemplateBuilder which can be used to create RestTemplate instances when needed. Remove below code from ClientConfiguration class and run your test.
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}

Categories

Resources