I have a Spring Boot Java application. There is a service class that throws a "401 Unauthorized" HttpClientErrorException since the access token used in the application has expired. I want to handle this exception globally for which I have used the #ControllerAdvice annotation.
The error is:
Caused by: org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
The class is:
#Slf4j
#EnableWebMvc
#ControllerAdvice(basePackages = Service.class)
public class HttpClientErrorHandler{
#ExceptionHandler(HttpClientErrorException.class)
#ResponseStatus(HttpStatus.UNAUTHORIZED)
public String errorHandle(HttpClientErrorException e) {
log.error("log HttpClientErrorException: ", e);
return "HttpClientErrorException_message";
}
}
Since the exception was caused in the service class, I have mentioned it specifically in the basePackages. The entire configuration for the program is specified in the application.yml file. I have not used the xml configuration. I don't understand why the #ControllerAdvice annotation is not working. The program still throws the exception. Can someone explain?
#ControllerAdvice(basePackages = Service.class)
The exception is bubbled to #Controller class, and #ControllerAdvice is supposed to apply to controller, so you should set basePackageClasses to your controller package instead of your service package.
By default, #ControllerAdvice is applied to all Controller so you can remove the basePackageClasses unless you want to narrow down the controller advise
I had also faced similar issue,
try adding #Order(Ordered.HIGHEST_PRECEDENCE) below #ControllerAdvice.
We add it to get priority over Spring's default DefaultHandlerExceptionResolver. To understand more about why we add it read this answer.
Also no need to give base packages, it will consider all packages by default.
To handle exception of any other type you can include below existing exception handler for HttpClientErrorException you already have written,
#ExceptionHandler(Exception.class)
public Strring handleAnyExceptions(Exception ex) {
return "your message";
}
Hope it helps !
Related
I have a created an annotation that verifies whether certain security aspects are correct.
For example, #RequireClientCertificate, with an Aspect implementation RequireClientCertificateAspect that verifies whether the correct HTTP header is indeed passed in to the Spring REST controller.
This works totally fine, IF the RequireClientCertificateAspect is actually loaded, i.e. if its package is mentioned somewhere in #ComponentScan().
However, if someone forgets to add this package to #ComponentScan, or the aspect is moved to another package, or someone (accidentally) removes the package from #ComponentScan, the aspect bean isn't loaded, and the aspect is completely not applied.
I have this annotation in a common library, shared by several microservices, so it's easy for one of the microservices to accidentally get it wrong. In that case, no checking of the client certificate would be performed.
Question: How can I enforce that, if the #RequireClientCertificate annotation is used, its corresponding Aspect implementation is also loaded?
Simplified usage example:
#Controller
#RequestMapping(value = "/v1.0", produces = MediaType.APPLICATION_JSON_VALUE)
#RequireClientCertificate
public class SomeApiController {
#ResponseBody
#PostMapping("/get-token/")
public ResponseEntity<Token> getToken() {
return ResponseEntity.ok(...get token...);
}
}
Simplified version of the aspect:
#Aspect
#Component
public class RequireClientCertificateAspect {
#Around("execution(* (#RequireClientCertificate *).*(..))")
public Object requireClientCertificateAspectImplementation(ProceedingJoinPoint joinPoint) throws Throwable {
... verify request header ...
try {
return joinPoint.proceed();
finally {
... some other things I need to check ...
}
}
}
Things I've tried/considered:
I can detect 'usage' of the annotation by adding a static field with an initializer to the interface. For example:
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface RestFactoryGatewaySecurityContext {
static public final boolean dummy = SomeClass.checkAspectIsLoaded();
}
However, such initializers are called very early, and I don't think Spring DI is 'up and running' far enough at that stage that I could even reliably determine whether the aspect bean is loaded.
Another option is to use #Autowired to inject the RequireClientCertificateAspect bean on the main app class explicitly. If somehow the bean isn't on the component scan, this will prevent Spring from instantiating the app.
So that does work, but requires someone to explicitly add this 'dummy' autowire, which in itself is easy to forget, in addition to being a bit 'ugly'.
If you use spring boot you can create your own starter.
Create file META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.MyCustomConfiguration
Then just add any validation you want to your configuration
#Configuration
public class MyCustomConfiguration{
}
You can #Autowired your RequireClientCertificateAspect into it, which will cause error if it isn't defined.
You can create method with #PostConstruct and do any validation you want.
If you went so far as creating custom starter, you can just initialize your bewns there.
More about it you can read here
I am facing an issue with Spring mvc annotation #Controlleradvice.
I have 2 controller classes: UserGapsController and RegistrationBaseController
Both classes use
#Controller
#Controlleradvice
#Autowired session object
#Scope(WebApplicationContext.SCOPE_SESSION)
#Controlleradvice annotation has to be used when #Modelattribute is used at method level. So i am having a method annotated with #Modelattribute in both classes.
Now problem is when i am using #Controlleradvice in just UserGapsController.java , application runs fine , when i use #Controlleradvice in RegistrationBaseController.java also , it breaks down at runtime with following error:
error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.requestmappinghandler: invocation of init method failed:nested exceotion is org.springframework.beans.factory.BeanCreationException :Error creating bean with name 'userGapsController' : Scope 'session' is not active for current thread
What is the reason for this error , can not we have 2 #Controlleradvice annotated classes ? When I comment #Controlleradvice in RegistrationBaseController.java , it executes fine then.
You trying to have multiple #ControllerAdvice classes that handle different exceptions.
You can use Order over controllerAdvice like this
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class RegistrationExceptionHandler {
//...
}
and
#ControllerAdvice
#Order(Ordered.LOWEST_PRECEDENCE) // or any int value
public class UserGapsExceptionHandler {
//...
}
Is there a way to get the class from where the #ControllerAdvice got its control.
i.e. If and execution of PersonController is going on and I get some error due to which the control transferred to the #ControllerAdvice class's method handleException(....). Is there a way to get the PersonController class name inside the handleException method with spring 3.2.3.
Any other way to achieve this?
Thank you for reading.
You can call the getStackTrace against your exception, first entry will give you the originating class
handleException(YourException ex) {
String exceptionController = ex.getStackTrace()[0].getClassName();
...
}
My Application - Java 1.6, Spring 3.1.2, Hibernate 4.1.1, Thymeleaf 2.0.15
Currently in my application, there is no any exception/error handling mechanism is implemented. I handling exceptions in ordinary cultural way. But now I need to introduce a "Robust Error Handling Mechanism". Please suggest me to implement Custom Error/Exception Handling mechanism with Example.
Thanks and appriciate from experts like you.
I'll make as an answer its easier to format. When you say "But I need to implement some Generalized Custom Exception so that same Exception could be thrown acrross the application." Its how I understood it and something that #ControllerAdvice is directly handling, but it applies only to Controllers. Than again, all you lower layers can declare throws on the method, and delegate it to Controller for exception handling. As an example, the following would be the error handling controller handling your custom exception
#ControllerAdvice
public class GlobalErrorHandler {
#ExceptionHandler(value = VermaException.class)
#ResponseBody
public String heightError(VermaException ex) {
return "error";
}
}
Your exception
public class VermaException extends Exception {
}
now whenever the the exception is thrown from the controller class, it will be captured and handled in your GlobalErrorHandler.
Again pasting the reference http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
UPDATE after comment
#ControllerAdvice is added in the version 3.2, for the earlier version you can have a CommonController extended by your controller, containing the error handler methods e.g. per Controller solution
#Controller
public class CommonController {
#ExceptionHandler
public #ResponseBody String handle(VermaException e) {
return "error";
}
}
an extending contorller
#Controller
public class ExceptionController extends CommonController {
#RequestMapping("/exception")
public #ResponseBody String exception() {
throw new VermaException();
}
}
I tried to see if I could use org.springframework.web.bind.annotation.ExceptionHandler in a Repository out of curiosity. As expected the annotation is ignored.
I have a low priority instrumentation service that I don't want exceptions to bubble up from. Rather than coding each method on the instrumentation service with a try/catch I would have liked to have a #ExceptionHandler method for the service - similar to technicques used in a Spring #Controller.
Thoughts?
I do not think that you can get this from Spring right now (perhaps you want to make a feature request...), but you should be able to do this pretty easily with Spring AOP.
#Aspect
public class DaoAspect {
#AfterThrowing(/*any method in a #Repository class that is not annotated with #ExceptionHandler*/ throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
//find method of throwing class that can handle the exception via #ExceptionHandler
}
}