#ControllerAdvice, how to get the class which called this method - java

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();
...
}

Related

How to enforce Aspect implementation is loaded when its corresponding annotation is used?

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

SpringBoot application fails startup when I have Aspect defined on a Bean Method

Working with Springboot 2.7.0. I had a a working application and I made these changes on top of it
Aspect Configuration
#Configuration
#EnableAspectJAutoProxy
#ComponentScan
public class AspectConfig {}
Aspect Interface
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Timed { }
Aspect Class to Measure method execution time
#Around("#annotation(Timed)")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
LOG.info("Time taken for {} is {} ms, joinPoint.getSignature(), System.currentTimeMillis() - start,);
return proceed;
}
Added the new #Timed annotation to an existing method in a bean (omitting non relevant code)
#Component
#ConditionalOnExpression("${oauth.enabled}")
public class JwtAuthFilter extends OncePerRequestFilter {
#Timed
public boolean verifySignatureAndExpiry(String bearerToken){
// method logic
}
}
This causes the Springboot application to fail startup.
I can get it to start if I add #Aspect to the JwtAuthFilter class.
but why would I need to do that? It makes the #Timed annotation limited use if I have to annotate every class that needs to use it with #Aspect. Not to mention, though there are no errors, the functionality won't work because an Aspect cannot work on another Aspect.
#Timed works on my controller method though.
#RestController
#RequestMapping(value = "/api/v1", produces = MediaType.APPLICATION_JSON_VALUE)
public class HealthController {
#GetMapping("/health")
#Timed
public Map<String, String> health(){
return Map.of("status", "up");
}
}
This causes the Spring Boot application to fail startup.
You should always post error messages and relevant stack traces, not just say "fails to start up". You are lucky that in this case, I remember the situation, so I can answer your question. Normally, I would be unable to do so without further information.
I can get it to start if I add #Aspect to the JwtAuthFilter class.
That does not make any sense. Why would you add #Aspect to something which is not an aspect? Of course, it makes the start-up error go away, but it also makes your real aspect not fire, because one Spring AOP aspect cannot advise another one, as you already mentioned. Therefore, this approach is - with all due respect - complete nonsense.
The reason for the exception is: You cannot advise your filter by Spring AOP, because it is derived from GenericFilterBean, which has some final methods. Final methods cannot be overriden, therefore not be proxied either. This has the effect of those methods being called upon the proxy instance directly instead of being delegated to the target object, i.e. if such a method accesses an instance field, it shall find it uninitialised, because the proxy's fields are not meant to be initialised, only the target object's ones. See also my answer here for more info.
In this case, final method org.springframework.web.filter.GenericFilterBean#init is trying to access this.logger, which leads to the NPE which makes Spring Boot's Tomcat fail to start up. This has been reported and briefly explained in this comment in Spring issue #27963, which has been closed as invalid.
#Timed works on my controller method though.
Yes, because your controller does not have a problem with accessing an instance field from a final method.
If you absolutely think that you need to measure your filter method's execution time from an aspect, you can switch from Spring AOP to native AspectJ, either for the whole project via load-time weaving or selectively for some target classes via compile-time weaving. I have tried locally, it works with the right pointcut. Then you can also advise your filter. FYI, the pointcut would be something like:
// Annotated class
#Around("execution(* *(..)) && !within(MyAspect) && #target(Timed)")
// Annotated method
#Around("execution(* *(..)) && !within(MyAspect) && #annotation(Timed)")
AspectJ is more powerful than Spring AOP, so you explicitly need to limit matching to method executions, otherwise other joinpoints such as method calls, constructor calls and others would be affected, too. You also need to make sure that the aspect does not advise itself or other aspects, which is perfectly possible in AspectJ.

SPRING BOOT - Handle HttpClientErrorException using #ControllerAdvice

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 !

Using #Retryable in methods define in spring bean's base class are not retried

I have a spring managed bean of type B. I have #EnableREtry in a #Configuration class. When I use #Retryable on doStuff(), the method gets retried on failure as expected.
But, the method I really want to retry is a method defined in the base class, A. A is a concrete class and not a spring managed bean. the doSomethingElse method doesn't get retried on throwing an exception.
I really want doSomethingElse to be retried, the base class method. However, I'm not sure how to do this. I'm guessing it's because A is a concrete class and not a bean, although it does serve as a base class.
Do I need to use a RetryableTemplate in class A?
public class B extends A {
public void doStuff() {
super.doSomethingElse();
}
}
public class A {
// doesn't actually retry
#Retryable
public void doSomething() {
throws new Exception();
}
}
#Retryable is implemented using Spring AOP.
Only external calls to retryable methods go through the proxy (which invokes the method within a RetryTemplate); internal calls within the class bypass the proxy and therefore are not retried.
You can play some tricks to get a reference to the proxy from the application context and call that, or simply use a RetryTemplate directly within your doStuff() method.

Spring annotations: Is there a similar annotation like #ExceptionHandler used in Controller stereotypes for #Repository or #Service stereotypes?

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
}
}

Categories