I need to create an aspect with a pointcut matching a method if:
it is annoted with MyAnnotationForMethod
One of its parameters (can have many) is annotated with #MyAnnotationForParam (but can have other annotations as well).
The aspect class look like this
#Pointcut("execution(#MyAnnotationForMethod * *(..,#aspects.MyAnnotationForParam Object, ..)) && args(obj)")
void myPointcut(JoinPoint thisJoinPoint, Object obj) {
}
#Before("myPointcut(thisJoinPoint , obj)")
public void doStuffOnParam(JoinPoint thisJoinPoint, Object obj) {
LOGGER.info("doStuffOnParam :"+obj);
}
The annoted method
#MyAnnotationForMethod
public string theMethod(String a, #MyAnnotationForParam #OtherAnnotation Object obj, Object b){
LOGGER.info(a+obj+b);
}
With eclipse -> warnings : On the poincut :
Multiple markers at this line
- no match for this type name: MyAnnotationForMethod [Xlint:invalidAbsoluteTypeName]
- no match for this type name: aspects.MyAnnotationForParam On the before : advice defined in xxx.xxx.xxx.xxx.MyAspect has not been applied [Xlint:adviceDidNotMatch]
Using last aspectJ plugin from http://download.eclipse.org/tools/ajdt/35/update
With maven command line using aspectj 1.6.9
[WARNING] no match for this type name: MyAnnotationForMethod [Xlint:invalidAbsoluteTypeName]
[WARNING] no match for this type name: aspects.MyAnnotationForParam [Xlint:invalidAbsoluteTypeName]
[WARNING] advice defined in xxx.xxx.xxx.xxx.MyAspect has not been applied [Xlint:adviceDidNotMatch]
The annotations :
package com.xxx.xxx.annotation;
// standard imports stripped
#Documented
#Target( { FIELD, CONSTRUCTOR, PARAMETER })
#Retention(RUNTIME)
public #interface #MyAnnotationForParam {}
and
package com.xxx.xxx.annotation;
// standard imports stripped
#Target(METHOD)
#Retention(RUNTIME)
#Documented
public #interface MyAnnotationForMethod {}
And of course it doesn' work properly.
Can you tell me what is wrong ?
thx.
Updated:
OK, the best reference I could find is on this page: Annotations, Pointcuts and Advice.
You can match the method, however you won't be able to catch the parameter (just the method and the annotation). So what you will have to do is a combination of pointcut matching and reflection. Something like this:
#Pointcut(
"execution(#com.xxx.xxx.annotation.MyAnnotationForMethod * *(.., #com.xxx.xxx.annotation.MyAnnotationForParam (*), ..))")
public void annotatedMethod(){}
#Before("annotatedMethod()")
public void doStuffOnParam(final JoinPoint jp){
final Signature signature = jp.getSignature();
if(signature instanceof MethodSignature){
final MethodSignature ms = (MethodSignature) signature;
final Method method = ms.getMethod();
final String[] parameterNames = ms.getParameterNames();
final Class<?>[] parameterTypes = ms.getParameterTypes();
final Annotation[][] parameterAnnotations =
method.getParameterAnnotations();
for(int i = 0; i < parameterAnnotations.length; i++){
final Annotation[] annotations = parameterAnnotations[i];
final MyAnnotationForParam paramAnnotation =
getAnnotationByType(annotations, MyAnnotationForParam.class);
if(paramAnnotation != null){
this.processParameter(ms.toShortString(),
parameterNames[i],
parameterTypes[i],
paramAnnotation);
}
}
}
}
/**
* In an array of annotations, find the annotation of the specified type, if any.
* #return the annotation if available, or null
*/
#SuppressWarnings("unchecked")
private static <T extends Annotation> T getAnnotationByType(final Annotation[] annotations,
final Class<T> clazz){
T result = null;
for(final Annotation annotation : annotations){
if(clazz.isAssignableFrom(annotation.getClass())){
result = (T) annotation;
break;
}
}
return result;
}
/**
* Do some processing based on what we found.
* #param signature method signature
* #param paramName parameter name
* #param paramType parameter type
* #param paramAnnotation annotation we found
*/
private void processParameter(final String signature,
final String paramName,
final Class<?> paramType,
final MyAnnotationForParam paramAnnotation){
System.out.println(MessageFormat.format(
"Found parameter ''{0}'' \n of type ''{1}'' \n with annotation ''{2}'' \n in method ''{3}''",
paramName,
paramType,
paramAnnotation,
signature));
}
Here is my test class for the above aspect:
public class TestClass{
#MyAnnotationForMethod
public void simpleTestMethod(#MyAnnotationForParam final String param1){
System.out.println("Method body (simple)");
};
#MyAnnotationForMethod
public void complexTestMethod(final String param1,
#MyAnnotationForParam final Float param2,
#MyAnnotationForParam final Boolean param3){
System.out.println("Method body (complex)");
};
public static void main(final String[] args){
System.out.println("Starting up");
final TestClass testObject = new TestClass();
testObject.simpleTestMethod("Hey");
testObject.complexTestMethod("Hey", 123.4f, false);
System.out.println("Finished");
}
}
and here is the output:
Starting up
Found parameter 'param1'
of type 'class java.lang.String'
with annotation '#com.xxx.xxx.annotation.MyAnnotationForParam()'
in method 'TestClass.simpleTestMethod(..)'
Method body (simple)
Found parameter 'param2'
of type 'class java.lang.Float'
with annotation '#com.xxx.xxx.annotation.MyAnnotationForParam()'
in method 'TestClass.complexTestMethod(..)'
Found parameter 'param3'
of type 'class java.lang.Boolean'
with annotation '#com.xxx.xxx.annotation.MyAnnotationForParam()'
in method 'TestClass.complexTestMethod(..)'
Method body (complex)
Finished
Hint
You will probably want to cache a lot of this, there is no need to parse every parameter of every annotation in every execution. Keep a map of which parameter of which method carries the annotation and process only those parameters.
The ms.getParameterNames() call in the above solution doesnt seem to work when the method is implemented from an interface. I get back nulls.
However, if I enable CGLIB, then it works.
<aop:aspectj-autoproxy proxy-target-class="true"/>
Related
I have the following class:
public final class SomeClass {
#Signature("some info")
public void someMethod() {
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD })
#Inherited
private #interface Signature {
String value();
}
}
This class is not in my source code nor in a dependency, it's compiled from a resource file on-the-fly at runtime. That means, the symbol Signature.class (as well as SomeClass.class) do not exist in my class loader and will never do at compile time, so I can't do the easy:
Signature signature = method.getAnnotation(Signature.class);
signature.value();
I would like to retrieve the value that is assigned to the annotation #Signature at runtime, but I'm being unable to.
All I know is that the method will only have one annotation (the #Signature annotation) and that this annotation will always have one parameter value, but that's all I know.
This is my (raw) attempt:
Class<?> compiledClass = compiler.compile(classSourceCode); //this properly return the class instance
for (Method method : compiledClass.getDeclaredMethods()) {
for (Annotation annotation : method.getAnnotations()) {
Class<? extends Annotation> realType = annotation.annotationType();
for (Method annotationMethod : realType.getDeclaredMethods()) {
System.out.println(annotationMethod.invoke(annotation)); //<-- THIS FAILS
}
}
}
The failure that I get at runtime is:
java.lang.IllegalAccessException: class SomeClass cannot access a member of interface SomeClass$Signature with modifiers "public abstract"
The method that I get is the method value().
How can I do that?
My annotation:
#Target({ElementType.TYPE, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ObjectName {
String name() default "";
String field() default "";
}
Some class with my annotation
#ObjectName("a_")
public class A {
#ObjectName("field_")
String filed;
}
Problem - when i get all my "ObjectName" annotations from class above, how can i get annotation's ElementType value (field, class or method type)?
So i want something like this
public void process(Class<?> clazz) {
Annotation[] annotations = clazz.getAnnotations();
for (Annotation anno : annotations) {
if (anno instanceof ObjectName) {
ObjectName annObjName = (ObjectName) anno;
Target target = anno.getAnnotation(Target.class);
if (target.getType().equals(ElementType.TYPE)
doThat(annObjName.name());
else if (target.getType().equals(ElementType.FIELD)
doThis(annObjName.field());
}
}
}
Can i even do this?
How can i do this or how can i find out if this annotation declared on filed or class?
You can't.
All you can do is look at where you call getAnnotations(), because you seem to incorrectly believe that clazz.getAnnotations() will return all annotations on everything in the class. That is false. When you call clazz.getAnnotations(), you will only get the annotations directly on the class. To get annotations on fields, you must call clazz.getFields(), and then call getAnnotations() on the Field elements. So there's no risk of getting them mixed up as long as you keep those straight.
Consider a UrlValidator method annotation that tests if a given url is valid before calling a method.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface UrlValdator{
String value();
}
This is working fine when routes are static and known ahead of time. For example:
#UrlValidator("http://some.known.url")
public void doSomething();
But this is not very flexible. For example, what if the route was implicit in the doSomething() method signature? Could I somehow access it form the Spring Expression Language, or some other means? For example, this doesn't work but is what I'm shooting for
#UrlValidator("#p1")
public void doSomething(String url)
or
#UrlValidator("#p1.url")
public void doSomething(Request request)
Is it possible to make annotations dynamic this way?
Related
This is the closest I've found, but the thread is old and the accepted answer is quire cumbersome/hard to follow. Is there a minimal working example/updated way to do this?
I'm not entirely sure if that's what you had in mind, but i can suggest using Spring AOP as it can give you a lot of flexibility.
Since you've mentioned in one of the comments that you're already using Spring AOP, I'm going to assume that you've added spring-boot-starter-aop as a dependency and that you've enabled support for handling components marked with #Aspect by annotating one of your config classes with #EnableAspectJAutoProxy
For example, having defined annotations as such:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface EnsureUrlValid {
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface UrlToVerify {
}
I can use them in a sample spring component as follows:
#Component
public class SampleComponent {
private static final Logger logger = LogManager.getLogger(SampleComponent.class);
#EnsureUrlValid
public void fetchData(String url) {
logger.info("Fetching data from " + url);
}
#EnsureUrlValid
public long fetchData(Long id, #UrlToVerify String url) {
logger.info("Fetching data for user#" + id + " from " + url);
// just to show that a method annotated like this can return values too
return 10L;
}
#EnsureUrlValid
public void fetchDataFailedAttempt() {
logger.info("This should not be logged");
}
}
And here's a sample "processor" of the EnsureUrlValid annotation. It looks for the annotated methods, tries to extract the passed-in url and depending on whether the url is valid or not, it proceeds with invoking the method or throws an exception. It's simple but it shows that you have complete control over the methods that you've annotated.
#Aspect
#Component
public class UrlValidator {
#Around(value = "#annotation(EnsureUrlValid)")
public Object checkUrl(ProceedingJoinPoint joinPoint) throws Throwable {
final Optional<String> urlOpt = extractUrl(joinPoint);
if (urlOpt.isPresent()) {
final String url = urlOpt.get();
if (isUrlValid(url)) {
return joinPoint.proceed();
}
}
throw new RuntimeException("The passed-in url either could not be resolved or is not valid");
}
private Optional<String> extractUrl(JoinPoint joinPoint) {
Object[] methodArgs = joinPoint.getArgs();
Object rawUrl = null;
if (methodArgs.length == 1) {
rawUrl = methodArgs[0];
}
else if (methodArgs.length > 1) {
// check which parameter has been marked for validation
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Parameter[] parameters = method.getParameters();
boolean foundMarked = false;
int i = 0;
while (i < parameters.length && !foundMarked) {
final Parameter param = parameters[i];
if (param.getAnnotation(UrlToVerify.class) != null) {
rawUrl = methodArgs[i];
foundMarked = true;
}
i++;
}
}
if (rawUrl instanceof String) { // if rawUrl is null, instanceof returns false
return Optional.of((String) rawUrl);
}
// there could be some kind of logic for handling other types
return Optional.empty();
}
private boolean isUrlValid(String url) {
// the actual validation logic
return true;
}
}
I hope it's somewhat helpful.
Short answer: Yes.
Long answer:
ElementType specifies the target of the annotation, which can be the following: ANNOTATION_TYPE, CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE, and TYPE_PARAMETER. Were are interested in PARAMETER here. Since we want from the compiler the run our code, RetentionPolicy.RUNTIME is fine for the retention type.
Next we have to add #Constraint annotation, which according to the documentation:
Marks an annotation as being a Bean Validation constraint.
This means, Spring will pick up your parameter and validate it in runtime. The last thing we have to do is to implement the validation itself which implies creating a class which implements ConstraintValidator interface.
Putting it all together:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UrlValidatorImplementation.class)
public #interface UrlValidator{
String message() default "Invalid url";
}
Implementation of the UrlValidatorImplementation class:
public class UrlValidatorImplementation implements ConstraintValidator<UrlValidator, String> {
#Override
public void initialize(UrlValidator annotation) {
// initialization, probably not needed
}
#Override
public boolean isValid(String url, ConstraintValidatorContext context) {
// implementation of the url validation
}
}
Usage of the annotation:
public void doSomething(#UrlValidator url) { ... }
I have multiple class with a Qualifier that I created:
#ServiceComponent(restPath = "/trucks")
public class TruckService {
}
#ServiceComponent(restPath = "/cars")
public class CarService {
}
here is the Qualifier (not important for the question)
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({TYPE, FIELD})
public #interface ServiceComponent {
public boolean exposeAsRest() default true;
#Nonbinding public String restPath() default "";
#Nonbinding public String restGetPrefix() default "get,find,all";
#Nonbinding public String restPostPrefix() default "create,new,post";
}
in another class, I inject those instance using javax.enterprise.inject.Instance<>
class SomeConfigurationClasss {
#Inject
#ServiceComponent()
Instance<Object> _restComponents;
#Override
public void iterate() throws Exception {
//iterate
for(Object obj : _restComponents){
somefuncion(obj);
}
//List.of(_restComponents)
//.flatMap(obj -> somefuncion(obj));
}
}
if I execute the "normal" iteration (for...) I get the Object (TruckService or CarService) given as parameter to the somefunction().
but if I use javaslang's List.of(...) I get the Instance itself. Which I think it's the expected behavior
Is there a possibility to use List.of on a Instance that can contain one or multiple bean (depending on the injection binding). (I already try to call iterator(), select() on the Instance)
Instance<T> extends Iterable<T> so you should use List#ofAll(Iterable)
I have a parent class Parent and a child class Child, defined thus:
class Parent {
#MyAnnotation("hello")
void foo() {
// implementation irrelevant
}
}
class Child extends Parent {
#Override
foo() {
// implementation irrelevant
}
}
If I obtain a Method reference to Child::foo, will childFoo.getAnnotation(MyAnnotation.class) give me #MyAnnotation? Or will it be null?
I'm interested more generally in how or whether annotation works with Java inheritance.
Copied verbatim from http://www.eclipse.org/aspectj/doc/released/adk15notebook/annotations.html#annotation-inheritance:
Annotation Inheritance
It is important to understand the rules relating to inheritance of annotations, as these have a bearing on join point matching based on the presence or absence of annotations.
By default annotations are not inherited. Given the following program
#MyAnnotation
class Super {
#Oneway public void foo() {}
}
class Sub extends Super {
public void foo() {}
}
Then Sub does not have the MyAnnotation annotation, and Sub.foo() is not an #Oneway method, despite the fact that it overrides Super.foo() which is.
If an annotation type has the meta-annotation #Inherited then an annotation of that type on a class will cause the annotation to be inherited by sub-classes. So, in the example above, if the MyAnnotation type had the #Inherited attribute, then Sub would have the MyAnnotation annotation.
#Inherited annotations are not inherited when used to annotate anything other than a type. A type that implements one or more interfaces never inherits any annotations from the interfaces it implements.
You found your answer already: there is no provision for method-annotation inheritance in the JDK.
But climbing the super-class chain in search of annotated methods is also easy to implement:
/**
* Climbs the super-class chain to find the first method with the given signature which is
* annotated with the given annotation.
*
* #return A method of the requested signature, applicable to all instances of the given
* class, and annotated with the required annotation
* #throws NoSuchMethodException If no method was found that matches this description
*/
public Method getAnnotatedMethod(Class<? extends Annotation> annotation,
Class c, String methodName, Class... parameterTypes)
throws NoSuchMethodException {
Method method = c.getMethod(methodName, parameterTypes);
if (method.isAnnotationPresent(annotation)) {
return method;
}
return getAnnotatedMethod(annotation, c.getSuperclass(), methodName, parameterTypes);
}
Using Spring Core you can resolve with
AnnotationUtils.java
While the answer to the question as asked is that Java's Method.getAnnotation() does not consider overridden methods, sometimes it is useful to find these annotations. Here is a more complete version of Saintali's answer that I'm currently using:
public static <A extends Annotation> A getInheritedAnnotation(
Class<A> annotationClass, AnnotatedElement element)
{
A annotation = element.getAnnotation(annotationClass);
if (annotation == null && element instanceof Method)
annotation = getOverriddenAnnotation(annotationClass, (Method) element);
return annotation;
}
private static <A extends Annotation> A getOverriddenAnnotation(
Class<A> annotationClass, Method method)
{
final Class<?> methodClass = method.getDeclaringClass();
final String name = method.getName();
final Class<?>[] params = method.getParameterTypes();
// prioritize all superclasses over all interfaces
final Class<?> superclass = methodClass.getSuperclass();
if (superclass != null)
{
final A annotation =
getOverriddenAnnotationFrom(annotationClass, superclass, name, params);
if (annotation != null)
return annotation;
}
// depth-first search over interface hierarchy
for (final Class<?> intf : methodClass.getInterfaces())
{
final A annotation =
getOverriddenAnnotationFrom(annotationClass, intf, name, params);
if (annotation != null)
return annotation;
}
return null;
}
private static <A extends Annotation> A getOverriddenAnnotationFrom(
Class<A> annotationClass, Class<?> searchClass, String name, Class<?>[] params)
{
try
{
final Method method = searchClass.getMethod(name, params);
final A annotation = method.getAnnotation(annotationClass);
if (annotation != null)
return annotation;
return getOverriddenAnnotation(annotationClass, method);
}
catch (final NoSuchMethodException e)
{
return null;
}
}