I have inherited a Java SpringBoot application (currently SpringBoot 2.7) that uses Hystrix for fault tolerance. I am aware that this has been deprecated, and we plan to more to Resilience4J
The Hystrix dependencies are
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
<version>2.2.10.RELEASE</version>
<scope>compile</scope>
</dependency>
The main class in the application is annotated with:
#EnableCircuitBreaker
#EnableHystrixDashboard
#EnableHystrix
The following properties are specified:
hystrix.threadpool.default.coreSize=30
hystrix.threadpool.default.maxQueueSize=300
hystrix.threadpool.default.queueSizeRejectionThreshold=300
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=30000
hystrix.command.default.circuitBreaker.errorThresholdPercentage=50
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000
hystrix.command.default.circuitBreaker.requestVolumeThreshold=60
hystrix.shareSecurityContext=true
I have gone through the usage of Hystrix within the application to understand exactly what the application is gaining from using Hystrix. I see that there are two different usages of the annotation HystrixCommand within the application.
In some cases, there are methods that are annotated as follows:
#HystrixCommand(fallbackMethod = "executeFallback")
public ResponseClass methodName (inputs...) {
private ResponseClass executeFallback(inputs...) {
LOGGER.error("Something went wrong...");
return new ResponseClass();
}
I have simplified the code, but my understanding is if the circuit is open, then it will result in the fallback method (named "executeFallback" above) being executed until the circuit is closed again.
But there are also many instances within the application of methods that are simply annotated with #HystrixCommand but there is no fallback method specified. These methods are also trying to access some data source, that could potentially be unavailable.
For example:
#HystrixCommand
public ResponseClass methodName (inputs...) {
My question is: what does an application gain from annotating methods with #HystrixCommand if no fallback method is specified?
Related
This question already has answers here:
Should SpringRunner be used in Spring Boot with Junit 5
(3 answers)
Closed last year.
My micro-service project based on spring-boot framework and all my unit test running with spring runner.
#RunWith(SpringRunner.class)
adding this annotations, imports the following library:
import org.springframework.test.context.junit4.SpringRunner;
How can I set my test classes to run with junit5 ?
Using JUnit Jupiter (aka JUnit 5) no longer requires ˋ #RunWith(SpringRunner.class)ˋ since this is a JUnit 4 mechanism. With recent versions of Spring/Spring Boot JUnit 5 support comes out of the box eg through using ˋspring-boot-starter-testˋ.
I recommend to exclude dependencies on JUnit 4 in your Maven/Gradle file to make confusing JUnit 4 and 5 features less likely.
Here’s an article that shows the basics: https://howtodoinjava.com/spring-boot2/testing/junit5-with-spring-boot2/
Remove JUnit4 from your build Path.
For example :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#TestPropertySource(locations = "classpath:application-local.properties")
public class MyTest {
#Before
public void setUp() {
...
}
#Test
public void testMethod() {
Assert.assertTrue(...);
}
}
will become
#SpringBootTest(classes = Application.class)
#TestPropertySource(locations = "classpath:application-local.properties")
public class MyTest {
#BeforeEach
public void setUp() {
...
}
#Test
public void testMethod() {
Assertions.assertTrue(...);
}
}
Spring 2.4 seems to include JUnit 5 and make it the default out of the box.
Besides updating #RunWith(SpringJUnit4ClassRunner.class) to #ExtendWith(SpringExtension.class) I had to add the following to build.gradle for the tests to actually run:
test {
useJUnitPlatform {}
}
This last step may have been due to JUnit 4 being a dependency of one of my dependencies, but every other thing I read didn't suggest this was needed.
The first annotation #RunWith(SpringRunner.class) is used to provide a bridge between Spring Boot test features and JUnit. SpringRunner.class enables full support of spring context loading and dependency injection of the beans in the tests. #SpringBootTest create ApplicationContext tests through SpringApplication that will be utilized in our tests. It bootstraps the entire container since the embedded server and creates a web environment.
In our test, we can mimic the real web environment setting it as RANDOM_PORT that also loads WebServerApplicationContext. The embedded server is started and listen to on a random port.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {YourPackage.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class YourClassTest {
#LocalServerPort
private int port;
#Autowired
TestRestTemplate restTemplate;
HttpHeaders headers = new HttpHeaders();
#ParameterizedTest
#JsonFileSource(resources = "/param.json")
void createBusinessEntity(JsonObject object){
....
}
}
#LocalServerPort annotation provides us the injected HTTP port that got allocated at runtime. It is a convenient alternative for #Value("${local.server.port}").
To access a third-party REST service inside a Spring application we use the Spring RestTemplate or TestRestTemplate the convenient alternative that is suitable for integration tests by injecting it in our test class. With spring-boot-starter-test dependency in our project, we can access to "TestRestTemplate" class in runtime.
In our test method, we are using the junit-json-params , a Junit 5 library that provides annotations to load data from JSON Strings or files in parameterized tests. We also annotated the method with #ParameterizedTest annotation to complement the library bellow. It is used to signal the annotated method is a parameterized test method. That method must not be private or static. They also must specify at least one ArgumentsProvider via #ArgumentsSource or a corresponding composed annotation.
Our #ArgumentsSource a JSON file #JsonFileSource(resources = "param.json") we put inside the test.resources package. #JsonFileSource lets you use JSON files from the classpath. It supports single objects, arrays of objects and JSON primitives.
The JSON object retrieved from the file is bound to the method params "object" that it is converted to a POJO object, in this case, our entity model.
In the Pom.xml we must import these libraries...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.joshka</groupId>
<artifactId>junit-json-params</artifactId>
<version>5.5.1-r0</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit-jupiter.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
Take a look at these articles that a post on DZone and my blog where you can access a complete sample and explanation step by step how to test spring boot microservice using Junit 5.
https://dzone.com/articles/microservices-in-publish-subscribe-communication-u
https://www.jeevora.com/2019/11/18/publish-subscribe-messaging-systems/
The below object mapper configuration is not working when I add jjwt security to spring boot application.
#Configuration
public class CustomObjectMapper extends ObjectMapper {
/**
* Default serial version id generated.
*/
private static final long serialVersionUID = 1L;
public CustomObjectMapper() {
this.setSerializationInclusion(Include.NON_EMPTY);
this.registerModule(new ThreeTenModule());
this.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
}
Security dependencies added here
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
where as the below Jackson annotations are working on class/field levels.
#JsonInclude(Include.NON_EMPTY)
Why the bean configured custom object mapper not been used for serialization & deserialization? Any other libraries configured object mapper overriding my custom mapper?
After a long investigation, i have noticed #EnableWebMvc annotated configuration bean available in one dependent library. And got to know from here that #EnableWebMvc disables Spring Boot's MVC auto-configuration, thus giving complete control to provide customer MVC configuration. HTTP Message Convertors will also be included in Spring MVC component which in turn disables my custom jackson object mapper configuration.
PS: As jjwt imports jackson databind dependency by default, it fell in my suspect list. Feel good that i could RCA. Thanks.
Goal: migration from Spring Boot 1.x (webMvc) to version 2 (webFlux) and Spring Cloud Edgware SR2 to FinchleyM8 (awaiting release version).
Problem: Feign -> OpenFeign. OpenFeign under the hood uses RxJava but WebFlux - Reactor3. At the current moment when I using Mono as returned type, I have got an error:
Caused by: org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class reactor.core.publisher.Mono]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of reactor.core.publisher.Mono (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
Code example:
#FeignClient(name = "thirdpartyresource", url = "${third.party.resource.url}")
public interface ThirdPartyResource {
#PostMapping(value = "/validate", consumes = APPLICATION_FORM_URLENCODED_VALUE)
Mono<ValidationResultDto> validate(MultiValueMap multiValueMap); // WORKS BAD
// Single<ValidationResultDto> validate(MultiValueMap multiValueMap); WORKS WELL
}
Question:
Do I need to create my own converter Single to Mono or it's some problems of spring-cloud-starter-openfeign and all should work OOTB?
The reactor.core.publisher.Mono belongs to the spring-boot-starter-webflux jar.
Get the latest version of it from the mvn repository.
Then add it to your pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>2.5.3</version>
</dependency>
Also do remove spring-boot-starter-web from your pom.xml, just in case if you have it.
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>-->
This fixed the issue!
You can use those methods to adapt:
For Single<T> rxJavaSingle
Mono.from(RxReactiveStreams.toPublisher(rxJavaSingle))
For Completable rxJavaCompletable
Mono.from(RxReactiveStreams.toPublisher(rxJavaCompletable))
I have a Spring Boot app that is not a web app, that has this piece of code
ResponseEntity<GeolocationAddress> response = new RestTemplate().getForEntity(urlStringConnection,
GeolocationAddress.class);
But then I have this error:
The import org.springframework.web cannot be
resolved
so I added this dependency
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>3.0.2.RELEASE</version>
</dependency>
But then when I start the app I got this error:
Could not evaluate condition on org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration due to org/springframework/web/context/support/StandardServletEnvironment not found. Make sure your own configuration does not rely on that class. This can also happen if you are #ComponentScanning a springframework package (e.g. if you put a #ComponentScan in the default package by mistake)
I may be a little late, but I had similar issues. By default Spring Boot tries to deduce the type of the application context to use by examining the class path. If it finds either javax.servlet.Servlet or org.springframework.web.context.ConfigurableWebApplicationContext, it instantiates a WebApplicationContext.
To avoid the error you got, I had to do the following in the main method of the app:
#SpringBootApplication
public class App {
public static void main(String[] args) {
new SpringApplicationBuilder(App.class)
.contextClass(AnnotationConfigApplicationContext.class).run(args);
}
}
I also excluded any unwanted AutoConfigure classes in the application.properties :
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,org.springframework.boot.actuate.autoconfigure.TraceWebFilterAutoConfiguration,org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration,org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration
I have an application running spring-boot, jersey2 and spring metrics:
below is maven snippet:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Jersey used to work well until introducing actuator dependency.
Then following bean has been created to make Jersey working as filter:
#Bean
public FilterRegistrationBean jerseyFilterRegistration() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setName("jerseyFilter");
bean.setFilter(new ServletContainer(resourceConfig()));
bean.setOrder(Ordered.LOWEST_PRECEDENCE);
bean.addInitParameter("com.sun.jersey.config.property.WebPageContentRegex", managementContextRegex);
return bean;
}
Metrics are mapped to /admin path. With this configuration I cannot make metrics working. However by adding management.port (different than main app port) both Jersey resource and metrics are available.
What I'm missing here to make both metrics and Jersey resource start working on the same port?
"com.sun.jersey.config.property.WebPageContentRegex"
This is the wrong property. That's for Jersey 1.x. For 2.x, it should be
"jersey.config.servlet.filter.staticContentRegex"
See ServletProperties.FILTER_STATIC_CONTENT_REGEX
As an aside you can avoid having to define your own FilterRegistrationBean by simply setting a couple configuration properties. In your application.properties, you could use the following
spring.jersey.type=filter
spring.jersey.init.jersey.config.servlet.filter.staticContentRegex=<your-regex>
Or you can configure the regex in your ResourceConfig subclass
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
property(ServletProperties.FILTER_STATIC_CONTENT_REGEX, "<your-regex>");
}
}
As another side, just an FYI, the cause of the problem is the default /* url-mapping used for Jersey. If you can change it, doing so would solve the problem. For instance /api. You can configure that in the properties with spring.jersey.applicationPath=/api or with #ApplicationPath("/api") on the ResourceConfig subclass.
And the final aside, there is also a property
ServletProperties.FILTER_FORWARD_ON_404
"jersey.config.servlet.filter.forwardOn404"
I'm not exactly sure how the staticContenRegex property works, I never really dug into to source code. So I don't know if it just does some file IO to get the static file or it forwards the request, but if it does some file IO, then I don't think the property will work for your use case, as the endpoints are not files. In which case the forwardOn404 should work.