If an exception should be ignored inside a method call, one would write eg the following:
public void addEntryIfPresent(String key, Dto dto) {
try {
Map<String, Object> row = database.queryForMap(key);
dto.entry.put(row);
} catch (EmptyResultDataAccessException e) {}
}
I'm trying to write eg a custom spring annotation that has the same effect, but could just be applied to the method header. That could look similar to the following:
#IgnoreException(EmptyResultDataAccessException.class) //this annotation does not exist
public void addEntryIfPresent(String key, Dto dto) {
Map<String, Object> row = database.queryForMap(key);
dto.entry.put(row);
}
How could such an annotation be created?
Here is a way of doing it with AspectJ.
First of all, define a method level annotation.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface IgnoreRuntimeException{
}
Then, define an around aspect for the annotation.
#Component
#Aspect
public class ExceptionHandlingAdvice {
#Around("#annotation(com.yourpackage.IgnoreRuntimeException) && execution(* *(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
Object returnObject = null;
// do something before the invocation
try {
// invoke method
returnObject = joinPoint.proceed();
// do something with the returned object
return returnObject;
} catch (Exception e) {
// do something with the exception
}
}
}
You can then apply the annotation on top of your methods to ignore exceptions.
#IgnoreRuntimeException
public void addEntryIfPresent(String key, Dto dto) {
// ...
}
You can also check the parameters of the annotation using the api of ProceedingJoinPoint to ignore only the exceptions that you'd like to ignore.
Related
Is it possible to create a custom annotation that simply tracks invocations of some methods, without having to add a service method call in every method explicit?
#InvocationCounter(path = "/test1") //I'm looking for this
#GetMapping("/person/{id}")
public Person getPerson(Long id) {
//...
}
On every getPerson() call, I want an invocation counter to record the invocation, like:
#Service
public class InvocationCounterService {
Map<String, AtomicInteger> counter;
public void count(String path) {
if (counter.get(path) == null) counter.put(path, new AtomicInteger()));
counter.get(path).incrementAndGet();
}
#Scheduled(fixedRate = 60000)
public void persist() {
//optionally process or persist the invocations
}
}
Question: how could I possibly tell Spring to invoke the count() service method on each annotated controller method?
The annotation InvocationCounter:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface InvocationCounter {
String path();
}
The Aspect InvocationCounterAspect:
#Aspect
#Component
public class InvocationCounterAspect {
#Autowired
InvocationCounterService invocationCounterService;
#Around("#annotation(InvocationCounter)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
InvocationCounter invocationCounter = signature.getMethod().getAnnotation(InvocationCounter.class);
final String path = invocationCounter.path(); //retrieve path from the annotation
invocationCounterService.count(path); //call the counter service
return joinPoint.proceed(); //proceed executing the annotated method
}
}
You should be looking at micrometer.io, Spring boot application natively supports metrics collection, including simple counter.
An annotation is a form of syntactic metadata that can be added to Java source code.
So you can add informations to your source with an annotation but you can't add code to be executed.
You can do with aspects as mentioned by pleft in the comments.
Or you can create a countExecutions function that takes your Function as a lambda expression to count the number of executions as follow:
public R countExecutions(Function<R, T> fun, T data) {
// invoke counter
return fun.apply(data);
}
and rewriting your code as
#GetMapping("/person/{id}")
public Person getPerson(Long id) {
return countExecutions( /* code previously in the getPerson as a Function */ , id);
}
I tried to put annotation to method and the retrieve the data inside it, but when I debug the code, I cannot get any annotations present.
This is may DatabaseSeederImpl, simple class that calls different services to seed data into database.
#Override
public void seed() {
seedData(SeedUserDto.class, userService);
seedData(SeedCategoryDto.class, categoryService);
seedData(SeedProductDto.class, productService);
}
private <S> void seedData(Class<?> dtoType, S service) {
Arrays.stream(service.getClass().getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(Seed.class))
.forEach(method -> {
try {
String fileName = method.getDeclaredAnnotation(Seed.class).fileName();
method.invoke(service, gson.fromJson(readJson(fileName), dtoType.arrayType()));
} catch (IllegalAccessException | InvocationTargetException | IOException e) {
e.printStackTrace();
}
});
}
This is my annotation which targets methods and its retention policy is set to runtime.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Seed {
String fileName();
}
And in my services, I set this annotation with proper value.
#Override
#Transactional
#Seed(fileName = "categories.json")
public void seedCategories(SeedCategoryDto[] categoryDtos) {
if (categoryRepository.count() == 0) {
List<Category> categories = Arrays.stream(categoryDtos)
.filter(dataValidator::isValid)
.map(dto -> modelMapper.map(dto, Category.class))
.collect(Collectors.toList());
categoryRepository.saveAll(categories);
}
}
And this is my directory tree.
If you use getMethods rather than getDeclaredMethods it should work. Because getDeclaredMethods returns only the new methods of the class who were not in the superclass or superinterface.
And since your methods is #Override-ing, if you tried to debug, you would have noticed it was not even looping through that method
I have annotation:
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
public #interface Loggable { }
and aspect:
#Aspect
public class AspectLogger {
#Around("#annotation(aspects.Loggable)")
public void aroundLogging(ProceedingJoinPoint joinPoint) {
System.out.println("aroundLogging()");
throw new AuthentificationFailException();
}
}
Also I have interface and class:
public interface IAuthInteractor {
public User authorization(String login, String password);
}
public class AuthInteractor implements IAuthInteractor {
private EntityDAO<User> userDAO;
private ITokenGenerator tokenGenerator;
public AuthInteractor(EntityDAO<User> userDAO,
ITokenGenerator tokenGenerator) {
this.userDAO = userDAO;
this.tokenGenerator = tokenGenerator;
}
#Loggable
public User authorization1(String login, String password) {
return null;
}
#Loggable
public User authorization(String login, String password) {
return null;
}
}
For first method (authorization1) annotation doesn't work. For method authorization (that was described in interafce) annotation works.
Why does it work this way? and how to work without interface?
First of all, the aspect's advice has a void return type, i.e. it will never kick in for methods returning other types such as User in your example. The aspect should not even compile. It does not for me in any case. The AspectJ compiler says:
applying to join point that doesn't return void: method-execution(de.scrum_master.app.User de.scrum_master.app.AuthInteractor.authorization(java.lang.String, java.lang.String))
So, assuming you change your advice to
#Around("#annotation(aspects.Loggable)")
public Object aroundLogging(ProceedingJoinPoint joinPoint) {
System.out.println("aroundLogging()");
throw new AuthentificationFailException();
}
it will compile and also kick in. I tested it locally.
Now let me just quickly change the advice to actually proceed to the original method instead of always throwing an exception so we can test a bit more without catching exceptions all the time. I also want to print the actual joinpoint signature, so we can see what is going on:
#Around("#annotation(aspects.Loggable)")
public Object aroundLogging(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint);
//throw new AuthentificationFailException();
return joinPoint.proceed();
}
If then you add this main method to your interface implementation class:
public static void main(String[] args) {
System.out.println("Interface object");
IAuthInteractor iAuthInteractor = new AuthInteractor(null, null);
iAuthInteractor.authorization("user", "pw");
System.out.println("\nImplementation object");
AuthInteractor authInteractor = new AuthInteractor(null, null);
authInteractor.authorization("user", "pw");
authInteractor.authorization1("user", "pw");
}
The console log should print something like this, assuming you use AspectJ and not just "AOP lite" via Spring AOP which does not support call() joinpoints:
Interface object
execution(User de.scrum_master.app.AuthInteractor.authorization(String, String))
Implementation object
call(User de.scrum_master.app.AuthInteractor.authorization(String, String))
execution(User de.scrum_master.app.AuthInteractor.authorization(String, String))
call(User de.scrum_master.app.AuthInteractor.authorization1(String, String))
execution(User de.scrum_master.app.AuthInteractor.authorization1(String, String))
As you can see, executions are always caught, but calls are not for interface type instances because the interface method is not annotated, only the implementation.
BTW, method annotations are not inherited anyway, so your #Inherited meta annotation for an annotation type with #Target({ElementType.METHOD}) is kinda useless.
I use #ControllerAdvice to handle all my app exceptions :
#ControllerAdvice
public class ExceptionHandlingController {
#ExceptionHandler({UnauthorizedException.class})
public String unauthorizedException() {
.........
}
#ExceptionHandler({UnauthorizedAjaxException.class})
#ResponseBody
public void unauthorizedAjaxException() {
.........
}
#ExceptionHandler({Exception.class})
public String globalException(){
.........
}
}
And somewhere in my code i do throw new UnauthorizedException();
#Around("#annotation(Authenticated)")
public Object profilingAuthentication(ProceedingJoinPoint pjp) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
if( request.getSession().getAttribute("idContact") == null ) {
if( "XMLHttpRequest".equals(request.getHeader("X-Requested-With")) )
throw new UnauthorizedAjaxException();
throw new UnauthorizedException();
}
return pjp.proceed();
}
But sadly Spring MVC appears to be acting random by using the most generic case (Exception) rather than more specific ones (UnauthorizedException for example). And sometimes he choose the correct one !
How the order works works ? and is there any way to specify the order ?
UnauthorizedException is a custom exception
public class UnauthorizedException extends Exception {
public UnauthorizedException(){
super();
}
public UnauthorizedException(String message){
super(message);
}
}
UPDATE
i found out that the order it's not rondom actually the methods who throw UnauthorizedException works normally but the others not !
#Authenticated
#RequestMapping(value="/favoris")
public String favoris(ModelMap model, HttpServletRequest request)
throws UnauthorizedException {
....
}
#Authenticated
#RequestMapping(value="/follow")
public String follow(ModelMap model, HttpServletRequest request) {
.....
}
So i have to add throws UnauthorizedException manually or there is some other solution ?
we are using exception handler in following way and never order get mixed and it work as expected.
So it could be possible if you will use it as following example then it will solve your problems
**********handler class******************
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(value = Exception.class)
public boolean handle1(Exception exc) {
System.out.println("#####Global Exception###" + exc);
exc.printStackTrace(System.out);
return true;
}
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(value = CustomException.class)
public boolean handle2(CustomException exc) {
System.out.println("###custom exception######" + exc);
exc.printStackTrace(System.out);
return true;
}
}
***************Controller class************
#RestController("test")
#RequestMapping("/test1")
public class TestController {
#RequestMapping("/t1")
public boolean test() {
if (true) {
throw new CustomException();
}
return true;
}
}
In above example exception habdler is handle2 because 1st of all it will search for matching exception if not found then go for parrent handler
If we throw new NullPointerException() then it will search for matching handler but not found in this case then go for parrent that is handle1
for more you can refer here
I hope it will help you. Thanks
Use an annotation #Order or implement an interface Ordered for #ControllerAdvice.
See implementations of:
ExceptionHandlerMethodResolver class
ExceptionHandlerExceptionResolver class (method initExceptionHandlerAdviceCache)
AnnotationAwareOrderComparator class
There is no order/priority as long as you have a single controlleradvice class in your project. But if you have multiple controlleradvice classes, you can set the Order. But here, in your case, the order is not applicable as the two exceptions are handled differently (i.e., UnauthorizedException and Exception).
The good thing is, Spring will automatically find the respective custom Exception class (if any,
otherwise generic Exception) and invoke the corresponding method.
Please refer for more information on Spring Controller Advice and Exception handling:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
I have an interface, for example
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
public #interface AutoLogged {
boolean query() default false;
}
How can I get query parameter in interceptor implementation?
#Provider
#AutoLogged
public class AutoLoggedInterceptor implements WriterInterceptor {
#Context
private ResourceInfo resourceInfo;
#Override
public void aroundWriteTo(final WriterInterceptorContext context)
throws IOException, WebApplicationException {
try {
final String methodName = this.resourceInfo.getResourceMethod().getName();
BasicAutoLoggedProducer.makeCall(methodName);
} catch (final Exception e) {
e.printStackTrace(System.err);
} finally {
context.proceed();
}
}
}
I can not find it in context.getPropertyNames. I see annotation AutoLogged with getAnnotations method. How to retrieve parameter query from interface?
You can simply do
AutoLogged annotation = resourceInfo.getResourceMethod().getAnnotation(AutoLogged.class);
if (annotation != null) {
boolean query = annotation.query();
}
"and want to set parameter query"
Not exactly sure what you mean hear, but if you mean you want to set the value at runtime, I'm not really sure the purpose and not really sure how to do it. Hopefully you men "get" instead of "set" :-)