Spring cloud loadbalancer - Feign + SimpleDiscoveryClient with healthcheck/retry - java

I'm struggling to switch from Ribbon to Spring Cloud Loadbalancer after upgrading spring cloud versions.
Setting up the SimpleDiscoveryClient with Feign was easy.
But the simplediscovery client is 'too simple'.
I want to add at least a healthcheck so it doesn't use an instance that is potentially down & preferably also a retry mechanism.
I've read the docs over & over but cannot find an easy way to set this up.
https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#instance-health-check-for-loadbalancer
I found this example custom config for the health check, but it doesn't work.
public class CustomLoadBalancerConfiguration {
#Bean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
ConfigurableApplicationContext context) {
return ServiceInstanceListSupplier.builder()
.withDiscoveryClient()
.withHealthChecks()
.build(context);
}
}
if I run it as is, it throws the following missing bean error:
Method discoveryClientServiceInstanceListSupplier in XXX.CustomLoadBalancerConfig required a bean of type 'org.springframework.web.client.RestTemplate' that could not be found.
Can anyone give me some pointers on how to get this working or how I can replicate the ribbon behavior?

Add the below RestTemplate Bean in your configuration.
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}

Try adding the following to your application class
#LoadBalancerClients(defaultConfiguration = LoadBalancerClientConfiguration.class)
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
This will enable the creation of DiscoveryClientServiceInstanceListSupplier (note the class itself is not available in the context, so you can't just autowire it for testing).

Related

Micronaut - What is Springframework #Bean equivalent?

I am very new to Micronauts and I have a fair bit of experience developing spring boot applications. With this background I was stumbled upon creating custom beans like how I used to create with #Bean annotations on Spring applications.
In my case I have a library that provides an Interface and its implementation class. I wanted to use the interface in my code and try to inject the implementation and it failes with below error
Caused by: io.micronaut.context.exceptions.NoSuchBeanException: No bean of type [io.vpv.saml.metadata.service.MetaDataParser] exists for the given qualifier: #Named('MetaDataParserImpl'). Make sure the bean is not disabled by bean requirements (enable trace logging for 'io.micronaut.context.condition' to check) and if the bean is enabled then ensure the class is declared a bean and annotation processing is enabled (for Java and Kotlin the 'micronaut-inject-java' dependency should be configured as an annotation processor).
Here is my code
#Singleton
public class ParseMetadataImpl implements ParseMetadata {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Inject
#Named("MetaDataParserImpl")
private MetaDataParser metaDataParser;
#Override
public IDPMetaData getIDPMetaData(URL url) throws IOException {
logger.info("Parsing {}", url);
logger.info("metaDataParser {}", metaDataParser);
return metaDataParser.parseIDPMetaData(url);
}
}
I am sure there is somehting wrong I am doing and need to understand what to do. I have this working by adding below code and removing annotations around metaDataParser.
#PostConstruct
public void initialize() {
//Want to Avoid This stuff
this.metaDataParser = new MetaDataParserImpl();
}
Using Spring Boot it would be possible to add a #Bean annotation to create some custom beans we can do #Autowired to inject it everywhere on our application. Is there an equivalent on Micronauths that I am missing. I went through the guide on https://docs.micronaut.io/2.0.0.M3/guide/index.html and was not able to get anything to get this working.
Can someone suggest how I can use the #Inject to inject custom beans?
Just incase you want to see this, here is the application on Github.
https://github.com/reflexdemon/saml-metadata-viewer
With the help from Deadpool and a bit of reading I got what I was looking for. The solution was creating #BeanFactory
See Javadoc here: https://docs.micronaut.io/latest/guide/ioc.html#builtInScopes
The #Prototype annotation is a synonym for #Bean because the default scope is prototype.
Thus here is an example that will match the the behavior of Spring framework
Here is the answer for anyone who also is looking for such a thing.
import io.micronaut.context.annotation.Factory;
import io.vpv.saml.metadata.service.MetaDataParser;
import io.vpv.saml.metadata.service.MetaDataParserImpl;
import javax.inject.Singleton;
#Factory
public class BeanFactory {
#Singleton
public MetaDataParser getMetaDataParser() {
return new MetaDataParserImpl();
}
}

Spring Boot Camel - Autowiring issues in Camel components

I am using Spring Boot 1.5.7 and Apache Camel 2.19.3, using Spring Boot AutoConfiguration provided by spring-boot-starter-camel
It is pretty basic Spring Boot and Camel initialized as in their tutorial, so we have a RouteBuilder component that does exactly that.
#Component
public class CIFRoutes extends RouteBuilder {
#Override
public void configure() throws Exception {
// build routes
}
}
We have a Configuration that defines some beans we need in our application
#Configuration
public class MyConfiguration {
#Bean
public void Map<String, Object> map() {
return new HashMap<>()
}
}
Finally, we have a custom InflightRepository implementation that should be scanned by auto-configuration and added to the CamelContext which basically works, but for some reason, the component doesn't get initialized properly. Means, its dependencies are not initialized but the bean is instantiated and injected in my Application.
#Component
public class MyCustomInflightRepository extends DefaultInflightRepository {
#Autowired
private Map<String, Object> map;
#Override
public void add(Exchange exchange) {
super.addExchange(exchange);
// ....
}
}
The problem now is that map remains (null), I also tried adding a #PostConstruct initializer method but it doesn't get called.
As far as I was able to reconstruct, it seems to be connected to premature in CamelAutoConfiguration where the CamelContext bean gets instantiated (done in private method afterPropertiesSet.
InflightRepository inflightRepository = getSingleBeanOfType(applicationContext, InflightRepository.class);
if (inflightRepository != null) {
LOG.info("Using custom InflightRepository: {}", inflightRepository);
camelContext.setInflightRepository(inflightRepository);
}
If MyCustomInflightRepository doesn't implement InflightRepository, the bean is initialized correctly, but indeed not recognized by Camel. When disabling auto-configuration, the bean's dependencies are injected.
So, either I'm doing the impossible by Spring standards or there's something fishy with the Camel component for Spring.
I'm a bit quick on resolving this (I wanted to post this two days ago already^^), but a colleague figured out what could be the problem.
When using CamelAutoConfiguration the InflightRepository bean (or practicially everything for which Camel tries to get a matching bean here), the context is accessed before property resolvers are fully initialized which leads to the bean being initialized (and cached in context) before any auto-wired properties can be resolved.
I'm not a Spring expert but this behavior is a bit problematic in my opinion because uninitialized beans are pulled into the CamelContext when you rely on Spring DI for your custom components.
To be sure, I'll raise this with the maintainers...
By the way, my simple solution was to manually setting the in-flight repository in context configuration (as suggested)
#Bean
public CamelContextConfiguration camelConfig() {
return new CamelContextConfiguration() {
#Override
public void beforeApplicationStart(CamelContext context) {
context.setInflightRepository(new MyCustomInflightRepository(/* Dependencies go here */ ));
}
#Override
public void afterApplicationStart(CamelContext camelContext) {
}
};
}
Also it seems to be an issue when use camel-http-starter in your project which isn't recommended, they claim it is deprecated.
So, either don't do DI (regardless if via property or constructor injection) for your camel-managed beans or skip that starter.
The problem is that a Map<String,Object> is too vague for Spring to be able to understand what you want; I think the default behavior is that it'll give you all beans keyed by name.
Instead, be more specific, or possibly provide the necessary parameters as constructor arguments and configure them explicitly in an #Bean method (it's a good idea to always use constructor injection anyway).

Overriding beans in Integration tests

For my Spring-Boot app I provide a RestTemplate though a #Configuration file so I can add sensible defaults(ex Timeouts). For my integration tests I would like to mock the RestTemplate as I dont want to connect to external services - I know what responses to expect. I tried providing a different implementation in the integration-test package in the hope that the latter will override the real implementation , but checking the logs it`s the other way around : the real implementation overrides the test one. How can I make sure the one from the TestConfig is the one used?
This is my config file :
#Configuration
public class RestTemplateProvider {
private static final int DEFAULT_SERVICE_TIMEOUT = 5_000;
#Bean
public RestTemplate restTemplate(){
return new RestTemplate(buildClientConfigurationFactory());
}
private ClientHttpRequestFactory buildClientConfigurationFactory() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(DEFAULT_SERVICE_TIMEOUT);
factory.setConnectTimeout(DEFAULT_SERVICE_TIMEOUT);
return factory;
}
}
Integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestConfiguration.class)
#WebAppConfiguration
#ActiveProfiles("it")
public abstract class IntegrationTest {}
TestConfiguration class:
#Configuration
#Import({Application.class, MockRestTemplateConfiguration.class})
public class TestConfiguration {}
And finally MockRestTemplateConfiguration
#Configuration
public class MockRestTemplateConfiguration {
#Bean
public RestTemplate restTemplate() {
return Mockito.mock(RestTemplate.class)
}
}
Since Spring Boot 1.4.x there is an option to use #MockBean annotation to fake Spring beans.
Reaction on comment:
To keep context in cache do not use #DirtiesContext, but use #ContextConfiguration(name = "contextWithFakeBean") and it will create separate context, while it will keep default context in cache. Spring will keep both (or how many contexts you have) in cache.
Our build is this way, where most of the tests are using default non-poluted config, but we have 4-5 tests that are faking beans. Default context is nicely reused
1.
You can use #Primary annotation:
#Configuration
public class MockRestTemplateConfiguration {
#Bean
#Primary
public RestTemplate restTemplate() {
return Mockito.mock(RestTemplate.class)
}
}
BTW, I wrote blog post about faking Spring bean
2.
But I would suggest to take a look at Spring RestTemplate testing support. This would be simple example:
private MockRestServiceServer mockServer;
#Autowired
private RestTemplate restTemplate;
#Autowired
private UsersClient usersClient;
#BeforeMethod
public void init() {
mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void testSingleGet() throws Exception {
// GIVEN
int testingIdentifier = 0;
mockServer.expect(requestTo(USERS_URL + "/" + testingIdentifier))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess(TEST_RECORD0, MediaType.APPLICATION_JSON));
// WHEN
User user = usersClient.getUser(testingIdentifier);
// THEN
mockServer.verify();
assertEquals(user.getName(), USER0_NAME);
assertEquals(user.getEmail(), USER0_EMAIL);
}
More examples can be found in my Github repo here
The Problem in your configuration is that you are using #Configuration for your test configuration. This will replace your main configuration. Instead use #TestConfiguration which will append (override) your main configuration.
46.3.2 Detecting Test Configuration
If you want to customize the primary configuration, you can use a
nested #TestConfiguration class. Unlike a nested #Configuration class,
which would be used instead of your application’s primary
configuration, a nested #TestConfiguration class is used in addition
to your application’s primary configuration.
Example using SpringBoot:
Main class
#SpringBootApplication() // Will scan for #Components and #Configs in package tree
public class Main{
}
Main config
#Configuration
public void AppConfig() {
// Define any beans
}
Test config
#TestConfiguration
public void AppTestConfig(){
// override beans for testing
}
Test class
#RunWith(SpringRunner.class)
#Import(AppTestConfig.class)
#SpringBootTest
public void AppTest() {
// use #MockBean if you like
}
Note: Be aware, that all Beans will be created, even those that you override. Use #Profile if you wish not to instantiate a #Configuration.
#MockBean and bean overriding used by the OP are two complementary approaches.
You want to use #MockBean to create a mock and forget the real implementation : generally you do that for slice testing or integration testing that doesn't load some beans which class(es) you are testing depend on and that you don't want to test these beans in integration.
Spring makes them by default null, you will mock the minimal behavior for them to fulfill your test.
#WebMvcTest requires very often that strategy as you don't want to test the whole layers and #SpringBootTest may also require that if you specify only a subset of your beans configuration in the test configuration.
On the other hand, sometimes you want to perform an integration test with as many real components as possible, so you don't want to use #MockBean but you want to override slightly a behavior, a dependency or define a new scope for a bean, in this case, the approach to follow is bean overriding :
#SpringBootTest({"spring.main.allow-bean-definition-overriding=true"})
#Import(FooTest.OverrideBean.class)
public class FooTest{
#Test
public void getFoo() throws Exception {
// ...
}
#TestConfiguration
public static class OverrideBean {
// change the bean scope to SINGLETON
#Bean
#Scope(ConfigurableBeanFactory.SINGLETON)
public Bar bar() {
return new Bar();
}
// use a stub for a bean
#Bean
public FooBar BarFoo() {
return new BarFooStub();
}
// use a stub for the dependency of a bean
#Bean
public FooBar fooBar() {
return new FooBar(new StubDependency());
}
}
}
With #Primary annotation, Bean overriding works with Spring Boot 1.5.X but fails with Spring Boot 2.1.X it throw error:
Invalid bean definition with name 'testBean' defined in sample..ConfigTest$SpringConfig:..
There is already .. defined in class path resource [TestConfig.class]] bound
Please add below properties= which will instruct Spring explicitly to allow overriding, it is self explainatory.
#SpringBootTest(properties = ["spring.main.allow-bean-definition-overriding=true"])
UPDATE: You can add the same property in application-test.yml (file name depend upon what test profile name you are tests with)
Getting a little deeper into it, see my second answer.
I solved the Problem using
#SpringBootTest(classes = {AppConfiguration.class, AppTestConfiguration.class})
instead of
#Import({ AppConfiguration.class, AppTestConfiguration.class });
In my case the Test is not in the same package as the App. So I need to specify the AppConfiguration.class (or the App.class) explicit. If you use the same package in the test, than I guess you could just write
#SpringBootTest(classes = AppTestConfiguration.class)
instead of (not working)
#Import(AppTestConfiguration.class );
It is pretty wired to see that this is so different. Maybe some one can explain this. I could not find any good answers until now. You might think, #Import(...) is not picked up if #SpringBootTestsis present, but in the log the overriding bean shows up. But just the wrong way around.
By the way, using #TestConfiguration instead #Configuration also makes no difference.
I´ve declared an inner configuration class within my test because I wanted to overwrite just a single method
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class FileNotificationWebhookTest{
public static class FileNotificationWebhookTestConfiguration {
#Bean
#Primary
public FileJobRequestConverter fileJobRequestConverter() {
return new FileJobRequestConverter() {
#Override
protected File resolveWindowsPath(String path) {
return new File(path);
}
};
}
}
}
However,
Declaring the configuration in #SpringBootTest did not work:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = {FileNotificationWebhookTest.FileNotificationWebhookTestConfiguration.class})
or annotating the test configuration with #Configuration did not work:
#Configuration
public static class FileNotificationWebhookTestConfiguration {
}
and was leading to
Caused by: org.springframework.context.ApplicationContextException:
Unable to start web server; nested exception is
org.springframework.context.ApplicationContextException: Unable to
start ServletWebServerApplicationContext due to missing
ServletWebServerFactory bean.
What did work for me ( contrary to some other posts here) was using #Import
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Import(FileNotificationWebhookTest.FileNotificationWebhookTestConfiguration.class)
class FileNotificationWebhookTest {
}
Using Spring: 5.3.3 with Spring-Boot-Starter: 2.4.2
#MockBean creates Mockito mock instead of production build.
If you do not want to use Mockito, but provide a replacement in some other way (i.e. by disabling some features of bean with feature toggles), I suggest using combination of #TestConfiguration (since Spring Boot 1.4.0) and #Primary annotation.
#TestConfiguration will load your default context and apply your #TestConfiguration piece in addition to it. Adding #Primary will force your mocked RestTemplate to be injected to it's dependents.
See simplified example below:
#SpringBootTest
public class ServiceTest {
#TestConfiguration
static class AdditionalCfg {
#Primary
#Bean
RestTemplate rt() {
return new RestTemplate() {
#Override
public String exec() {
return "Test rest template";
}
};
}
}
#Autowired
MyService myService;
#Test
void contextLoads() {
assertThat(myService.invoke()).isEqualTo("Test rest template");
}
}
This is super weird.
In my case, (Spring Boot 2.6.7), I could simply #Import MyTestConfiguration containing a custom #Primary #Bean into my #SpringBootTest, and everything worked.
Right until I needed to explicitly name my bean.
Then I suddenly had to resort to
#SpringBootTest(
properties = ["spring.main.allow-bean-definition-overriding=true"],
classes = [MyTestConfig::class],
)
Check this answer along with others provided in that thread.
It's about overriding bean in Spring Boot 2.X, where this option was disabled by default. It also has some ideas about how to use Bean Definition DSL if you decided to take that path.
The simplest solution I found was to set this property in application.properties:
spring.main.allow-bean-definition-overriding=true
This will enable overriding of beans.
Next, create a configuration class in test, and annotate your bean with:
#Bean
#Primary
This way, this bean will override your usual bean when running tests.

How to access Spring Bean from JerseyTest subclass

Here is my abstract class which starts Jersey with given Spring context:
public abstract class AbstractJerseyTest extends JerseyTest {
public void setUp() throws Exception {
super.setUp();
}
#AfterClass
public void destroy() throws Exception {
tearDown();
}
#Override
protected URI getBaseUri() {
return URI.create("http://localhost:9993");
}
#Override
protected Application configure() {
RestApplication application = new RestApplication();
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
properties.put("contextConfigLocation", "classpath:spring-context-test.xml");
application.setProperties(properties);
application.register(this);
return application;
}
}
So, the problem is that I need to access Spring bean from my test to populate database with some data.
Jersey version is 2.6
Also I found a similar question here
But it's related to Jersey 1.x so it doesn't work for Jersey 2.x
Could anyone point me in the right direction?
Solution was really simple.
I added:
#Autowired
private Repository repository;
to the AbstractJerseyTest and this field was automatically autowired during test startup. I don't know details about how it works, but it seems that when I register instance of the test in REST application
application.register(this);
it automatically autowires all beans in the test.
Normally in your case, I'd just say work with mocks, but there are cases where you may need to expose the services in the test class.
To do this without any "ugly hacks", you will need to get a handle on the ServiceLocator (which is analogous to Spring's ApplicationContext). When the Jersey app boots up, all the Spring services from the ApplicationContext are put into the ServiceLocator through HK2's Spring bridge.
The problem is JerseyTest does not expose the ServiceLocator in any way. The only way I can think of to get a hold of it, is to create your own TestContainerFactory, and create the ApplicationHandler, which exposes the ServiceLocator.
Trying to implement your own TestContainerFactory is not a walk in the park, if you don't know what you're doing. The easiest thing to do is just look at the source code for Jersey's InMemoryTestContainerFactory. If you look at the constructor for the inner class InMemoryTestContainer, you will see it creating the ApplicationHandler. This is how you can expose the ServiceLocator, through the appHandler.getServiceLocator().
So if you copied that class, and exposed the ServiceLocator, you could create your JerseyTest extension, and call the ServiceLocator.inject(Object) method to inject the test class.
public abstract class AbstractServiceLocatorAwareJerseyTest extends JerseyTest {
private final ServiceLocatorAwareInMemoryTestContainerFactory factory
= new ServiceLocatorAwareInMemoryTestContainerFactory();
private ServiceLocator locator;
#Override
public TestContainerFactory getTestContainerFactory() {
return factory;
}
#Before
#Override
public void setUp() throws Exception {
super.setUp();
this.locator = factory.getServiceLocator();
if (injectTestClass()) {
this.locator.inject(this);
}
}
public boolean injectTestClass() {
return true;
}
public ServiceLocator getServiceLocator() {
return locator;
}
}
And if for any reason you needed it, the ServiceLocator also has the ApplicationContext, which you could also expose to your test class if needed.
I put together a GitHub project, with a complete implementation, with tests if you want to take a look at it.
UPDATE
Though the OP's answer to this question works, I believe the fact that it works, is a bug. I originally deleted this answer, after the OP posted their answer, but after some testing, I believe that solution is a bug, so I've undeleted this post for anyone who doesn't like the warning1 you get when you use that solution
1. "WARNING: A provider SimpleTest registered in SERVER runtime does not implement any provider interfaces applicable in the SERVER runtime. Due to constraint configuration problems the provider SimpleTest will be ignored."

Bean casting error from nested injections

I am working with Spring Integration with my project right now, specifically with MessageChannel/PublishSubscribeChannel. What I am trying to achieve is to create a broker module, so that other part of the system can call this module to send message to a specific MessageChannel.
Here is what I am doing now in the broker module:
#Configuration
public class BrokerConfiguration {
#Bean
public MessageChannel brokerChannel1() {
return new PublishSubscribeChannel();
}
}
and:
#Component
public class BrokerA {
#Autowired
#Qualifier("brokerChannel1")
public MessageChannel messageChannel;
public void sendAMessage() {
messageChannel.send(MessageBuilder.withPayload("This is a message!").build());
}
}
I have played around this setup by creating a SpringBootApplication within the broker module and it seems to work perfectly fine. However, when I try to use it in a different module of my system like this:
#Autowired
private BrokerA brokerA;
public void doSomethingHere() {
brokerA.sendAMessage();
}
I get a ClassCastException like this:
java.lang.ClassCastException: org.springframework.integration.channel.PublishSubscribeChannel cannot be cast to org.springframework.messaging.MessageChannel
And when I change messageChannel in BrokerA to the type of PublishSubscribeChannel, it will complain about PublishSubscribeChannel doesn't have a method called send().
This really baffles me. Any suggestions or comments? Thank you!
You have an old version of Spring Integration on the classpath; MessageChannel etc was moved from o.s.integration... to o.s.messaging in Spring 4.0.
You need to use Spring Integration 4.x.
Check your classpath, probably you have duplicated jars.
I ran your code on my environment with last spring boot version without any version specification about spring and it's working just right, the only error is on
MessageBuilder.withPayload("This is a message!") it should be MessageBuilder.withPayload("This is a message!").build()
And I verified using org.springframework.integration.support.MessageBuilder.
Try doing an explicit cast in the return statement of brokerChannel1() in BrokerConfiguration.

Categories