Hystrix fallback method is not invoked - java

I'm trying hystrix fallback method.
On localhost:8082, customer-service is running which returns name of the customer.
If the customer-service is down, fallback method should invoke. But it is not happening.
Below is the code snippet.
Please suggest.
#SpringBootApplication
#EnableDiscoveryClient
#EnableCircuitBreaker
#RestController
public class DemoHystrixApplication {
#GetMapping("/")
public String name() {
String str = getCustomerName();
return str;
}
#HystrixCommand(fallbackMethod = "getFallbackCustomerName")
private String getCustomerName() {
RestTemplate restTemplate = new RestTemplate();
URI uri = URI.create("http://localhost:8082");
return restTemplate.getForObject(uri, String.class);
}
private String getFallbackCustomerName() {
System.out.println("coming inside fallback method");
return "Resillient Customer";
}
public static void main(String[] args) {
SpringApplication.run(DemoHystrixApplication.class, args);
}
}

Both the methods i.e. the actual and fallback methods should be public and move these methods to a separate class and annotate it with #Component.
Give a try, hope this helps.

Your #HystrixCommand annotated method should be public.
Not sure about the fallback method but I would set it public as well.

This is because of AOP.
The Spring container injects aspect-aware bean when injecting a bean.
When the name() function is called at the user's request, the method of the aspect-aware bean is called so annotation works.
However, calling this.getCustomerName() directly within the name() calls getCustomerName() on the raw bean before it is wrapped in proxy. It doesn't know aspect. Therefore, annotation does not work.

You can also try stopping and starting the service, if you added the dependency of netflix-hystrix and have dev-tools to pick up changes while executing the service.

Fallback metod should be called from another bean. The problem is you are calling fallback method from RestController.

Related

Hystrix fallback method best practice

Ok so I couldn't find any helpful materials on this topic, a big chunk of articles I found had one method that was annotated with #HystrixCommand and had defined a fallback method.
The other solution I found was using #DefaultProperties(defaultFallback = "fallbackMethod") but the problem with this is that the methods need to have compatible return types.
Unfortunately for me in my service I have many methods with completely different signatures and also I need to get hold of the throwable (in docs it is mentioned that you cannot have any parameters for a default fallback method). The methods look something like this:
#Service
#RequiredArgsConstructor
public class MyService {
private final FeignClient feignClient;
#Override
public String methodA(final CustomObjectA o, final String entity) {
...
}
#Override
public String methodB(final String collection, final Map<String, Object> requestBody) {
...
}
#Override
public String methodC(final String collection, final String id, final Map<String, Object> requestBody) {
...
}
}
And ofc I have more than 3 methods def in the service...
The thing I really want to avoid is making 20 hystrix default fallback methods.
Is there a way where I could def a standard fallback for all methods, no matter what the signatures they have, or am I stuck with defining a fallback method for every single method?
Thanks in advance!!
You will have to implement a fall back for each method.
However using the FallbackFactory might make this easier and allow each method to call one reusable method.
Maybe you don't really want hystrix fallbacks if they are the same for each method. All try catch might solve the same problem.
Let me share the code snippet used in my project.
To call an api like http://www.baidu.com/xxx, you have below steps to follow.
1.Api Definition (fallback = WebServiceApiFallback.class)
#Component
#FeignClient(value = "webServiceApi", configuration = FeignConfiguration.class, fallback = WebServiceApiFallback.class)
public interface WebServiceApi {
#Headers(value = {"Content-Type: application/json", "Accept-Encoding: gzip,deflate"})
#GetMapping(value = "/xxx")
BaseResponse<YourResponse> xxx(YourRequest request);
2.Fallback Definition
#Component
public class WebServiceApiFallback implements WebServiceApi {
#Override
public BaseResponse<YourResponse> xxx(YourRequest request) {
// Your Fallback Code here, when api request failed.
}
3.api host configuration, maybe application.properties...
webServiceApi.ribbon.listOfServers=http://www.baidu.com
4.use it
#Autowired
private WebServiceApi webServiceApi;
For any api, you can just define you request and response, and feign will do the request、 encode、and decode.
[Ref] https://github.com/spring-cloud/spring-cloud-netflix/issues/762

How to wrap an annotation and conditionally applies it to a method

Say I have an annotation (#RequiresAccount) introduced in another library and I'm using it in my project, is there a way to conditionally apply it to a method, e.g. apply it when the customer is from website A and not apply when customer is from website B?
I've taken a look and the only possibility I've found was, creating a wrapper-Annotation:
#Aspect
#Component
public class RequiresAccountWrapperAspect {
#Autowired
private HttpServletRequest request;
private RequiresAccountAspect requiresAccountAspect = new RequiresAccountAspect();
#Around("#annotation(com.example.demo.components.RequiresAccountWrapper)")
public Object checkIfRequiresAccount(ProceedingJoinPoint joinPoint) throws Throwable {
String requestURL = request.getRequestURL().toString();
if (requestURL.startsWith("http://localhost")) {
requiresAccountAspect.checkAccount(joinPoint);
}
return joinPoint.proceed();
}
}
So everywhere you've used your RequiresAccount annotation, you can use this wrapper instead. For example:
#GetMapping("/test")
#RequiresAccountWrapper
public String h() {
return "test";
}
As you can see I'm creating a new instance of the aspect. I don't know if you have access to the Aspect-class itself but if you have you can then call the method in it and pass the joinPoint. To find the URL from the request you can inject the HttpServletRequest.

How to split classes to use serveral #Controller

I am learning spring boot, and i developed the below simple example. I would like to annotate a class as Controller using #Controller. this class has constructor and I want to have access to GreetingFromDeuController as shown:
http://localhost:8080:/GreetingFromDeuController?str = "hi"
the error i am receiving is
#RequestMapping is not applicable on a constructor
please let me know how to solve.
code:
#Controller
#RequestMapping("/GreetingFromDeuController")
public class GreetingFromDeuController {
private String str;
#RequestMapping("/GreetingFrom/deu")
GreetingFromDeuController(#RequestParam(value = "str") String str) {
this.str = str;
}
#RequestMapping("/GreetingFromDeuController")
public String getGreetingFromDeu() {
return this.str;
}
}
First of all your constructor gets initialize much before you hit your URL. So you need to work on your design or tell me your business requirement and I will try to provide you a solution. My refactor code solution will help you to achieve that in two steps. First hit POST method which will do work on setting variable and then subsequent hits of GET method will return that set value.
We can refactor code like below. It will explain use of RequestMapping on method and class.
Considering we have to write two API, one for reading and one for writing.
URLS :
1. POST http://localhost:8080/example/greetings (in request body send {str:'hi'})
2. GET http://localhost:8080/example/greetings
#Controller
#RequestMapping("/example")
public class GreetingFromDeuController {
private String str;
#RequestMapping(value="/greetings" , method = RequestMethod.POST)
public void setGreetingFromDeu(#RequestBody(value = "str") String str)
{
this.str = str;
}
#RequestMapping(value="/greetings" , method = RequestMethod.GET)
public String getGreetingFromDeu()
{
return this.str;
}
}
The #RequestMapping documentation says:
Annotation for mapping web requests onto methods in request-handling
classes with flexible method signatures.
Then you can not do that, if you want to initialize your variables or whatever you can use several ways:
1.- Use #PostConstruct
#PostContruct
public void init() {
this.str = "Anything";
}
2.- Use a simple request to set anything only
#RequestMapping(value="/refresh/anythings", method = RequestMethod.PUT)
public void refresh(#RequestBody(value = "str") String str) {
this.str = str;
}
3.- Use #Value
In application.properties / application.yaml
properties.str = anything
In the Controller
#Value("${properties.str:default}") // by default str is "default"
public String str;
#RequestMapping(value="/greetings" , method = RequestMethod.GET)
public String getGreetingFromDeu() {
return this.str;
}
As far I am concerned, #RequestMapping is not meant for constructors. It should be used for annotating methods or classes. Methods that are responsible for handling requests.
#RequestMapping should be used to map request with endPoint. which can be used as class level and method level.
You can use #RestController (improved from #Controller see difference).
The ideal flow for Spring Boot is Controller -> Service -> Repository
Controller -> maps request with endPoint and return response
Service -> perform business logic
Repository -> Handle database operation
Example
#RestController
#RequestMapping("/api")
public class GreetingController {
#Autowired GreetinService greetingService;
// Request http://localhost:8080/api/GreetingFrom
#GetMapping("/GreetingFrom")
public ResponseEntity<String> GreetingRequestParam(#RequestParam(value = "name") String name) {
greetingService.performBusinessLogic(name);
return new ResponseEntity<String>("Greetings from "+name,HttpStatus.OK);
}
// Request http://localhost:8080/api/GreetingFrom/user2121
#GetMapping("/GreetingFrom/{name}")
public ResponseEntity<String> GreetingPathVariable(#PathVariable(value = "name") String name) {
return new ResponseEntity<String>("Greetings from "+name,HttpStatus.OK);
}
}

Is there a way to access a PathVariable in a controller level #PreAuthorize annotation?

is there a way to access the httpRequest within a controller level #PreAuthorize annotation to obtain a PathVariable and use it for the expression inside the #PreAuthorize annotation?
I want to be able to do the following, where #somePathVariable would result in the actual value passed for the PathVariable:
#RequestMapping(value = "/{somePathVariable}/something")
#PreAuthorize("#someBean.test(#somePathVariable)")
public class SomeController { ... }
It also would be sufficient if i could access the HttpServletRequest and get the PathVariable manually.
Please note that this expression is at the controller level before answering. I'd appreciate any help!
So as #pvpkiran already commented. It's probably not possible to get the param the way i want. However his workaround with using a bean to access the PathVariables manually seems to work just fine.
#Component
#RequiredArgsConstructor
public class RequestHelper {
private final HttpServletRequest httpServletRequest;
/* https://stackoverflow.com/questions/12249721/spring-mvc-3-how-to-get-path-variable-in-an-interceptor/23468496#23468496 */
public Object getPathVariableByName(String name) {
final Map pathVariables = (Map) httpServletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
return pathVariables.get(name);
}
}
and
#RequestMapping(value = "/{somePathVariable}/something")
#PreAuthorize("#someBean.test(#requestHelper.getPathVariableByName('somePathVariable'))")
public class SomeController { ... }
did the job. It's not perfect but it works. The other (prob. better) option is to use #PreAuthorize on method level.
Thanks for your help!

Spring AOP aspect with annotations is not working for base class

I have annotation defined as below
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface OTPFlow {
}
And class A defined as below
public abstract class A {
#OTPFlow
public ModelAndView doSomething() {
//do something and return ModelAndView
}
}
Class B is a controller defined as below
#Controller
#RequestMapping(value = {"/someurl"})
public class B extends A {
#RequestMapping(value = {"/get"}, method = {RequestMethod.POST, RequestMethod.GET})
public ModelAndView get(HttpServletRequest request, HttpServletResponse response) {
return doSomething();
}
}
Aspect is defined as
#Component
#Aspect
public class OTPAspect {
private static final Logger logger = LoggerFactory.getLogger(OTPAspect.class);
#Pointcut("#annotation(OTPFlow)")
public void OTPFlow() {}
#Around("OTPFlow()")
public Object checkOTP(ProceedingJoinPoint joinPoint) {
try {
logger.info("Inside Aspect");
return joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
The problem is when i access "/someurl/get" url, the aspect does not execute. But when i annotate "get" method of class B, aspect executes.
So basically, annotated methods of superclass does not invoke Aspect.
What is the issue? Is there any other way to achieve this? Any help would be appreciated. Thanks
What happens when spring applies the aspect, is that spring creates proxies that wrap your controller instance to intercept calls and add the aspect behaviour before calling the instance method. This has the effect, that any call to "this" from within your controller instance is directly invoked on that instance and will not be intercepted by the wrapping proxy. Therefore, the "get" method is called on the proxy class, which in turn will call the "get" method of the controller instance, and when the latter tries to call the "doSomething" method it will not pass through the proxied "doSomething", but through the internal one.
The way to handle this situation is to apply aspects directly to the methods of your class that will be externally called, in your case directly on the "get" method instead of the "doSomething"
I want to offer an alternative to what M. Deinum and Marios have said correctly: Use AspectJ instead of Spring AOP. AspectJ does not rely on proxies, is faster, more powerful and integrates nicely with Spring as described in Spring manual, Section 9.8, Using AspectJ with Spring applications. With AspectJ what you want to do works out of the box.

Categories