How to Disable Ribbon and just use FeignClient in Spring Cloud - spring-cloud

I am aware that we can force FeignClient to use OkHttp instead of Ribbon by providing the url Ex. #FeignClient(url="serviceId", name="serviceId")
I want the OkHttpClient to be used even when just the name is provided. Ex. #FeignClient(name="serviceId")
As per the spring cloud documentation "if Ribbon is enabled it is a LoadBalancerFeignClient, otherwise the default feign client is used."
How can I disable ribbon so that the default feign client will be used.

None of the solutions on the internet worked for me.
Simply setting an absolute url in the url portion resulted in loadbalancing exceptions
// this resulted in java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: localhost
#Lazy
#Configuration
#Import(FeignClientsConfiguration.class)
public class MyConfig {
#LocalServerPort
private int port;
#Bean
public MyClient myClient(final Decoder decoder, final Encoder encoder, final Client client) {
return Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.target(MyClient.class, "http://localhost:" + localServerPort);
}
}
setting spring.cloud.loadbalancing.ribbon.enabled=false resulted in application context problems. Additional settings needs to be disabled for this to work. I did not probe further
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'eurekaLoadBalancerClientConfiguration': Invocation of init method failed; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:160)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:416)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1788)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595)
...
...
My working solution
Finally, after inspecting the source code in org.springframework.cloud.openfeign.ribbon.DefaultFeignLoadBalancedConfiguration, I came up with this solution
#Lazy // required for #LocalServerPort to work in a #Configuration/#TestConfiguration
#TestConfiguration
#Import(FeignClientsConfiguration.class)
public class MyConfig {
#LocalServerPort
private int port;
#Bean
public MyClient myClient(Decoder decoder, Encoder encoder, Client client, Contract contract) {
return Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.target(MyClient.class, "http://localhost:" + localServerPort);
}
// provide a default `FeignClient` so that Spring will not automatically create their LoadBalancingFeignClient
#Bean
public Client feignClient(SpringClientFactory clientFactory) {
return new Client.Default(null, null);
}
}

I had the same question but my setup is a bit different and I did not get it working in my case (using spring-cloud-starter-openfeign with spring mvc style annotations).
FYI: I needed a custom client with an SSLSocketFactory and ended up just creating the bean for the client and keeping the url on #FeignClient
#Bean
public Client myClient() {
return new Client.Default(getSSLSocketFactory(), new NoopHostnameVerifier());
}
However, we do have projects using spring-cloud-starter-feign where the URL is not provided on the annotation. Not sure if the config below is complete (I did not set it up) but it might point you in the right direction...
dependencies
compile("org.springframework.cloud:spring-cloud-starter-feign") {
exclude group: 'org.springframework.cloud', module: 'spring-cloud-starter-ribbon'
exclude group: 'org.springframework.cloud', module: 'spring-cloud-starter-archaius'
}
config
#Configuration
#Import(FeignClientsConfiguration.class) // org.springframework.cloud.netflix.feign.FeignClientsConfiguration
public class MyConfig {
#Value("${client.url}")
private String url;
#Bean
public MyClient myClient(final Decoder decoder, final Encoder encoder, final Client client) {
return Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.target(MyClient.class, url);
}
}

It has nothing to do with Ribbon.
Check this:
feign:
httpclient:
enabled: false
This will disable the spring cloud autoconfigured httpclient, and will search a #Bean named httpClient in the context. So provide the definition of #Bean in a #Configuration class and that's all.
Check class FeignAutoConfiguration in spring cloud feign.
https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html

Related

Configuring spring-cloud loadbalancer without autoconfiguration

I've read whole documentation, tutorial [1], and spend several hours in sources, but I still do not understand how to configure loadbalancer, especially if I don't use magic annotations.
I have following configuration:
#Configuration
public class AppConfig {
public static final String SERVICE_ID = "service";
#Primary
#Bean
public ServiceInstanceListSupplier serviceInstanceListSupplier() {
return ServiceInstanceListSuppliers.from(SERVICE_ID,
new DefaultServiceInstance(SERVICE_ID + "1", SERVICE_ID, "localhost", 8886, false),
new DefaultServiceInstance(SERVICE_ID + "2", SERVICE_ID, "localhost", 8887, false));
}
#Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
return new LoadBalancerClientFactory();
}
#Bean
public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(LoadBalancerProperties properties) {
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerClientFactory(), properties);
}
}
and use bean loadBalancerExchangeFilterFunction as:
WebClient.builder()
.baseUrl("http://service/test-consumer")
.filter(lbFunction)
.build();
and it works. The problem is, that it works regardless of what hostname I use. So if I replace hostname "service" with whatever work I like, I will be still sending data to localhost:8886 or localhost:8887.
Can someone explain what is the role of serviceId and how this is paired to collection of DefaultServiceInstance?
(I want to understand the internals, what are the key components, their purpose and their interplay. I'm not primarily looking for magic annotation, but that one actually explained would be also great. Debugging it is really hard, I have several A4 with class diagrams and it still makes no sense at all).
Question: Is there a misconfiguration? What is the purpose of serviceId? It seems that none, as webclient using ReactorLoadBalancerExchangeFilterFunction will create roundrobin loadbalancer over configured ServiceInstances regardless of what is actual hostname used in given webclient.
Question2: how could I create 2 loadbalanced services and control to which service (not node) will request go? Do I need 2 separate webclients or some url pattern(like using serviceId in place of hostname) will do? If I need 2 webclients, how is pairing to DefaultServiceInstance done?
[1] https://spring.io/guides/gs/spring-cloud-loadbalancer/
EDIT:
after suggested update, the configuration looks like:
#Configuration
public class AppConfig {
#Bean
public ServiceInstanceListSupplier instanceSupplier(ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.build(context);
}
#Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
return new LoadBalancerClientFactory();
}
#Bean
public ReactorLoadBalancerExchangeFilterFunction loadBalancerExchangeFilterFunction(LoadBalancerProperties properties) {
return new ReactorLoadBalancerExchangeFilterFunction(loadBalancerClientFactory(), properties);
}
}
application.properties contains:
spring.cloud.discovery.client.simple.instances.complicated[0].uri=http://localhost:8886
spring.cloud.discovery.client.simple.instances.complicated[1].uri=http://localhost:8887
webclient call to URL: http://localhost:8888/test-consumer (ie. hostname not matching serviceID) produces:
o.s.c.l.core.RoundRobinLoadBalancer : No servers available for service: localhost
eactorLoadBalancerExchangeFilterFunction : LoadBalancer does not contain an instance for the service localhost
webclient call to URL: http://complicated/test-consumer (ie. hostname matching serviceID) produces:
o.s.c.l.core.RoundRobinLoadBalancer : No servers available for service: complicated
eactorLoadBalancerExchangeFilterFunction : LoadBalancer does not contain an instance for the service complicated
The reason for this is that this.serviceId = environment.getProperty(PROPERTY_NAME); in DiscoveryClientServiceInstanceListSupplier(ReactiveDiscoveryClient,Environment) evaluates as null, thus even though I'm looking for some serviceId, delegate.getInstance is called with null, so no ServiceInstances are found. IF I removed #Bean instanceSupplier, and hope for autoconfiguration do it somehow magically, the this.serviceId = environment.getProperty(PROPERTY_NAME); is somehow magically set, serviceId is propagated correctly, and it works. For calls which leads elsewhere than configured serviceId, it fails saying, that this serviceId is not know, instead of making call.
SO it does mean, that if I configure loadbalancer, I cannot call anything else but (auto)configured services???
The LoadBalancer config should not be in a #Configuration-annotated class; instead, it should be a class passed for config via #LoadBalancerClient or #LoadBalancerClients annotation, as described here.
Also, the only bean you need to instantiate is the ServiceInstanceListSupplier (if you add spring-cloud-starter-loadbalancer, LoadBalancerClientFactory, and ReactorLoadBalancerExchangeFilterFunction will be instantiated by the starter).
So your LoadBalancer configuration class will look like so (without #Configuration):
public class AppConfig {
#Bean
public ServiceInstanceListSupplier instanceSupplier(ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.build(context);
}
}
and your actual #Configuration class (for example, where you configure other webflux-related beans), will have the following annotation: #LoadBalancerClients(defaultConfiguration = AppConfig.class).
Then, if you enable health-checks in the complicated instances, it should work without any problems.
Finally, able to resolve this issue with the below Configuration. not sure why it works only with Non blocking approach and when we pass the new RestTemplate
#Bean
public ServiceInstanceListSupplier instanceSupplier(ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withBlockingHealthChecks(new RestTemplate())//this change
.build(context);
}

SpringBoot 2.2.6 JUnit dataSource lookup error

I'm working on a big project written with java8 and SringBoot 2.2.6. The project uses Envers and, the girl builds the architecture say to me that she doesn't manage to put in the application.properties the Envers configuration. Than she do as follows:
#Configuration
public class JPAConfig {
#Autowired
private DataSource dataSource;
#Bean(name="entityManagerFactory")
public LocalSessionFactoryBean sessionFactory() throws IOException {
LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();
factoryBean.setHibernateProperties(getHibernateProperties());
factoryBean.setDataSource(dataSource);
factoryBean.setPackagesToScan("it.xxxx.xxxxx.xxxxx.common.model");
return factoryBean;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", PostgreSQL82Dialect.class.getName());
properties.put("hibernate.default_schema", "test");
properties.put("hibernate.listeners.envers.autoRegister", true);
properties.put("org.hibernate.envers.revision_field_name", "rev");
properties.put("org.hibernate.envers.revision_type_field_name", "rev_type");
properties.put("org.hibernate.envers.audit_table_prefix", "aud_");
properties.put("org.hibernate.envers.store_data_at_delete", true);
properties.put("org.hibernate.envers.audit_table_suffix", "");
return properties;
}
}
Problem is that without dataSource class name I can't start my #SpringBootTest classes and I don't know how to add it in a scenario like this (without change the configuration I mean).
I also tries to add this row inside the application.properties:
spring.profiles.active=#spring.profile#
spring.datasource.driver-class-name=org.postgresql.Driver
#JPA
spring.datasource.jndi-name=jdbc/test
But doesn't work at all..
If I run the App with JUnit I obtain this error:
org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException: Failed to look up JNDI DataSource with name 'jdbc/test'; nested exception is javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial
Can you help me??
Thanks a lot
You need to register your Datasource as JNDI resource in the spring-boot embedded tomcat.
You can add it as test scope configuration.
This answer shows how to register a JNDI resource: https://stackoverflow.com/a/26005740/5230585

Overridden RabbitSourceConfiguration (app starters) does not work with Spring Cloud Edgware

I'm testing an upgrade of my Spring Cloud DataFlow services from Spring Cloud Dalston.SR4/Spring Boot 1.5.9 to Spring Cloud Edgware/Spring Boot 1.5.9. Some of my services extend source (or sink) components from the app starters. I've found this does not work with Spring Cloud Edgware.
For example, I have overridden org.springframework.cloud.stream.app.rabbit.source.RabbitSourceConfiguration and bound my app to my overridden version. This has previously worked with Spring Cloud versions going back almost a year.
With Edgware, I get the following (whether the app is run standalone or within dataflow):
***************************
APPLICATION FAILED TO START
***************************
Description:
Field channels in org.springframework.cloud.stream.app.rabbit.source.RabbitSourceConfiguration required a bean of type 'org.springframework.cloud.stream.messaging.Source' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.cloud.stream.messaging.Source' in your configuration.
I get the same behaviour with the 1.3.0.RELEASE and 1.2.0.RELEASE of spring-cloud-starter-stream-rabbit.
I override RabbitSourceConfiguration so I can set a header mapper on the AmqpInboundChannelAdapter, and also to perform a connectivity test prior to starting up the container.
My subclass is bound to the Spring Boot application with #EnableBinding(HeaderMapperRabbitSourceConfiguration.class). A cutdown version of my subclass is:
public class HeaderMapperRabbitSourceConfiguration extends RabbitSourceConfiguration {
public HeaderMapperRabbitSourceConfiguration(final MyHealthCheck healthCheck,
final MyAppConfig config) {
// ...
}
#Bean
#Override
public AmqpInboundChannelAdapter adapter() {
final AmqpInboundChannelAdapter adapter = super.adapter();
adapter.setHeaderMapper(new NotificationHeaderMapper(config));
return adapter;
}
#Bean
#Override
public SimpleMessageListenerContainer container() {
if (config.performConnectivityCheckOnStartup()) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Attempting connectivity with ...");
}
final Health health = healthCheck.health();
if (health.getStatus() == Status.DOWN) {
LOGGER.error("Unable to connect .....");
throw new UnableToLoginException("Unable to connect ...");
} else if (LOGGER.isInfoEnabled()) {
LOGGER.info("Connectivity established with ...");
}
}
return super.container();
}
}
You really should never do stuff like healthCheck.health(); within a #Bean definition. The application context is not yet fully baked or started; it may, or may not, work depending on the order that beans are created.
If you want to prevent the app from starting, add a bean that implements SmartLifecycle, put the bean in a late phase (high value) so it's started after everything else. Then put your code in start(). autStartup must be true.
In this case, it's being run before the stream infrastructure has created the channel.
Some ordering might have changed from the earlier release but, in any case, performing activity like this in a #Bean definition is dangerous.
You just happened to be lucky before.
EDIT
I just noticed your #EnableBinding is wrong; it should be Source.class. I can't see how that would ever have worked - that's what creates the bean for the channels field of type Source.
This works fine for me after updating stream and the binder to 1.3.0.RELEASE...
#Configuration
public class MySource extends RabbitSourceConfiguration {
#Bean
#Override
public AmqpInboundChannelAdapter adapter() {
AmqpInboundChannelAdapter adapter = super.adapter();
adapter.setHeaderMapper(new MyMapper());
return adapter;
}
}
and
#SpringBootApplication
#EnableBinding(Source.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
If that doesn't work, please edit the question to show your POM.

How to exclude RequestInterceptor for an specific Spring Cloud Feign client?

I have a number of clients for which a "global" RequestInterceptor has been defined. For one of the clients I need this "global" interceptor to be excluded. Is it possible to override the full set of RequestInterceptors for a particular FeignClient?
#FeignClient(value = "foo", configuration = FooClientConfig.class)
public interface FooClient {
//operations
}
#Configuration
public class FooClientConfig{
//How do I exclude global interceptors from this client configuration?
}
The spring-cloud-netflix version in use is 1.1.0 M5
It seems there is no easy way to override the global interceptor.
I think you could do it like this:
#Configuration
public class FooClientConfig{
#Bean
RequestInterceptor globalRequestInterceptor() {
return template -> {
if (template.url().equals("/your_specific_url")) {
//don't add global header for the specific url
return;
}
//add header for the rest of requests
template.header(AUTHORIZATION, String.format("Bearer %s", token));
};
}
}
Based on the issue stated here. Instead of excluding interceptors, you need to define different feign clients for each API. Add your interceptors based on your needs.
public class ConfigOne {
#Bean
public InterceptorOne interceptorOne(AdditionalDependency ad) {
return new InterceptorOne(ad);
}
}
Just make sure you don't use #Configuration annotation on above class.
Instead, importing this bean on client definition would be a working solution.
#FeignClient(name = "clientOne", configuration = ConfigOne.class)
public interface ClientOne { ... }
An enhanced way of solving this is to pass a custom header to your request like:
#PostMapping("post-path")
ResponseEntity<Void> postRequest(#RequestHeader(HEADER_CLIENT_NAME) String feignClientName, #RequestBody RequestBody requestBody);
I want to set the header in interceptor for only this feign client. Before setting the header, first, the interceptor checks HEADER_CLIENT_NAME header if exists and have the desired value:
private boolean criteriaMatches(RequestTemplate requestTemplate) {
Map<String, Collection<String>> headers = requestTemplate.headers();
return headers.containsKey(HEADER_CLIENT_NAME)
&& headers.get(HEADER_CLIENT_NAME).contains("feign-client-name");
}
Thus, you can check before setting the basic authentication. In interceptor:
#Override
public void apply(RequestTemplate template) {
if (criteriaMatches(template)) {
/*apply auth header*/
}
}
In this way, other feign client's requests won't be manipulated by this interceptor.
Finally, I set the feignClientName to the request:
feignClient.postRequest("feign-client-name", postBody);
One way to do this to remove the #Configuration annotation from the FooClientConfig class as in the current situation it is applied globally.
And then use
#FeignClient(value = "foo", configuration = FooClientConfig.class)
on all of the feign clients you want to use the config with.

Test the remote client jndi lookup using arquillian

Setup: arquillian, jboss as 7.1.1.final as a managed Container
I am currently migrating an EJB application from EJB 2.x to 3.x and JBoss 3.x to JBoss AS 7.1.
During this process i would like to get most classes under test and stumbled over arquillian.
While arquillian seems to offer some nice features on inter-bean-functionality i cannot figure out whether or not the testing of remote client features using jndi lookups works or not.
I used the Arquillian Getting started guides on my beans which worked, but since these are using #Inject and in my application jndi lookups are used everywhere i (at least think that i) need to swerve from that path.
Here is the TestCase i created based on Arquillian Getting Started. I explicitly left in all attempts using jndi properties of which i thought they might help.
The Test
should_create_greeting()
works if the Greeter bean using a separate Producer.
#RunWith(Arquillian.class)
public class GreeterTest {
public static final String ARCHIVE_NAME = "test";
Logger logger = Logger.getLogger(GreeterTest.class.getName());
#Deployment
public static Archive<?> createDeployment() {
JavaArchive jar = ShrinkWrap.create(JavaArchive.class, ARCHIVE_NAME + ".jar").addPackage(Greeter.class.getPackage())
.addAsManifestResource("test-persistence.xml", "persistence.xml").addAsManifestResource("OracleGUIDS-ds.xml")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
return jar;
}
/**
* #Inject works using a producer with {#code #Produces}
*/
// #Inject
// Greeter greeter;
#ArquillianResource
Context context;
GreeterRemote greeter;
#Before
public void before() throws Exception {
Map<String, String> env = new HashMap<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.as.naming.InitialContextFactory");
env.put("jboss.naming.client.ejb.context", "true");
// env.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT",
// "false");
// env.put("jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS",
// "false");
// env.put("jboss.naming.client.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED",
// "false");
for (Map.Entry<String, String> entry : env.entrySet()) {
context.addToEnvironment(entry.getKey(), entry.getValue());
}
greeter = (GreeterRemote) context.lookup(ARCHIVE_NAME + "/" + Greeter.class.getSimpleName() + "!"
+ GreeterRemote.class.getName());
}
#Test
public void should_create_greeting() {
Assert.assertEquals("Hello, Earthling!", greeter.createGreeting("Earthling"));
greeter.greet(System.out, "Earthling");
}
}
Is it possible to get this test running with jndi lookup? Am i missing something?
If you want to test the Remote features of a EJB you probably want to run on the client side and not in container.
You can configure the Deployment to be only client side by using #Deployment(testable=false). The #Test methods will then run as if you were a remote client.
Beyond that you can just lookup the bean via the injected Context if you want.
I had the same issue, so in a workaround i just added on the method to be tested the remoteejb as a parameter.
On my ejb:
public List localBean.obtain(RemoteEJB remoteEjb){
return remoteEjb.obtain();
}
Then on the arquillian test :
#Inject
private LocalBean localBean;
#Inject
private RemoteEJB remoteEjb;
#Test
public void test(){
List<Vo>voList = localBean.obtain(remoteEjb);
}
The best part is the remote ejb its injected and on the caller method original
#EJB(lookup="java:global/ear/ejb/RemoteEjb")
private RemoteEJB remoteEjb;