Cannot get Annotation with Reflection Java - java

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

Related

How to wrap a method with try-catch by annotation?

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.

Convert automatically into a centralized bean for multiple domain objects

I am creating a project which will respond to collect multiple bean object, save it to the database and return the status of the transaction. There can be multiple objects that can be sent from the client. For each object, they are having separate database thus separate controller.
So I planned to create a framework that can accept multiple objects from multiple controllers and send only one centralized object. But I am not sure how to use a centralized object as a return type in the controller(currently I returned them as Object). Below is my code:
Controller:
#RestController
#RequestMapping("/stat/player")
public class PlayerController {
#Autowired
private StatService<PlayerValue> statPlayer;
#RequestMapping("/number/{number}")
public Object findByNumber(#PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Service:
#Service
#Transactional(isolation = Isolation.READ_COMMITTED)
public class PlayerServiceImpl implements StatService<PlayerValue> {
#Autowired
private PlayerRepository repository;
#Override
public PlayerValue findByNumber(String number) {
Optional<PlayerEntity> numberValue = repository.findByNumber(number);
return numberValue.map(PlayerEntity::toValue).orElse(null);
}
}
In service I returned the PlayerValue object but I want to wrap this object into a centralized bean ResponseValue. I created an aspect for that
#Aspect
#Component
public class Converter {
private static final Logger LOG = LoggerFactory.getLogger(Converter.class);
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void restControllerClassMethod() {}
private <T> ResponseValue<T> convert(List<T> results) {
String message = results.isEmpty() ? "No result found" : ResponseValueStatus.OK.toString();
return new ResponseValue<>(ResponseValueStatus.OK, message, results);
}
#Around("restControllerClassMethod()")
#SuppressWarnings("unchecked")
public <T> ResponseValue<T> convert(ProceedingJoinPoint joinPoint) {
ResponseValue value;
try {
Object findObject = joinPoint.proceed();
List<Object> objects = toList(findObject);
value = convert(objects);
} catch (NullPointerException e) {
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString()));
//this exception will go in a controller advice and create a response value with this message
} catch (Throwable e) {
LOG.error("Exception occurred while converting the object", e);
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s with exception message %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString(), e.getMessage()));
}
return value;
}
private List<Object> toList(Object findObject) {
List<Object> objects = new ArrayList<>();
if (findObject instanceof List) {
((List) findObject).forEach(item -> objects.add(findObject));
} else {
objects.add(findObject);
}
return objects;
}
}
To sum up, There could be multiple entity similar to PlayerValue. I need a way to return the result in a centralized bean. Above process work, BUT for this I have to give return type as Object in Controller. Does anybody has an idea how can I use return type as List or T in controller. Also I know it can be done by implementing a ValueConverter interface, but this conversion is straightforward. So it would be nice if any other developer don't have to implement the ValueConverter everytime he want to add a different controller.
Also feel free to review the implementation and let me know if anyone has some alternative idea or some comments.
Note: I reduce a lot of code in the question so that it can be easier to understandable without understanding the actual requirement context. Please do let me know if anyone need more info.
After some research I came across to a better design solution for the framework (but of course with flaws) to achieve conversion to a centralized bean for multiple domain objects is to use a marker interface.
Marker interface can provide a centralized type for all the bean. The main rule need to be followed by the client is to implement that marker interface. So the basic solution is
Marker interface:
public interface StateResponseValue<T> {
}
Implement the interface in all the bean.
public class PlayerValue implements StateResponseValue<PlayerValue> {
}
public class ResponseValue<T> implements StateResponseValue<T> {
//fields and their getter and setter
}
Change the return type in service and controller.
public interface StatService<T> {
StateResponseValue<T> findByNumber(String number);
}
Change the return type in controller
#RestController
#RequestMapping("/stat/player")
public class PlayerController {
#Autowired
private StatService<PlayerValue> statPlayer;
#RequestMapping("/number/{number}")
public StateResponseValue<T> findByNumber(#PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Note: The main drawback I feel is that whenever we want to access the field client need to explicitly cast the object to ResponseValue which is still pretty ugly.
What if you create an AbstractStatController which is generic ?
Generic interface StatService:
public interface StatService<T> {
T findByNumber(String number);
}
Generic abstract class AbstractStatController:
public abstract class AbstractStatController<T> {
abstract StatService<T> getStatService();
#RequestMapping("/number/{number}")
public T findByNumber(#PathVariable String number) {
return getStatService().findByNumber(number);
}
}
Concrete class PlayerController:
#RestController
#RequestMapping("/stat/player")
public class PlayerController extends AbstractStatController<Player> {
private final PlayerService playerService;
public PlayerController(PlayerService playerService) {
this.playerService = playerService;
}
#Override
StatService<Player> getStatService() {
return playerService;
}
}

Annotation works only for methods from interface

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.

How to use reflection to identify annotated methods?

We are trying to use reflection to extract the exact mapped values to our methods. The code structure in the target module consists of multiple classes as follows -
#Controller
#RequestMapping(value = "/questions")
public class XYZController {
#RequestMapping(value = "/ask")
public boolean myMethod1() {..}
#RequestMapping(value = "/{questionNo.}/{questionTitle}")
public MyReturnObject myMethod2(){..}
}
What we are trying to grep here is the list of endpoints like /questions/ask and /questions/{questionNo.}/{questionTitle} for which the code we tried to execute filters all such classes based on the #Controller annotation and at the same time we are able to get the list of all the endpoints separately. The code we have tried so far is -
public class ReflectApi {
public static void main(String[] args) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
final List<String> filteredClasses = new ArrayList<>();
scanner.addIncludeFilter(new AnnotationTypeFilter(Controller.class));
Set<BeanDefinition> filteredPackage = scanner.findCandidateComponents("com.package.test");
// gives me a list of all the filtered classes
filteredPackage.stream().forEach(beanDefinition -> filteredClasses.add(beanDefinition.getBeanClassName()));
// call to get the annotation details
filteredClasses.stream().forEach(filteredClass -> getEndPoints(filteredClass));
}
public static void getEndPoints(String controllerClassName) {
try {
Class clazz = Class.forName(controllerClassName);
Annotation classAnnotation = clazz.getDeclaredAnnotation(RequestMapping.class);
if (classAnnotation != null) {
RequestMapping mappedValue = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
System.out.println("Controller Mapped -> " + mappedValue.value()[0]); //This gives me the value for class like "/questions"
runAllAnnotatedWith(RequestMapping.class);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// followed from a SO reference
public static void runAllAnnotatedWith(Class<? extends Annotation> annotation) {
Reflections reflections = new Reflections(new ConfigurationBuilder().setUrls(ClasspathHelper.forJavaClassPath())
.setScanners(new MethodAnnotationsScanner()));
Set<Method> methods = reflections.getMethodsAnnotatedWith(annotation);
methods.stream().forEach(m -> {
if (m != null) {
RequestMapping mappedValue = m.getAnnotation(RequestMapping.class); //this is listing out all the #RequestMapping annotations like "/questions" , "/ask" etc.
System.out.println(mappedValue.value()[0]);
}
});
}
}
But the missing part is the concatenation of the class to method RequestMapping value here.
How do we loop inside a class to search annotations only from its methods?
Or is there any simpler way of doing this from what we are using?
References used - Scanning Java annotations at runtime && How to run all methods with a given annotation?
Aren't endpoints methods in the end?
So, I think what you need to implement is:
=> Retrieve all declared methods of each and any class that has #RequestMapping and #Controller annotations.
=> Iterate the annotations on each of those Method objects to check if they contain #RequestMapping
OR
=> Iterate through all the Methods for each class in Step 1 and find the annotated one's using method.getDeclaredAnnotation(RequestMapping.class) and skip Step 3
=> Remember that combination of class and method for your mapping
=> Do some error handling, in case you don't find any annotated method in there.

How to get #interface parameter in jersey WriterInterceptor Implementation

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" :-)

Categories

Resources