Empty Feign configuration is not extended - java

I have several Feign clients, with different configurations. The common config looks like the following
public class FeignLogConfig {
#Bean
public LogOkHttpInterceptor LogOkHttpInterceptor() { //custom interceptor
return new LogOkHttpInterceptor();
}
#Bean
public feign.okhttp.OkHttpClient okHttpClient(LogOkHttpInterceptor interceptor) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.addInterceptor(interceptor);
return new feign.okhttp.OkHttpClient(builder.build());
}
}
that can be further extended
public class FeignRetryerConfig extends FeignLogConfig {
#Bean
public Retryer retryer() {
return new Retryer.Default(100, 500, 5);
}
}
or simply
public class FeignEmptyConfig extends FeignLogConfig {}
A client annotated with
#FeignClient(value = "retryClient", url = url, configuration = FeignRetryerConfig.class)
or
#FeignClient(value = "logClient", url = url, configuration = FeignLogConfig.class)
will actually use the defined interceptor, yet
#FeignClient(value = "emptyClient", url = url, configuration = FeignEmptyConfig.class)
would not use the LogOkHttpInterceptor. I can't find an explanation on the documentation, so I don't know if I'm actually missing something.
A minimal example can be found here.
EDIT: to me, at the moment, seems not related to Feign, but to how Spring aggregates the configurations. While the above FeignEmptyConfig doesn't work, the following does work!
#Import(CommonFeignConfig.class)
public class EmptyFeignConfig {}

Related

Is it possible to change the Feign Client at runtime?

Let's say I have a FeignClient setup like below with a configuration MyFeignConfiguration.class.
Is it possible to change the feign client(myClient) at runtime as I would like to have different client for different profile(e.g. development and testing)?
#FeignClient(name = "myTestClient", url = "${my.path}", configuration = MyFeignConfiguration.class)
public interface fooClient {
//etc....
}
public class MyFeignConfiguration {
#Bean
public Client myClient() {
return new HttpsClient();
}
}
I use #Profile to configure different clients.
public class MyFeignConfiguration {
#Profile("dev")
#Bean
public Client myDevClient() {
return new DevClient();
}
#Profile("test")
#Bean
public Client myTestClient() {
return new TestClient();
}
}

Spring Boot: substitute #Configuration class with another

I have a custom configuration class that I am loading using spring factories during bootstrap. The problem is that it is being overwritten by another similar configuration class coming from a spring ** starter package. I've tried excluding the second one, but it still loads. Also tried to set priorities, but that didn't work too.
Here's a snippet of my custom configuration class:
#Slf4j
#Configuration
#RequiredArgsConstructor
public class CustomAwsParamStorePropertySourceLocatorConfig implements PropertySourceLocator
...
And the one I'm trying to exclude that is coming from spring boot aws starter:
public class AwsParamStorePropertySourceLocator implements PropertySourceLocator {
The AwsParamStoreBootstrapConfiguration class has the ConditionalOnProperty annotation at the class level...
#Configuration(proxyBeanMethods = false)
#EnableConfigurationProperties(AwsParamStoreProperties.class)
#ConditionalOnClass({ AWSSimpleSystemsManagement.class, AwsParamStorePropertySourceLocator.class })
#ConditionalOnProperty(prefix = AwsParamStoreProperties.CONFIG_PREFIX, name = "enabled", matchIfMissing = true)
public class AwsParamStoreBootstrapConfiguration {
private final Environment environment;
public AwsParamStoreBootstrapConfiguration(Environment environment) {
this.environment = environment;
}
#Bean
AwsParamStorePropertySourceLocator awsParamStorePropertySourceLocator(AWSSimpleSystemsManagement ssmClient,
AwsParamStoreProperties properties) {
if (StringUtils.isNullOrEmpty(properties.getName())) {
properties.setName(this.environment.getProperty("spring.application.name"));
}
return new AwsParamStorePropertySourceLocator(ssmClient, properties);
}
So if you configured the property aws.paramstore.enabled=false it should stop that configuration from creating the AwsParamStorePropertySourceLocator bean.
It's important to note, that would also stop the creation of the AWSSimpleSystemsManagement bean which is also created in the AwsParamStoreBootstrapConfiguration class, so if you require that bean, you may need to also create it in your custom Configuration class.
#Bean
#ConditionalOnMissingBean
AWSSimpleSystemsManagement ssmClient(AwsParamStoreProperties properties) {
return createSimpleSystemManagementClient(properties);
}
public static AWSSimpleSystemsManagement createSimpleSystemManagementClient(AwsParamStoreProperties properties) {
AWSSimpleSystemsManagementClientBuilder builder = AWSSimpleSystemsManagementClientBuilder.standard()
.withClientConfiguration(SpringCloudClientConfiguration.getClientConfiguration());
if (!StringUtils.isNullOrEmpty(properties.getRegion())) {
builder.withRegion(properties.getRegion());
}
if (properties.getEndpoint() != null) {
AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
properties.getEndpoint().toString(), null);
builder.withEndpointConfiguration(endpointConfiguration);
}
return builder.build();
}

Could not autowire. No beans of 'RestTemplateBuilder' type found

I have a Java-Spring LIBRARY (NOT an application) which sends notifications via phone numbers. I'm using a Rest template to send a POST request. But instead of creating a new object of RestTemplate, I would want to use RestTemplateConfiguration #Configuration to do so.
IntelliJ version - 2020.2
Spring - 2.3.3.RELEASE
Java - 11
1st issue - When I'm trying to create a RestTemplateConfiguration class, I get the error -
Could not autowire. No beans of 'RestTemplateBuilder' type found.
#Configuration
public class RestTemplateConfiguration {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
2nd issue If I create a new object of RestTemplateBuilder, it doesn't give the error, but this is not how I want this class to behave. Also, this doesn't seem to work when in the class (mentioned below) SamplePhoneNumbers.java, I try to use RestTemplate restTemplate. It is coming as null.
#Configuration
public class RestTemplateConfiguration {
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder;
return builder.build();
}
}
Here is the class which sends notifications and where I am trying to use the Rest Template.
#Slf4j
public class SamplePhoneNumbers implements SampleClassStratergy {
RestTemplate restTemplate;
private static SamplePhoneNumbers samplePhoneNumbers = null;
private String phoneNumbersNotificationServiceURL = Enums.NotificationURL.enum_value + Enums.PhoneNumberApi.enum_value;
SamplePhoneNumbers() {
this.restTemplate = new RestTemplate();
}
public static SamplePhoneNumbers getInstance() {
if(samplePhoneNumbers ==null) {
samplePhoneNumbers = new SamplePhoneNumbers();
}
return samplePhoneNumbers;
}
#Async
public void sendNotification(String title, String message, List<String> listOfPhoneNumbers) {
SmsMessage smsMessage= new SmsMessage(title, message, listOfPhoneNumbers, Collections.emptyList(), Collections.emptyList());
try {
HttpEntity<SmsMessage> newRequest = new HttpEntity<>(smsMessage);
restTemplate.postForObject(phoneNumbersNotificationServiceURL, newRequest, String.class);
log.info("Notification sent via phone number.");
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
3rd issue: Is there any way I can get rid of getInstance() method so I don't have to handle Singleton logic? If I can create the class as a bean, that should work I guess, but I'm not sure how that can be achieved in this case.
Since this is a Library, it doesn't have a main method and no #SpringBootapplicationContext.
Can someone please assist me with the solution?
I also got the similar error while running Junit test case and resolved it by adding a bean of -
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder();
}
Do you want invoke your Java-Spring LIBRARY in a spring boot application? If so, you should add #ComponentScan in spring boot application main method as below.
pacakge com.prod
// for instance, Java-Spring LIRARY package is com.third
#SpringBootApplication
#ComponentScan(basePackages={"com.prod","com.third"})
public class Application {
Then you should do as below:
#Configuration
public class RestTemplateConfiguration {
#Bean(name="myRestTemplate")
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
#Slf4j
#Component
public class SamplePhoneNumbers implements SampleClassStratergy {
#Autowired
#Qualifier("myRestTemplate")
RestTemplate restTemplate;
#Async
public void sendNotification(String title, String message, List<String> listOfPhoneNumbers) {
SmsMessage smsMessage= new SmsMessage(title, message, listOfPhoneNumbers, Collections.emptyList(), Collections.emptyList());
try {
HttpEntity<SmsMessage> newRequest = new HttpEntity<>(smsMessage);
restTemplate.postForObject(phoneNumbersNotificationServiceURL, newRequest, String.class);
log.info("Notification sent via phone number.");
} catch (Exception e) {
log.error(e.getMessage());
}
}
}

Multiple dynamic HTTP endpoints

I want to run multiple HTTP endpoints which should be creates based on list of paths.
Currently I'm able to create one endpoint:
#MessagingGateway(defaultRequestChannel = "requestChannel")
public interface Gateway {
String sendReceive(String in);
}
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from("requestChannel").transform(new ObjectToStringTransformer())
.handle(new MyHandle())
.get();
}
#Bean
public HttpRequestHandlingMessagingGateway httpGate() {
HttpRequestHandlingMessagingGateway gateway = new HttpRequestHandlingMessagingGateway(true);
RequestMapping mapping = new RequestMapping();
mapping.setMethods(HttpMethod.POST);
mapping.setPathPatterns("/path");
gateway.setRequestMapping(mapping);
gateway.setRequestChannel(requestChannel());
gateway.setRequestPayloadType(byte[].class);
return gateway;
}
but I want to do somthing like this:
#Autowired
List<String> paths;
#PostConstruct
public void createEndpoints() {
for (String path : paths) {
//code for dynamic endpoint creation
}
}
private class MyHandle extends AbstractReplyProducingMessageHandler {
#Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return this.getMessageBuilderFactory().withPayload("Your message: " + requestMessage.getPayload());
}
}
Can you tell me how can I do it?
Since Java DSL 1.2 there is a IntegrationFlowContextexactly for such a use-case to register IntegrationFlow and dependent beans dynamically.
https://spring.io/blog/2016/09/27/java-dsl-for-spring-integration-1-2-release-candidate-1-is-available
The GA release today.
You should just follow with the samples in those blog post and pay attention to the org.springframework.integration.dsl.http.Http factory.
But, indeed, do that as early as possible. The #PostConstruct is good phase for this use-case.
When it will be later, the HandlerMapping won't be able to detect an new mapping. Just because it does the scan in its afterPropertiesSet().

Loading Beans based on hostname

I am writing services in Spring boot that get their configurations from Spring cloud. These services are multi-tenant and the tenant is based on the host name.
what I have now is
public class MyController {
#Autowired
public MyController(MyServiceFactory factory) {
...
}
#RequestMapping("some/path/{id}")
ResponseEntity<SomeEntity> getSomeEntity(#RequestHeader header, #PathVariable id) {
return factory.getMyService(header).handle(id);
}
}
where MyServiceFactory looks something like...
public class MyServiceFactory {
private final HashMap<String, MyService> serviceRegistry = new HashMap<>();
public MyService getMyService(String key) {
return serviceRegistry.get(key);
}
MyServiceFactory withService(String key, MyService service) {
this.serviceRegistry.put(key, service);
return this;
}
}
then in a configuration file
#Configuration
public ServiceFactoryConfiguration {
#Bean
public MyServiceFactory getMyServiceFactory() {
return new MyServiceFactory()
.withService("client1", new MyService1())
.withService("client2", new MyService2());
}
}
While what I have now works, I don't like that I need to create a factory for every dependency my controller may have. I'd like to have my code look something like this...
public class MyController {
#Autowired
public MyController(MyService service) {
...
}
#RequestMapping("some/path/{id}")
ResponseEntity<SomeEntity> getSomeEntity(#PathVariable id) {
return service.handle(id);
}
}
with a configuration file like
#Configuration
public class MyServiceConfiguration() {
#Bean
#Qualifier("Client1")
public MyService getMyService1() {
return new MyService1();
}
#Bean
#Qualifier("Client2")
public MyService getMyService2() {
return new MyService2();
}
}
I can get the code that I want to write if I use a profile at application start up. But I want to have lots of different DNS records pointing to the same (pool of) instance(s) and have an instance be able to handle requests for different clients. I want to be able to swap out profiles on a per request basis.
Is this possible to do?
Spring profiles would not help here, you would need one application context per client, and that seems not what you want.
Instead you could use scoped beans.
Create your client dependent beans with scope 'client' :
#Bean
#Scope(value="client",proxyMode = ScopedProxyMode.INTERFACES)
#Primary
MyService myService(){
//does not really matter, which instance you create here
//the scope will create the real instance
//may be you can even return null, did not try that.
return new MyServiceDummy();
}
There will be at least 3 beans of type MyService : the scoped one, and one for each client. The annotation #Primary tells spring to always use the scoped bean for injection.
Create a scope :
public class ClientScope implements Scope {
#Autowired
BeanFactory beanFactory;
Object get(String name, ObjectFactory<?> objectFactory){
//we do not use the objectFactory here, instead the beanFactory
//you somehow have to know which client is the current
//from the config, current request, session, or ThreadLocal..
String client=findCurrentClient(..);
//client now is something like 'Client1'
//check if your cache (HashMap) contains an instance with
//BeanName = name for the client, if true, return that
..
//if not, create a new instance of the bean with the given name
//for the current client. Easiest way using a naming convention
String clientBeanName=client+'.'+name;
Object clientBean=BeanFactory.getBean(clientBeanName);
//put in cache ...
return clientBean;
};
}
And your client specific beans are configured like this :
#Bean('Client1.myService')
public MyService getMyService1() {
return new MyService1();
}
#Bean('Client2.myService')
public MyService getMyService2() {
return new MyService2();
}
Did not test it but used it in my projects. Should work.
tutorial spring custom scope

Categories

Resources