Spring-Retry cannot locate recovery method when implement an interface - java

I found a problem when doing spring-retry with spring boot. When a class implements an interface, it cannot enter the #recover method after exceeding the maximum number of retries. But when I inject a normal class, I can enter this method.Your prompt help and kind advices would be much appreciated,thanks!
When I do this,I can enter the #Recover method
#Service
public class TestService {
#Retryable(Exception.class)
public String retry(String c) throws Exception{
throw new Exception();
}
#Recover
public String recover(Exception e,String c) throws Exception{
System.out.println("got error");
return null;
}
}
But once the class implement another interface, it's not works
#Service
public class TestService implements TestServiceI{
#Override
#Retryable(Exception.class)
public String retry(String c) throws Exception{
throw new Exception();
}
#Recover
public String recover(Exception e,String c) throws Exception{
System.out.println("got error");
return null;
}
}

Spring-Retry uses AOP to implement the #Retry. When using AOP the default is to use JDK Dynamic Proxies. JDK Dynamic Proxies are interface based.
This means you get a dynamically created class that pretents to be a TestServiceI however it isn't a TestService. The proxy doesn't include the recover method (as it isn't on the interface) and as such Spring Retry cannot detect it.
To fix you need to enable class based proxies for Spring Retry by setting the proxyTargetClass attribute on #EnableRetry to true (see the javadoc).
#EnableRetry(proxyTargetClass=true)

mark proxyTargetClass=true: #EnableRetry(proxyTargetClass = true)
The #Recover method must return the same type as the original method;

Related

Spring boot #Cacheable not working as expected with #Transactional

I am not able to share the actual code because of corporate policies but below is an example of method structures.
So in the example I want to the cache on the method in Class B to be cleared when the exception is thrown in class A.
NB: I can not move the cache to Class A so that is not a feasible solution.
I have tried reading all answers and posts online to get this working but not able to figure it out.
Please help with suggestions. A
I have set the following properties in application.properties
spring.cache.enabled=true
spring.cache.jcache.config=classpath:cache/ehcache.xml
#EnableCaching
#EnableTransactionManagement
Main Class{
#Autowired
CacheManager cacheManager
#PostConstruct
void postConstruct(){
(JCacheCacheManager)cachemanager).setTransactionAware(true);
}
}
#Service
Class A{
#Autowired
B b;
#Transactional
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
//TestCode to test cache clears if exception thrown here
throw new RuntimeException("test");
}
}
#Service
Class B{
#Cacheable("cacheName")
public List<Data> getDataFromSystem(String key){
client call code here
return dataList;
}
}
There should be other ways, but the following could be a valid solution.
The first step will be to define a custom exception in order to be able to handle it later as appropriate. This exception will receive, among others, the name of the cache and the key you want to evict. For example:
public class CauseOfEvictionException extends RuntimeException {
public CauseOfEvictionException(String message, String cacheName, String cacheKey) {
super(message);
this.cacheName = cacheName;
this.cacheKey = cacheKey;
}
// getters and setters omitted for brevity
}
This exception will be raised by your B class, in your example:
#Service
Class A{
#Autowired
B b;
#Transactional
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
// Sorry, because in a certain sense you need to be aware of the cache
// name here. Probably it could be improved
throw new CauseOfEvictionException("test", "cacheName", "key");
}
}
Now, we need a way to handle this kind of exception.
Independently of that way, the idea is that the code responsible for handling the exception will access the configured CacheManager and trigger the cache eviction.
Because you are using Spring Boot, an easy way to deal with it is by extending ResponseEntityExceptionHandler to provide an appropriate #ExceptionHandler. Please, consider read for more information the answer I provided in this related SO question or this great article.
In summary, please, consider for example:
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Autowired
private CacheManager cacheManager;
#ExceptionHandler(CauseOfEvictionException.class)
public ResponseEntity<Object> handleCauseOfEvictionException(
CauseOfEvictionException e) {
this.cacheManager.getCache(e.getCacheName()).evict(e.getCacheKey());
// handle the exception and provide the necessary response as you wish
return ...;
}
}
It is important to realize that when dealing with keys composed by several arguments by default (please, consider read this as well) the actual cache key will be wrapped as an instance of the SimpleKey class that contains all this parameters.
Please, be aware that this default behavior can be customized to a certain extend with SpEL or providing your own cache KeyGenerator. For reference, here is the current implementation of the default one provided by the framework, SimpleKeyGenerator.
Thinking about the problem, a possible solution could be the use of some kind of AOP as well. The idea will be the following.
First, define some kind of helper annotation. This annotation will be of help in determining which methods should be advised. For example:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface EvictCacheOnError {
}
The next step will be defining the aspect that will handle the actual cache eviction process. Assuming you only need to advice Spring managed beans, for simplicity we can use Spring AOP for that. You can use either an #Around or an #AfterThrowing aspect. Consider the following example:
#Aspect
#Component
public class EvictCacheOnErrorAspect {
#Autowired
private CacheManager cacheManager;
#Around("#annotation(your.pkg.EvictCacheOnError)")
public void evictCacheOnError(ProceedingJoinPoint pjp) {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (CauseOfEvictionException e) {
this.cacheManager.getCache(
e.getCacheName()).evict(e.getCacheKey()
);
// rethrow
throw e;
}
}
}
The final step would be annotate the methods in which the behavior should be applied:
#Service
Class A{
#Autowired
B b;
#Transactional
#EvictCacheOnError
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
throw new CauseOfEvictionException("test", "cacheName", "key");
}
}
You may even try generalizing the idea, by providing in the EvictCacheOnError annotation all the necessary information you need to perform the cache eviction:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface EvictCacheOnError {
String cacheName();
int[] cacheKeyArgsIndexes();
}
With the following aspect:
#Aspect
#Component
public class EvictCacheOnErrorAspect {
#Autowired
private CacheManager cacheManager;
#Autowired
private KeyGenerator keyGenerator;
#Around("#annotation(your.pkg.EvictCacheOnError)")
// You can inject the annotation right here if you want to
public void evictCacheOnError(ProceedingJoinPoint pjp) {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (Throwable t) {
// Assuming only is applied on methods
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
// Obtain a reference to the EvictCacheOnError annotation
EvictCacheOnError evictCacheOnError = method.getAnnotation(EvictCacheOnError.class);
// Compute cache key: some safety checks are imperative here,
// please, excuse the simplicity of the implementation
int[] cacheKeyArgsIndexes = evictCacheOnError.cacheKeyArgsIndexes();
Object[] args = pjp.getArgs();
List<Object> cacheKeyArgsList = new ArrayList<>(cacheKeyArgsIndexes.length);
for (int i=0; i < cacheKeyArgsIndexes.length; i++) {
cacheKeyArgsList.add(args[cacheKeyArgsIndexes[i]]);
}
Object[] cacheKeyArgs = new Object[cacheKeyArgsList.size()];
cacheKeyArgsList.toArray(cacheKeyArgs);
Object target = pjp.getTarget();
Object cacheKey = this.keyGenerator.generate(target, method, cacheKeyArgs);
// Perform actual eviction
String cacheName = evictCacheOnError.cacheName();
this.cacheManager.getCache(cacheName).evict(cacheKey);
// rethrow: be careful here if using in it with transactions
// Spring will per default only rollback unchecked exceptions
throw new RuntimeException(t);
}
}
}
This last solution depends on the actual method arguments, which may not be appropriate if the cache key is based on intermediate results obtained within your method body.

Transactional annotation to rollback for every checked exception - with AspectJ [duplicate]

One way to configure Spring to rollback on a non RuntimeExceptions is using #Transactional(rollbackFor=...) annotation on the service classes. The problem with this approach is that we need to define (rollbackFor=...) for almost all the service classes which seems really redundant.
My question: Is there any way to configure a default behaviour for Spring transaction manager to rollback on a non RuntimeException whenever it happens without declaring it on every #Transactional annotation. Something like using #ApplicationException(rollback=true) annotation on an exception class in EJB.
You can't do it for application level with #Transactional , but you can :
variant 1 : extend #Transactional annotation and put it as default value for rollbackfor. But set rollbackFor unchecked exceptions only that you need .With this you can control rollbacks only for case that you sure , and avoid copy past of #Transactional(rollbackFor =MyCheckedException.class)
Like:
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Transactional(rollbackFor=MyCheckedException.class)
public #interface TransactionalWithRollback {
}
And use this annotation instead of standard #Transactional.
variant 2 : you can create extension from AnnotationTransactionAttributeSource and override method determineTransactionAttribute:
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae)
//Determine the transaction attribute for the given method or class.
TransactionAttribute see TransactionAttribute api , there is a method
boolean rollbackOn(Throwable ex) Should we roll back on the given exception?
protected TransactionAttribute determineTransactionAttribute(
AnnotatedElement ae) {
return new DelegatingTransactionAttribute(target) {
#Override
public boolean rollbackOn(Throwable ex) {
return (check is exception type as you need for rollback );
}
};
}
Second approach is not so good as first as you do it really global for transaction manager. Better use custom annotation as you can control it any apply only for methods/classes where you really need it. But if you need it in any case use second variant , it will be your default transnational behavior.
This config solves it:
#Configuration
public class MyProxyTransactionManagementConfiguration extends ProxyTransactionManagementConfiguration {
#Bean
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource() {
#Nullable
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
TransactionAttribute ta = super.determineTransactionAttribute(element);
if (ta == null) {
return null;
} else {
return new DelegatingTransactionAttribute(ta) {
#Override
public boolean rollbackOn(Throwable ex) {
return super.rollbackOn(ex) || ex instanceof Exception;
}
};
}
}
};
}
}
This is a similar approach as this answer, i.e. changing the default globally, but with as minimal change to Spring's config as possible, and still leaving the possibility to customize rollback rules per method as usual (with rollbackFor, noRollbackFor etc.).
This is achieved by simply adding a default RollbackRule for Exception.class. Since the rules have precedence according to the exception class hierarchy (the rule for the most specific exception class applicable wins), the new rule has basically lowest precendence, if no other rules are defined on the annotation.
#Configuration
public class MyTransactionManagementConfiguration {
/**
* Note: This custom config does NOT recognize {#code javax.transaction.Transactional} annotations in contrast to
* the original Spring behaviour. Check the original {#code AnnotationTransactionAttributeSource} source code for an idea how to add that.
*
* #see AnnotationTransactionAttributeSource#AnnotationTransactionAttributeSource(boolean)
*/
#Bean
#Primary
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSourceWithDefaultRollBackForAllExceptions() {
return new AnnotationTransactionAttributeSource(
new SpringTransactionAnnotationParser() {
#Override
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = (RuleBasedTransactionAttribute) super.parseTransactionAnnotation(attributes);
List<RollbackRuleAttribute> rules = new ArrayList<>(rbta.getRollbackRules());
rules.add(new RollbackRuleAttribute(Exception.class));
rbta.setRollbackRules(rules);
return rbta;
}
}
);
}
}

Spring-AOP pointcut not working?

Below is my code snippet:
ServiceImpl.java
#Service
public class ServiceImpl implements Service {
private Response worker(Audit send) throws ArgumentException {
System.out.println("STEP_1");
worker(send.getRequest(), send.getId());
}
private Response worker(Request request, String id) throws ArgumentException {
System.out.println("STEP_2");
try {
//throwing some exception
} catch (Exception e) {
System.out.println("STEP_3");
}
}
}
Now, what I want is whenever NullPointerException is being thrown from method worker(Request request, String id) as shown above I want to perform some specific task. For that I have written an Aspect class which is following:
MyAspect.java
#Aspect
#Component
public class MyAspect{
#Pointcut("com.xyz.myapp.ServiceImpl.worker() && args(request,..)")
private void someOperation(Request request) {}
#Before("someOperation(request)")
public void process(Request request) {
System.out.println("SUCCESS");
}
#AfterThrowing("com.xyz.myapp.ServiceImpl.worker() && args(request,..)")
public void doRecoveryActions() {
System.out.println("EXCEPTION_SUCCESS");
}
}
Current Output:
STEP_1
STEP_2
STEP_3
Desired Output:
STEP_1
STEP_2
STEP_3
SUCCESS
EXCEPTION_SUCCESS
As you can see MyAspect.java is not getting triggered hence NOT printing values.
What can be the reason for this?
Note:
I tried making worker as public classes too but it didn't work.
Also tried changing the name of the methods to eliminate any overloading issue that too didn't work.
Tried various other pointcut expressions all in vain as of now.
In my application there are other aspect classes working absolutely fine.
You made a typical Spring AOP beginner's mistake: You assume that it works for private methods, but as the documentation clearly says, it does not. Spring AOP is based on dynamic proxies, and those only work for public methods when implementing interfaces via JDK proxies and additionally for protected and package-scoped methods when using CGLIB proxies.
You should make the worker() method public if you want to intercept it from an aspect.
P.S.: Full-fledged AspectJ also works for private methods, but to switch to another AOP framework would be overkill here.
Update: You also have other problems in your code:
The first worker method, even if you make it public, does not return anything. The last statement should be return worker(send.getRequest(), send.getId());, not just worker(send.getRequest(), send.getId());.
Your pointcut com.xyz.myapp.ServiceImpl.worker() will never match because it has an empty argument list, but your method has arguments. The args() does not help you here.
The syntax of your pointcut is also wrong because it does not specify a return type for the method, not even *. Furthermore, the method name itself is not enough, it should be enclosed in an actual pointcut type such as execution(). I.e. you want to write something like:
#Pointcut("execution(* com.xyz.myapp.ServiceImpl.worker(..)) && args(request, ..)")
private void someOperation(Request request) {}
To intercept a method that throws an exception you can use this code (works only if methods are public):
#AfterThrowing(pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",throwing="ex")
public void doRecoveryActions(NullPointerException ex) {
// ...
}
Source: Spring AOP

Spring AOP doesn`t work with class comprising #Transactional method

I develop web-app, with a need to store heavy-weight files and use Apache FTP Server for this purpose. When a new user register his account, the folder named as his username must be created on remote server. To establish connection, before UserCreatingServiceImpl.createUser() method will be performed, I use Spring AOP:
#Component
#Aspect
public class RemoteServerConnectionEstablisher {
private static boolean connectionEstablished = false;
#Autowired
private RemoteServerConnector serverConnector;
#Pointcut("execution(* com.storehouse.business.services.impl.UserCreatingServiceImpl.createUser(..)) ||"
+ " execution (* com.storehouse.business.services.impl.ItemCreatingServiceImpl.createItem(..)) ||"
+ "execution (* com.storehouse.business.services.impl.FileDownloadingServiceImpl.downloadFile(..))")
public void pointcut() {
}
#Before("pointcut()")
public void establishConnection(JoinPoint jp) {
if (!connectionEstablished) {
if (serverConnector.connectToRemoteServer()) {
connectionEstablished = true;
}
}
}
#After("pointcut()")
public void disconnect(JoinPoint jp) {
if (connectionEstablished) {
if (serverConnector.disconnect()) {
connectionEstablished = false;
}
}
}
}
Here is the service class with createUser() method:
#Service
public class UserCreatingServiceImpl implements UserCreatingService {
#Autowired
private UserService userService;
#Autowired
private FTPClient ftpClient;
#Override
public boolean createUser(UserDto userDto) {
try {
ftpClient.makeDirectory(userDto.getUsername());
UserMapper userMapper = new UserMapper();
userService.persistUser(userMapper.dtoToEntity(userDto));
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
#Transactional
public void checkIfUsernameExist(String username) {
}
}
Everything had worked fine, until I added #Transactional method to service -class:
#Transactional
public void checkIfUsernameExist(String username) {
}
Now methods of Aspect-class don`t invoke. Could you explain the reason. Thanks in advance for help.
The issue lies in your pointcut expression.
execution(* com.storehouse.business.services.impl.UserCreatingServiceImpl.createUser(..))
You are intercepting the execution of the createUser method on the UserCreatingServiceImpl. This works when you don't add something that creates a proxy for your implementation. As you will be directly calling this method.
However as you have added #Transactional a proxy is created and the method call is now done on the UserCreatingService as that is the interface that is left due do the created proxy. By default spring uses JDK Dynamic proxies, which are interface based.
To solve do one of these things
Rewrite your pointcut to operate on the interface instead of implementing class
Use class based instead of interface based proxies
Use compile or load time weaving
Rewrite pointcut
Use execution(* com.storehouse.business.services.UserCreatingService+.createUser(..)) instead of what you have now. This will use the interface instead of the concrete class.
Use class based proxies
Assuming you use #EnableAspectJAutoProxy add proxyTargetClass=true to it, leading to #EnableAspectJAutoProxy(proxyTargetClass=true). This will create class based proxies and should make the original pointcut work.
Use compile or load time weaving
Instead of using proxies you also could change the way your code is build/loaded. For compile time weaving you would have to modify your build to use the AspectJ compiler to apply the aspects at compilation time, then you don't need the proxies any more.
Or instead of #EnableAspectJAutoProxy you could add #EnableLoadTimeWeaving which, if you use a recent servlet container, would weave the aspects as soon as a class is being loaded.
Both would eliminate the need for proxies (at least for this part) and would make the original pointcuts work.

Spring MVC: bind an exception handler to particular method

Good day!
I have a #Controller. Some of its methods throw the same exception, but I want to handle this exceptions in different way.
Is there a way how to bind an #ExceptionHandler to a particular method?
You need to use AOP tools like CDI Interceptor or AspectJ to achieve this cross-cutting concerns. A Concern is a term that refers to a part of the system divided on the basis of the functionality.
Basically this type of feature is used to handle logging, security and also handling the errors... which are not part of your business logic...
Like if you want to change the logger for application from log4j to sl4j then you need to go through each and every classes where you have used log4j and change it. But if you have used AOP tools then you only need to go the interceptor class and change the implementation. Something like plug and play and very powerful tool.
Here is a code snippet using JavaEE CDI Interceptor
/*
Creating the interceptor binding
*/
#InterceptorBinding
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface BindException {
}
After we have define interceptor binding we need to define interceptor binding implementation
/*
Creating the interceptor implementation
*/
#Interceptor
#BindException
public class ExceptionCDIInterceptor {
#AroundInvoke
public Object methodInterceptor(InvocationContext ctx) throws Exception {
System.out.println("Invoked method " + ctx.getMethod().getName());
try {
return ctx.proceed(); // this line will try to execute your method
// and if the method throw the exception it will be caught
} catch (Exception ex) {
// here you can check for your expected exception
// code for Exception handler
}
}
}
Now we only need to apply interceptor to our method
/*
Some Service class where you want to implement the interceptor
*/
#ApplicationScoped
public class Service {
// adding annotation to thisMethodIsBound method to intercept
#BindException
public String thisMethodIsBound(String uid) {
// codes....
// if this block throw some exception then it will be handled by try catch block
// from ExceptionCDIInterceptor
}
}
You can achieve same feature using AspectJ also.
/*
Creating the Aspect implementation
*/
#Aspect
public class ExceptionAspectInterceptor {
#Around("execution(* com.package.name.SomeService.thisMethodIsBound.*(..))")
public Object methodInterceptor(ProceedingJoinPoint ctx) throws Throwable {
System.out.println("Invoked method " + ctx.getSignature().getName());
try {
return ctx.proceed(); // this line will try to execute your method
// and if the method throw the exception it will be caught
} catch (Exception ex) {
// here you can check for your expected exception
// codes for Exception handler
}
}
}
Now we only need to enable the AspectJ to our application config
/*
Enable the AspectJ in your application
*/
#Configuration
#EnableAspectJAutoProxy
public class AppConfig {
#Bean
public SomeService SomeService() {
return new SomeService();
}
}
/*
Some Service class where you want to implement the Aspect
*/
package com.package.name;
public class SomeService {
public String thisMethodIsBound(String uid) {
// codes....
// if this block throw some exception then it will be handled by try catch block
// from ExceptionAspectInterceptor
}
}
I have code example in my git repo https://github.com/prameshbhattarai/javaee-exceptionBinding by using CDI interceptor.
Just as an option (obviously, it's not ideal): you can wrap the exception into a custom exception in one of your methods and then catch it in an #ExceptionHandler
void boo() throws WrappingException {
try {
} catch (TargetException e) {
throw new WrappingException(e);
}
}
then
#ExceptionHandler(WrappingException.class)
public void handleWrappingException() {
// handle
}
#ExceptionHandler(TargetException.class)
public void handleTargetException() {
// handle
}
Could you please explain why do you need this? I'm asking out of curiosity, because I've never felt like this is required and here is why:
Exception usually represents a very specific "mistake" - something that went wrong in a very specific way.
Basically, exception represents a mistake, not a flow...
There are two "degrees of freedom" that spring can support out of the box:
Exception parameters. Maybe stuff like error code, which can be declared as a data field of exception itself.
Exception inheritance. Example:
If you have in your system a UserDoesNotExistException and you want to be more specific in a case of say, the system that manages the users that are retired in some flows, you can always create a more specific exception:
class UserRetiredException extends UserDoesNotExistException {...}
Obviously, spring can support both the cases: In ExceptionMapper you have the access to the exception anyway so you can do something like:
handleException(SomeExceptionWithErrorCode ex) {
if(ex.getErrorCode() == "A") {
// do this
}
else if(ex.getErrroCode() == "B") {
// do that
}
}
In the second case you just have different exception mappers for the different types of exceptions.
You can also consider #ControllerAdvice annotation to reuse code or something.
I don't think you can specify a specific #ExceptionHandler for a method, but you can bind an #ExceptionHandler method to a specific Exception.
So if you want to handle all DataIntegrityViolationException one way and all other Exceptions in another you should be able to achieve that with something like this:
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleIntegrityViolation() {
// do stuff for integrity violation here
}
#ExceptionHandler(Exception.class)
public void handleEverythingElse() {
// do stuff for everything else here
}
You can derive sub-exceptions from the common exception thrown by other methods according to how you want to handle them.
Say you have declared the parent exception as ParentException. Derive sub classes like ChildAException extends ParentException, ChildBException extends ParentException etc.
Define a #ControllerAdvice class that catches the ParentException and define the specific behaviors in delegate methods.
#ControllerAdvice
public class ParentExceptionHandler {
#ExceptionHandler(ParentException.class)
public ResponseEntity<Object> handleParentException(ParentException pe) {
if (pe instanceof ChildAException) {
return handleChildAException((ChildAException) pe);
} else if (...) {
...
} else {
// handle parent exception
}
}
private ResponseEntity<Object> handleChildAException(ChildAException cae) {
// handle child A exception
}
}
I just got the same issue like you. So I checked the spring source code for this situation. It seems that spring will search in the #Controller class for any method that is annotated with #ExceptionHandler first, if nothing matched then it will continue to search for all class that is annotated with #ControllerAdvice. So you can just use the strategy below:
MyController with a #ExceptionHandler method:
#RestController
public class MyController {
#RequestMapping("/foo")
public String foo() {
throw new IllegalArgumentException();
}
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handle(IllegalArgumentException ex) {
return new ResponseEntity<>("Specific handler", HttpStatus.BAD_REQUEST);
}
}
AnotherController without any method annotated with #ExceptionHandler:
#RestController
public class AnotherController {
#RequestMapping("/bar")
public String bar() {
throw new IllegalArgumentException();
}
}
A global #ControllerAdvice class:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handle(IllegalArgumentException ex) {
return new ResponseEntity<>("Global handler", HttpStatus.BAD_REQUEST);
}
}
Then if you visiting http://ip:port/foo, you will get 400 status code with Specific handler, and 400 status code with Global handler when you visit http://ip:port/bar.
I agree that the inability to map a specific #ExceptionHandler to handle only one specific method in the #RestController should be a very desirable feature.
I tried try{}catch(Exception ex){} and catch no exception. But
exception handler handles it nicely.
Since we are talking about hibernate exceptions, these exceptions are usually thrown at the commit phase of transaction. The problem here is that seems like you have transaction opened right in your controller which is considered as a bad practice.
What you should do is - open transaction in the application layer.
Controller just maps xml/json to incomming RequestDto object.
And then you call the Service to handle the business logic.
The Service(or its method) should be annotated by #Transactional.
#RestController
public class MyController {
#Autowired // but better to use constructor injection
private MyService myService;
public ResponseDto doSomething(RequestDto request) {
try {
myService.doSomething(request);
} catch (DataIntegrityViolationException ex) {
// process exception
}
}
}
#Transactional
class MyService {
public void doSomething() {
// do your processing which uses jpa/hibernate under the hood
}
}
Once you done that, the try catch will start behaving as expected on controller level.
However, I would even go further as DatabaseExeption shouldn't really go that far to controller. The alternative would be to use manual transaction inside of a service and do a try catch there.
Then in the Service layer transform database exception in a more generic exception with all necessary information for controllers to process.
And then you should catch that more generic exception (MyDatabaseAccessException) in the controller and transform as you wish for the sake of a presentation layer.
===
The #ControllerAdvice suggested here is good for a global exception handling across controllers.
The #ExceptionHandler is not suitable for each method unless you wnat to have controller per method. And even after that it can clash with global #ControllerAdvice.
I am not sure why spring doesn't allow #ExceptionHandler at a method level, it would simplify a lot of cases like yours.
My solution is to annotate a method with a marker:
#ExceptionHandler(SomeException.class)
public ResponseEntity<String> handleSomeException(SomeException e, HandlerMethod handlerMethod) {
var marker = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getMethod(), MarkerAnnotation.class);
if (marker != null) return something();
else return somethingElse();
}

Categories

Resources