Spring retry not working in RestController - spring

I am trying spring retry and I am facing a strange issue. When I use the retry annotation on a method within a Rest Controller, the retry does not work. But if I move that method to a separate service class, it works. The following code does not work:
#RestController
public class HelloController {
#RequestMapping(value = "/hello")
public String hello() {
return getInfo();
}
#Retryable(RuntimeException.class)
public String getInfo() {
Random random = new Random();
int r = random.nextInt(2);
if (r == 1) {
throw new RuntimeException();
} else {
return "Success";
}
}
}
But the following does:
#RestController
public class HelloController {
#Autowired
private SomeService service;
#RequestMapping(value = "/hello")
public String hello() {
String result = service.getInfo();
return result;
}
}
#Service
public class SomeService {
#Retryable(RuntimeException.class)
public String getInfo() {
Random random = new Random();
int r = random.nextInt(2);
if (r == 1) {
throw new RuntimeException();
} else {
return "Success";
}
}
}
My question is why the #Retryable is not working when used in the controller?

The issue you are seeing is due to how you are calling your getInfo() method.
In the first example, you are calling getInfo() from within the same spring managed bean. In the second example you are calling getInfo() from a different spring managed bean. This distinction is subtle, but very important, and it is very likely what is causing your issues.
When you use the #Retryable annotation, Spring is creating a proxy around your original bean so that they can do special handling in special circumstances. In this specific case, Spring applies an Advice that will delegate a call to your actual method, catching RuntimeException's that it may throw, and retrying the invocation of your method according to the configuration of your #Retryable annotation.
The reason this proxy matters in your case is that only external callers see the proxy advice. Your bean has no knowledge that it is proxied, and only knows that its methods are being called (by the proxy advice). When your bean calls a method on itself, no further proxying is involved, which is why the retrying does not actually occur.

Related

Spring AOP with prototype beans

I am using Spring AOP to fire metrics in our application. I have created an annotation #CaptureMetrics which has an #around advice associated with it. The advice is invoked fine from all the methods tagged with #CaptureMetrics except for a case when a method is invoked on a prototype bean.
The annotation has #Target({ElementType.TYPE, ElementType.METHOD})
PointCut expression:
#Around(value = "execution(* *.*(..)) && #annotation(captureMetrics)",
argNames = "joinPoint,captureMetrics")
Prototype bean creation
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public DummyService getDummyServicePrototypeBean(int a, String b) {
return new DummyService(a, b);
}
DummyService has a method called dummyMethod(String dummyString)
#CaptureMetrics(type = MetricType.SOME_TYPE, name = "XYZ")
public Response dummyMethod(id) throws Exception {
// Do some work here
}
When dummyService.dummyMethod("123") is invoked from some other service, the #Around advice is not called.
Config class
#Configuration
public class DummyServiceConfig {
#Bean
public DummyServiceRegistry dummyServiceRegistry(
#Value("${timeout}") Integer timeout,
#Value("${dummy.secrets.path}") Resource dummySecretsPath) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map<String, String> transactionSourceToTokens = mapper.readValue(
dummySecretsPath.getFile(), new TypeReference<Map<String, String>>() {
});
DummyServiceRegistry registry = new DummyServiceRegistry();
transactionSourceToTokens.forEach((transactionSource, token) ->
registry.register(transactionSource,
getDummyServicePrototypeBean(timeout, token)));
return registry;
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public DummyService getDummyServicePrototypeBean(int a, String b) {
return new DummyService(a, b);
}
}
Singleton Registry class
public class DummyServiceRegistry {
private final Map<String, DummyService> transactionSourceToService = new HashMap<>();
public void register(String transactionSource, DummyService dummyService) {
this.transactionSourceToService.put(transactionSource, dummyService);
}
public Optional<DummyService> lookup(String transactionSource) {
return Optional.ofNullable(transactionSourceToService.get(transactionSource));
}
}
Any advice on this please?
Note:
The prototype Dummy service is used to call a third party client. It is a prototype bean as it has a state that varies based on whose behalf it is going to call the third party.
A singleton registry bean during initialization builds a map of {source_of_request, dummyService_prototype}. To get the dummyService prototype it calls getDummyServicePrototypeBean()
The configuration, registry and prototype dummy bean were correct.
I was testing the flow using an existing integration test and there instead of supplying a prototype Bean, new objects of DummyService were instantiated using the new keyword. It wasn't a spring managed bean.
Spring AOP works only with Spring managed beans.

How to get request in MyBatis Interceptor

I want to measure time of sql execution which will be run by MyBatis (Spring Boot project) and bind that with other request parameters, so I can get full info about performance issues regarding specific requests. For that case I have used MyBatis Interceptor on following way:
#Intercepts({
#Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
#Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class QueryMetricsMybatisPlugin implements Interceptor {
#Override
public Object intercept(Invocation invocation) throws Throwable {
Stopwatch stopwatch = Stopwatch.createStarted();
Object result = invocation.proceed();
stopwatch.stop();
logExectionTime(stopwatch, (MappedStatement) invocation.getArgs()[0]);
return result;
}
}
Now when it come to binding with request, I want to store those metrics in request as attribute. I have tried this simple solution to get request, but that was not working since request was always null (I have read that this solution won't work in async methods, but with MyBatis Interceptor and its methods I think that's not the case):
#Autowired
private HttpServletRequest request;
So, the question is how properly get request within MyBatis interceptor?
One important note before I answer your question: it is a bad practice to access UI layer in the DAO layer. This creates dependency in the wrong direction. Outer layers of your application can access inner layers but in this case this is other way round. Instead of this you need to create a class that does not belong to any layer and will (or at least may) be used by all layers of the application. It can be named like MetricsHolder. Interceptor can store values to it, and in some other place where you planned to get metrics you can read from it (and use directly or store them into request if it is in UI layer and request is available there).
But now back to you question. Even if you create something like MetricsHolder you still will face the problem that you can't inject it into mybatis interceptor.
You can't just add a field with Autowired annotation to interceptor and expect it to be set. The reason for this is that interceptor is instantiated by mybatis and not by spring. So spring does not have chance to inject dependencies into interceptor.
One way to handle this is to delegate handling of the interception to a spring bean that will be part of the spring context and may access other beans there. The problem here is how to make that bean available in interceptor.
This can be done by storing a reference to such bean in the thread local variable. Here's example how to do that. First create a registry that will store the spring bean.
public class QueryInterceptorRegistry {
private static ThreadLocal<QueryInterceptor> queryInterceptor = new ThreadLocal<>();
public static QueryInterceptor getQueryInterceptor() {
return queryInterceptor.get();
}
public static void setQueryInterceptor(QueryInterceptor queryInterceptor) {
QueryInterceptorRegistry.queryInterceptor.set(queryInterceptor);
}
public static void clear() {
queryInterceptor.remove();
}
}
Query interceptor here is something like:
public interface QueryInterceptor {
Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
}
Then you can create an interceptor that will delegate processing to spring bean:
#Intercepts({
#Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }),
#Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}) })
public class QueryInterceptorPlugin implements Interceptor {
#Override
public Object intercept(Invocation invocation) throws Throwable {
QueryInterceptor interceptor = QueryInterceptorRegistry.getQueryInterceptor();
if (interceptor == null) {
return invocation.proceed();
} else {
return interceptor.interceptQuery(invocation);
}
}
#Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
#Override
public void setProperties(Properties properties) {
}
}
You need to create an implementation of the QueryInterceptor that does what you need and make it a spring bean (that's where you can access other spring bean including request which is a no-no as I wrote above):
#Component
public class MyInterceptorDelegate implements QueryInterceptor {
#Autowired
private SomeSpringManagedBean someBean;
#Override
public Object interceptQuery(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
// do whatever you did in the mybatis interceptor here
// but with access to spring beans
}
}
Now the only problem is to set and cleanup the delegate in the registry.
I did this via aspect that was applied to my service layer methods (but you can do it manually or in spring mvc interceptor). My aspect looks like this:
#Aspect
public class SqlSessionCacheCleanerAspect {
#Autowired MyInterceptorDelegate myInterceptorDelegate;
#Around("some pointcut that describes service methods")
public Object applyInterceptorDelegate(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
QueryInterceptorRegistry.setQueryInterceptor(myInterceptorDelegate);
try {
return proceedingJoinPoint.proceed();
} finally {
QueryInterceptorRegistry.clear();
}
}
}

Spring Autowiring and thread-safety

I am new to Spring and recently created a test RESTful web service application.
I am following the Spring #Autowiring way of injecting the beans. Below is my code and a question:
#Service
public class HelloWorld {
#Autowired
private HelloWorldDaoImpl helloWorldDao;
public void serviceRequest() {
helloWorldDao.testDbConnection();
}
}
#RestController
public class HelloWorldController {
#Autowired
private HelloWorld helloWorld;
#RequestMapping(value = "/test", method = RequestMethod.POST)
public String test() {
helloWorld.serviceRequest();
return "Success";
}
}
Now my question is, when I have two requests coming in exactly at same time and they both are sharing the same Service class variable "helloWorld", then how do we ensure that value returned for Request 1 does not go to Request 2 and vice-versa?
Does Spring take care of such multi-threading issues automatically when we use #Autowired?
Spring doesn't intrinsically look after the thread safety of your application, especially since this happens on a completely different layer. Autowiring (and Spring proxying) has nothing to do with it, it's just a mechanism for assembling dependent components into a working whole.
Your example is not a very representative one either, as both beans you presented are effectively immutable. There's no shared state that could be potentially reused by concurrent requests. To illustrate that really Spring doesn't care about thread safety for you, you could try the following code:
#Service
public class FooService {
// note: foo is a shared instance variable
private int foo;
public int getFoo() {
return foo;
}
public void setFoo(int foo) {
this.foo = foo;
}
}
#RestController
public class FooController {
#Autowired
private FooService fooService;
#RequestMapping(value = "/test")
public String test() {
int randomNumber = makeSomeRandomNumber();
fooService.setFoo(randomNumber);
int retrievedNumber = fooService.getFoo();
if (randomNumber != retrievedNumber) {
return "Error! Foo that was retrieved was not the same as the one that was set";
}
return "OK";
}
}
If you stress-test this endpoint you're guaranteed to get the error message sooner or later - Spring will do nothing to prevent you from shooting yourself in the foot.
Basically, what happen is that HTTP Request work in pairs, for every request there is a response basic explanation about http.
About the two request at the same time this also may help
The spring bean (HelloWorld) is singleton by default look here, so this exactly code will return the same result

#Async annotated method doesn't operate Asynchronously when called by controller

I'm toying with asynchronous calls to reduce some waiting time on client side when calling a page.
As a experiement i have a controller that calls a pojo with a method annotated with #Async. In that menthod i sleep for 10000 ms to simulation operation to test whether my theory works, and it seems not to. Code below and further information can be found after it:
Test Controller
#Conroller
public class TestController {
#RequestMapping("/test")
public String testAsyncCall() {
new AsyncTestClass().asyncOpOne();
return "secondpage";
}
}
Asynchronous Class containing the #Async annotated method
public class AsyncTestClass {
#Async
public void asyncOpOne() {
try {
Thread.sleep(10000);
System.out.println("done working");
} catch (InterruptedException e) {
//
}
}
}
Now from my understanding when the client makes the call to "/test" in their browser the controller should return call the asynchronous method and instantly return "secondpage" to be rendered.
What is happening is that the controller doesn't return the second page until the 10000 ms in the asynchronous call has finished, only then it returns the secondpage.
FYI
#EnableAsync is added to one of my config files (using Java Configuration).
What am i doing wrong here that is causing the controller to wait for the async to finish its sleep before continuing?
Spring uses AOP to apply #Async behavior to your beans (the same goes for #Transactional for instance).
Spring will only apply AOP to beans it knows, as you are constructing a new instance outside of the scope of Spring the #Async does nothing. Simply add it as a bean to your configuration and inject it into your controller.
#Bean
public AsyncTestClass asyncTestClass() {
return new AsyncTestClass():
}
Then in your calling class.
#Conroller
public class TestController {
#Autowired
private AsyncTestClass asyncTestClass;
#RequestMapping("/test")
public String testAsyncCall() {
asyncTestClass.asyncOpOne();
return "secondpage";
}
}

Check the state validity of a Spring proxied bean without try-catch

I have a bean being created by a service with the following class:
#Configuration
public class AccessManager {
#Bean(name="access", destroyMethod="destroy")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#Autowired
public Access create(HttpServletRequest request) {
System.out.println(request.getRemoteAddr());
return new Access();
}
}
Everything works as expected, except that when the application is starting, this method is being called, probably because I have some other singleton beans that use the Access bean. At the start up there is no request bound to the Thread, and it's expected to get a java.lang.IllegalStateException when trying to access any property of the request parameter.
No problem. The question is, is it possible to check if the underlying HttpServletRequest of the proxy request is null before calling a property that raises the exception?
You probably want to take a look at RequestContextHolder#getRequestAttributes(). That will return null if you're not currently in a context where request scope could be used.
#Configuration
public class AccessManager {
#Bean(name="access", destroyMethod="destroy")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#Autowired
public Access create(HttpServletRequest request) {
if (RequestContextHolder.getRequestAttributes() != null) {
System.out.println(request.getRemoteAddr());
}
return new Access();
}
}
I think the issue here is with separation of concerns. Usually your service layer should not have any dependency on the servlet classes. This is very much a controller/UI concern.
Your service class should be provided with the properties which it needs to do its job. In this case a String. This service method should be called from a controller method which is injected with the servlet request.
Something like the following:
#Controller
public class MyController {
#Autowired
private AccessManager accessManager;
#RequestMapping
public void handleRequest(HttpServletRequest request) {
accessManager.create(request.getRemoteAddr());
}
}
and your service would then look like this:
#Service
public class AccessManager {
public Access create(String remoteAddress) {
return new Access();
}
}
To sum up, anything annotated as #Service shouldn't have access to the Request.

Resources