Is it possible to change the Feign Client at runtime? - java

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();
}
}

Related

Empty Feign configuration is not extended

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 {}

Configuring RoundRobinLoadBalancer for unit tests

Since Ribbon client side load balancer is in maintenance mode, I migrated to RoundRobinLoadBalancer by setting spring.cloud.loadbalancer.ribbon.enabled to false.
With Ribbon for client tests I used to do
#Configuration
#Profile("test")
public class ClientTestConfig {
#Bean
public StubServer stubServer() {
return new StubServer().run();
}
#Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", stubServer().getPort()));
}
}
Whats the equivalent the code above for RoundRobinLoadBalancer?
I recently did the same switch away from Ribbon. One of the Spring guides has an example of how to configure a custom ServiceInstanceListSupplier:
https://spring.io/guides/gs/spring-cloud-loadbalancer/#_load_balance_across_server_instances
For your case this would probably look something like this:
#Configuration
#Profile("test")
public class ClientTestConfig {
#Bean
public StubServer stubServer() {
return new StubServer().run();
}
#Bean
ServiceInstanceListSupplier serviceInstanceListSupplier() {
return new MyServiceInstanceListSupplier(stubServer().getPort());
}
}
class MyServiceInstanceListSupplier implements ServiceInstanceListSupplier {
private final Integer port;
MyServiceInstanceListSupplier(Integer port) {
this.port = port;
}
#Override
public String getServiceId() {
return "";
}
#Override
public Flux<List<ServiceInstance>> get() {
return Flux.just(Arrays.asList(new DefaultServiceInstance("", "", "localhost", port, false)));
}
}
Notice that I am using empty strings as the instanceId and serviceId. That is probably not the best practice, but works fine in my case.

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;
}

Spring Integration manually publish message to channel

I'm in the process of learning how to use the Java Spring Framework and started experimenting with Spring Integration. I'm trying to use Spring Integration to connect my application to an MQTT broker both to publish and subscribe to messages but I'm having trouble finding a way to manually publish messages to an outbound channel. If possible I want to build it using notations in the java code exclusively rather than xml files defining beans and other related configuration.
In every example I've seen the solution to manually publishing a message seems to be to use a MessagingGateway Interface and then use the SpringApplicationBuilder to get the ConfigurableApplicationContext to get a reference to the gateway interface in the main method. The reference is then used to publish a message. Would it be possible to use AutoWired for the interface instead? In my attempts I just get a NullPointer.
My aim is to build a game where I subscribe to a topic to get game messages and then whenever the user is ready to make the next move, publish a new message to the topic.
Update:
This is one of the examples I've been looking at of how to setup an outbound channel: https://docs.spring.io/spring-integration/reference/html/mqtt.html
Update 2 after answer from Gary Russel:
This is some example code I wrote after looking at examples which gets me a NullPointer when using #AutoWired for the Gateway when running gateway.sendToMqtt in Controller.java. What I want to achieve here is to send an mqtt message manually when a GET request is handled by the controller.
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
Controller.java
#RestController
#RequestMapping("/publishMessage")
public class Controller {
#Autowired
static Gateway gateway;
#RequestMapping(method = RequestMethod.GET)
public int request(){
gateway.sendToMqtt("Test Message!");
return 0;
}
}
MqttPublisher.java
#EnableIntegration
#Configuration
public class MqttPublisher {
#Bean
public MqttPahoClientFactory mqttClientFactory(){
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setServerURIs("tcp://localhost:1883");
return factory;
}
#Bean
#ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound(){
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler("clientPublisher", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("topic");
return messageHandler;
}
#Bean
public MessageChannel mqttOutboundChannel(){
return new DirectChannel();
}
#MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface Gateway {
void sendToMqtt(String data);
}
}
Update:
Not sure if this is the proper logging but it is what I get from adding:
logging.level.org.springframework.web=Debug
logging.level.org.hibernate=Error
to application.properties.
https://hastebin.com/cuvonufeco.hs
Use a Messaging Gateway or simply send a message to the channel.
EDIT
#SpringBootApplication
public class So47846492Application {
public static void main(String[] args) {
SpringApplication.run(So47846492Application.class, args).close();
}
#Bean
public ApplicationRunner runner(MyGate gate) {
return args -> {
gate.send("someTopic", "foo");
Thread.sleep(5_000);
};
}
#Bean
#ServiceActivator(inputChannel = "toMqtt")
public MqttPahoMessageHandler mqtt() {
MqttPahoMessageHandler handler = new MqttPahoMessageHandler("tcp://localhost:1883", "foo",
clientFactory());
handler.setDefaultTopic("myTopic");
handler.setQosExpressionString("1");
return handler;
}
#Bean
public MqttPahoClientFactory clientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setUserName("guest");
factory.setPassword("guest");
return factory;
}
#Bean
public MqttPahoMessageDrivenChannelAdapter mqttIn() {
MqttPahoMessageDrivenChannelAdapter adapter =
new MqttPahoMessageDrivenChannelAdapter("tcp://localhost:1883", "bar", "someTopic");
adapter.setOutputChannelName("fromMqtt");
return adapter;
}
#ServiceActivator(inputChannel = "fromMqtt")
public void in(String in) {
System.out.println(in);
}
#MessagingGateway(defaultRequestChannel = "toMqtt")
public interface MyGate {
void send(#Header(MqttHeaders.TOPIC) String topic, String out);
}
}

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().

Categories

Resources