Method parameter annotations access - java

It took me a while to figure out that I was not making a mistake in annotating my method parameters.
But I am still not sure why, in the following code example, the way no. 1 does not work:
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
public class AnnotationTest {
#Retention(RetentionPolicy.RUNTIME)
#interface MyAnnotation {
String name() default "";
}
public void myMethod(#MyAnnotation(name = "test") String st) {
}
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Class<AnnotationTest> clazz = AnnotationTest.class;
Method method = clazz.getMethod("myMethod", String.class);
/* Way no. 1 does not work*/
Class<?> c1 = method.getParameterTypes()[0];
MyAnnotation myAnnotation = c1.getAnnotation(MyAnnotation.class);
System.out.println("1) " + method.getName() + ":" + myAnnotation);
/* Way no. 2 works */
Annotation[][] paramAnnotations = method.getParameterAnnotations();
System.out.println("2) " + method.getName() + ":" + paramAnnotations[0][0]);
}
}
Output:
1) myMethod:null
2) myMethod:#AnnotationTest$MyAnnotation(name=test)
Is it just a flaw in the annotation imnplementation in Java?
Or is there a logical reason why the class array returned by Method.getParameterTypes() does not hold the parameter annotations?

This is not a flaw in the implementation.
A call to Method#getParameterTypes() returns an array of the parameters' types, meaning their classes. When you get the annotation of that class, you are getting the annotation of String rather than of the method parameter itself, and String has no annotations (view source).

Related

Why do the following code prints the stack trace and why is a.toString() in the return statement not giving an error or exception?

Following is the complete code -
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
class A2<T> {
T ob;
A2(T ob) {
this.ob = ob;
}
#Annos(str="T example")
T retob() {
return ob;
}
#Override
public String toString() {
Annotation a = null;
try {
Class<?> c = this.getClass();
Method m = c.getMethod("retob");
a = m.getAnnotation(Annos.class);
}catch(NoSuchMethodException NSMe) {
NSMe.printStackTrace();
}
return a==null?"EMPTY":a.toString();
}
public static void main(String args[]) {
System.out.println(new A2<Integer>(2).toString());
}
}
#Retention(RetentionPolicy.RUNTIME)
#interface Annos {
String str();
}
If I have override the toString() method then how can a.toString() in the return statement as well as in the main method work?
Here is the output -
java.lang.NoSuchMethodException: A2.retob()
at java.base/java.lang.Class.getMethod(Class.java:2227)
at A2.toString(A2.java:20)
at A2.main(A2.java:28)
EMPTY
Why is it not able to get the method retob?
The solution is to change
Method m = c.getMethod("retob");
to
Method m = c.getDeclaredMethod("retob");
This is needed because your retob method is not public.
From the documentation of getMethod:
[The class's] declared public instance and static methods as returned by getDeclaredMethods() and filtered to include only public methods that match given name and parameterTypes
This means that getMethod uses getDeclaredMethod but then ignores the method if it's not public. Using getDeclaredMethod directly solves that problem.
About your question regarding the toString:
return a==null?"EMPTY":a.toString(); still works because a is null. That's why "EMPTY" is returned.

Is it possible to know whether an Annotation Method is overriden (boolean values)?

I have tried a lot of things online but nothing seem to work for me. I want to know whether an annotation method has been #Overriden (either with the same value as its default).
Take a look at this example:
public class AnnoTest {
#Anno
private String something;
public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
Field field = AnnoTest.class.getDeclaredField("something");
field.setAccessible(true);
boolean isDefault= field.getAnnotation(Anno.class).annotationType().getDeclaredMethod("include").isDefault();
System.out.println(isDefault); //returns false
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD })
public #interface Anno {
boolean include() default false;
}
}
For some reason it returns false. When i change it to:
#Anno(include = false)
private String something;
It returns false again. Is there a way to know whether the value has been declared in the annotation?
I know i could just compare the default value and its current value, but it will not work for me. I want to know if it has been declared.
With other words I need some kind of magic boolean that does the following:
#Anno
private String something;
return false.
#Anno(include = true)
private String something;
return true.
#Anno(include = false)
private String something;
return true.
The reason of this is that i am wishing to add a method (to my annotation) named "parent". When a parent (a String) has been declared the annotation, this field will inherit the annotation of the field named parent. Take a look at this example:
public class AnnoTest {
#Anno(include = false)
private Something something = new Something();
#Anno(parent = "something")
private Something somethingElse = new Something();
public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
AnnoTest test = new AnnoTest();
Field somethingField = AnnoTest.class.getDeclaredField("something");
somethingField.setAccessible(true);
Field somethingElseField = AnnoTest.class.getDeclaredField("somethingElse");
somethingField.setAccessible(true);
Anno anno = somethingElseField.getAnnotation(Anno.class);
if (anno.parent().equals("something")) {
boolean include = somethingField.getAnnotation(Anno.class).include();
test.somethingElse.isIncluded = include;
}
//If not declared it will return true, which it should be false, because "something" field has it false.
boolean include = somethingElseField.getAnnotation(Anno.class).include();
//if somethingElse has declared "include", dominate the value, else keep it from the parent
test.somethingElse.isIncluded = include;
}
public class Something {
boolean isIncluded;
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD })
public #interface Anno {
boolean include() default false;
String parent() default "";
}
}
The reflection api does not allow to query whether an annotation value has been specified explicitly or merely defaulted.
The usual workaround is to specify a default value that nobody in their right mind would specify explicitly, and check for that value instead. For instance, JPA uses "" for that purpose.
One might try
Boolean value() default null;
but as you rightly point out in the comments, java does not support Boolean annotation values, only boolean ones. You could use an enum with 3 values instead, but that's probably burdensome for your users.
That leaves dark magic: You could parse the classfile yourself. This would work, because the classfile only lists specified annotation attributes, as the following javap output shows:
Given
#Anno(false)
public void foo()
we get
Constant pool:
...
#16 = Utf8 Lstackoverflow/Anno;
#17 = Utf8 value
#18 = Integer 0
public void foo();
descriptor: ()V
flags: ACC_PUBLIC
RuntimeVisibleAnnotations:
0: #16(#17=Z#18)
but given
#Anno()
public void foo() {
we get
Constant pool:
...
#16 = Utf8 Lstackoverflow/Anno;
public void foo();
descriptor: ()V
flags: ACC_PUBLIC
RuntimeVisibleAnnotations:
0: #16()
That said, even if you manage to do this, you might surprise your users and confuse their tooling. For instance, it's quite possible that an IDE will flag explicit assignments of a default value as redundant.
If at all possible, I'd therefore change your annotation so you don't have to distinguish whether a boolean has been specified explicitly.
I know a few years has passed but for reference, there is a less dark magic way to achieve this. If you have access to the .java file, you can use the JavaCompiler api to process the annotations and know whether an Annotation Method is overriden.
Small example:
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.*;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.util.Trees;
import java.util.List;
import java.util.Set;
#Retention(RetentionPolicy.RUNTIME)
#interface Anno {
boolean include() default false;
}
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#SupportedAnnotationTypes("*")
class AnnotationProcessor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element e : roundEnv.getElementsAnnotatedWith(Anno.class)) {
final Trees trees = Trees.instance(processingEnv);
List<? extends AnnotationTree> annotationTrees = ((MethodTree) trees.getTree(e)).getModifiers().getAnnotations();
System.out.printf("%s is annotated with %s%n", e, annotationTrees);
if (annotationTrees.size() > 0 && annotationTrees.get(0).getArguments().size() > 0) {
System.out.println("Using overridden value");
} else {
System.out.println("Using default value");
}
}
return true;
}
}
class Main {
#Anno(include = false)
public void includeFalse() {
}
#Anno(include = true)
public void includeTrue() {
}
#Anno()
public void includeDefault() {
}
public static void main(String[] args) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
File file = new File(System.getProperty("user.dir") + "/src/Annot.java"); // Location of the .java file
Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjectsFromFiles(Collections.singletonList(file));
JavaCompiler.CompilationTask task = compiler.getTask(null,
fileManager,
null,
null,
null,
fileObjects);
task.setProcessors(Collections.singletonList(new AnnotationProcessor()));
task.call();
}
}
I still wouldn't recommend doing this because as mentioned earlier it can cause a lot of confusion for the users. In fact the only reason I know this trick is because it caused a very hard to find bug in our code :)

AspectJ annotation on field that triggers #Before advice

I have already written AspectJ aspects that perform #Around advice triggered by method annotations. Now I want to do the same, but where fields are annotated instead of methods. So with each method invocation of the class below, it must set the accountSummary field to the correct implementation. Is there a way to accomplish this? I presume using #Before advice would be the best way of going about it. Using CDI is not an option - the solution must use AspectJ.
public class PoolableBusinessLogic {
#InjectServiceClientAdapter(legacy=LegacyAccountSummary.class,new=NewAccountSummary.class)
private AccountSummary accountSummary;
public void foo() {
// use correct accountSummary impl, decided in #Before code
}
public void bar() {
// use correct accountSummary impl, decided in #Before code
}
}
I am not sure what exactly you want to achieve, so I am presenting two alternative solutions.
First, let us create some application classes in order to have a fully compileable sample:
package de.scrum_master.app;
public interface AccountSummary {
void doSomething();
}
package de.scrum_master.app;
public class LegacyAccountSummary implements AccountSummary {
#Override
public void doSomething() {
System.out.println("I am " + this);
}
}
package de.scrum_master.app;
public class NewAccountSummary implements AccountSummary {
#Override
public void doSomething() {
System.out.println("I am " + this);
}
}
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface InjectServiceClientAdapter {
Class<?> legacyImpl();
Class<?> newImpl();
}
package de.scrum_master.app;
public class PoolableBusinessLogic {
#InjectServiceClientAdapter(legacyImpl = LegacyAccountSummary.class, newImpl = NewAccountSummary.class)
private AccountSummary accountSummary;
public void foo() {
accountSummary.doSomething();
}
public void bar() {
System.out.println("Account summary is " + accountSummary);
}
}
Now we need an entry point:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
PoolableBusinessLogic businessLogic = new PoolableBusinessLogic();
businessLogic.foo();
businessLogic.bar();
System.out.println();
}
}
}
Obviously this yields an error because the member accountSummary has not been initialised:
Exception in thread "main" java.lang.NullPointerException
at de.scrum_master.app.PoolableBusinessLogic.foo(PoolableBusinessLogic.java:8)
at de.scrum_master.app.Application.main(Application.java:7)
Now we have two options, depending on what you want to achieve:
Option A: dynamic injection
Scenario: For each field access (even in the same PoolableBusinessLogic instance) decide dynamically what type of object instance to return. Here in this example I will just be randomising in order to simulate another if-else criterion.
BTW, I hope it is okay that I use the more expressive native AspectJ syntax. You can easily convert the aspect to annotation style.
package de.scrum_master.aspect;
import java.util.Random;
import org.aspectj.lang.SoftException;
import de.scrum_master.app.InjectServiceClientAdapter;
public aspect DynamicInjectionAspect {
private static final Random RANDOM = new Random();
Object around(InjectServiceClientAdapter adapterAnn) :
get(* *) && #annotation(adapterAnn)
{
try {
Class<?> implClass = RANDOM.nextBoolean() ? adapterAnn.legacyImpl() : adapterAnn.newImpl();
return implClass.newInstance();
} catch (Exception e) {
throw new SoftException(e);
}
}
}
This yields the following output:
I am de.scrum_master.app.LegacyAccountSummary#4d9cfefb
Account summary is de.scrum_master.app.NewAccountSummary#7e28388b
I am de.scrum_master.app.NewAccountSummary#2986e62
Account summary is de.scrum_master.app.LegacyAccountSummary#6576e542
I am de.scrum_master.app.NewAccountSummary#60c58418
Account summary is de.scrum_master.app.LegacyAccountSummary#4763754a
I am de.scrum_master.app.NewAccountSummary#52a971e3
Account summary is de.scrum_master.app.NewAccountSummary#7274187a
I am de.scrum_master.app.LegacyAccountSummary#23f32c4a
Account summary is de.scrum_master.app.LegacyAccountSummary#31e0c0b6
As you can see, within each of the five output groups (i.e. for each PoolableBusinessLogic instance) there are different account summary object IDs and sometimes (not always) even different class names.
Option B: static injection
Scenario: Per PoolableBusinessLogic instance decide dynamically what type of object instance to statically assign to the annotated member if its value is null. After that, do not overwrite the member anymore but return the previously initialised value. Again I will just be randomising in order to simulate another if-else criterion.
Attention: Do not forget to deactivate the first aspect, e.g. by prepending if(false) && to its pointcut. Otherwise the two aspects will be conflicting with each other.
package de.scrum_master.aspect;
import java.lang.reflect.Field;
import java.util.Random;
import org.aspectj.lang.SoftException;
import de.scrum_master.app.InjectServiceClientAdapter;
public aspect StaticInjectionAspect {
private static final Random RANDOM = new Random();
before(InjectServiceClientAdapter adapterAnn, Object targetObj) :
get(* *) && #annotation(adapterAnn) && target(targetObj)
{
try {
Field field = targetObj.getClass().getDeclaredField(thisJoinPoint.getSignature().getName());
field.setAccessible(true);
if (field.get(targetObj) != null)
return;
Class<?> implClass = RANDOM.nextBoolean() ? adapterAnn.legacyImpl() : adapterAnn.newImpl();
field.set(targetObj,implClass.newInstance());
} catch (Exception e) {
throw new SoftException(e);
}
}
}
This is a bit uglier because it involves using reflection for finding the member field. Because it might be (and in our example really is) private we need to make it accessible before doing anything with it.
This yields the following output:
I am de.scrum_master.app.NewAccountSummary#20d1fa4
Account summary is de.scrum_master.app.NewAccountSummary#20d1fa4
I am de.scrum_master.app.NewAccountSummary#2b984909
Account summary is de.scrum_master.app.NewAccountSummary#2b984909
I am de.scrum_master.app.LegacyAccountSummary#1ae3043b
Account summary is de.scrum_master.app.LegacyAccountSummary#1ae3043b
I am de.scrum_master.app.LegacyAccountSummary#2e2acb47
Account summary is de.scrum_master.app.LegacyAccountSummary#2e2acb47
I am de.scrum_master.app.LegacyAccountSummary#7b87b9fe
Account summary is de.scrum_master.app.LegacyAccountSummary#7b87b9fe
Now the output looks different: Within each of the five output groups (i.e. for each PoolableBusinessLogic instance) both output lines show exactly the same object ID.
For Option A: dynamic injection in kriegaex's answer, the annotation-style aspect will look like this:
#Aspect
public class InjectServiceClientAdapterAspect {
#Pointcut("get(* *) && #annotation(injectAnnotation)")
public void getServiceClientAdapter(InjectServiceClientAdapter injectAnnotation) {
}
#Around("getServiceClientAdapter(injectAnnotation)")
public Object injectServiceClientAdapter(final ProceedingJoinPoint joinPoint, final InjectServiceClientAdapter injectAnnotation) {
// injection code goes here
}

Accessing an annotation's value inside the method to which it is applied to

Question: Is there a way to access an annotation's value inside the method to which it is being applied to?
Example: Say I have an annotation like so:
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyAnnotation {
int value default 0;
}
Next, I have a class C with functions f1/f2 that use this annotation. The value is known up-front during compile time.
public class C {
#MyAnnotation(value = 1)
public void f1() {
// Is there a way to get the 'value=1' here??
g();
}
#MyAnnotation(value = -1)
public void f2() {
g();
}
}
And I have another function g that must print 'positive' or 'negative' or 'zero' based on the annotation's value.
public static void g() {
// Must print 'positive' or 'negative' or 'zero'
}
1) Is there a way for f1/f2 to 'capture' the annotation's value and pass it along to g() as a parameter?
2) My current solution to this problem is to do this:
Have an initialization step in g()'s class to iterate through all of C's functions and pre-construct a hashmap of function name to value (Eg: {'C.f1': 1, 'C.f2': -1}). To do this, I use getClass(), getMethods() and getAnnotations() and filter to have only use MyAnnotation's.
Change g()'s signature to accept class and method name and use that as a key to look in the hashmap. The new call in f1 will be g(this, "f1") <-- I explicitly write "f1" to avoid looking in the stack trace for the method name.
Needless to say, this is very ugly and I'm hoping there's a simpler way to do this. Thanks.
Edit: I am not using (and don't plan to) the Spring Framework for this project.
I'm not sure it's a good design choice to access the annotation information inside the annotated method. Annotations represents meta-information which is not a part of the program. Usually they are accessible by a program runner that creates objects and run methods via reflection.
If you want to pass an attribute or a property to a method you can simply define a parameter which can be set by your "runner" depending on the annotation value.
However, you can find out which class and method you are in using the Thread.currentThread().getStackTrace() method and then access the annotation information.
Here is an example showing both approaches. It shows how to locate the current method in the stack trace and how to run the program via reflection and pass the method parameter.
public class StackTrace {
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public static #interface MyAnnotation {
int value() default 0;
}
public static class C {
#MyAnnotation(value = 1)
public void f1() { g(); }
#MyAnnotation(value = -1)
public void f2() {
g();
}
}
public static class X {
#MyAnnotation(value = 1)
public static void x1(int param) { System.out.println("Param1: " + param);}
#MyAnnotation(value = -1)
public static void x2(int param) { System.out.println("Param2: " + param); }
}
private static void g() {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
StackTraceElement traceElement = stackTraceElements[3];
try {
Class<?> type = Class.forName(traceElement.getClassName());
String methodName = traceElement.getMethodName();
Method method = type.getMethod(methodName);
System.out.println(methodName + ": " +
method.getAnnotation(MyAnnotation.class).value());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void boo() throws InvocationTargetException, IllegalAccessException {
for (Method m : X.class.getDeclaredMethods()) {
MyAnnotation annotation = m.getAnnotation(MyAnnotation.class);
if (annotation != null) {
m.invoke(null, annotation.value());
}
}
}
public static void main(String[] args)
throws InvocationTargetException, IllegalAccessException {
C c = new C();
c.f1();
c.f2();
boo();
}
}
Output:
f1: 1
f2: -1
Param1: 1
Param2: -1

Anyway to create a pointcut to methods of a class' members?

Given a class with a bunch of members, each with their own getter/setter/etc methods, is there a way to design a pointcut that will trigger only on members' methods when contained within the parent class?
For example:
public MyClass{
List myList = new ArrayList<String>();
}
If I want to create a pointcut to advise myList.add(), is there a way to do this? I do not wish to advise all ArrayList.add() calls. Only to Collections.add() that are members of MyClass.
I've tried playing around with within and cflow, but to no avail:
pointcut addPointcut() : cflow( execution( * *.getMyList() ) ) && call( * *.add(..));
but it does not seem to work. I presume that given that that the add() calls are not actually part of the get() control flow, it doesn't seem to trigger properly.
After some more playing around, I've noticed the following solution seems to work:
pointcut addPointcut(): within( MyClass ) && call( * *.add(..) );
Is this the correct implementation?
I've tried to limit the pointcut to only advise calls to add() when passing an #Entity object, but it does not work. Ex:
pointcut addEntityPointcut(): within( MyClass ) && call( * *.add(#javax.persistence.Entity *) );
and yet the addPointcut() works when called with an #Entity as a parameter.
Is the argument type based on the actual calling method, or based on the add() signature?
EDIT
I was too quick to jump to the wrong conclusion. After sleeping, I've come to recognize that my pointcut will not work.
public class FirstClass{
List<String> strings = new ArrayList<>();
// getters and setters
}
public class Execute{
public main(){
FirstClass fc = new FirstClass();
fc.getStrings().add( "This call is advised" ); // <---- Is there any way to advise this add() method?
List<String> l = new ArrayList<>();
l.add( "This call is not advised" ); // <---- this one should not be advised
}
}
I'm looking for a way to advise the add() method called from any class. However, I'm only looking to advise the add() method on the member List contained within FirstClass, even when called from outside FirstClass.
Is the argument type based on the actual calling method, or based on the add() signature?
In AspectJ for the call() pointcut you need to specify method or constructor signatures. The add() method in this case does not have any parameters annotated by #Entity, thus what you are trying to do does not work. This is a workaround using reflection:
Sample annotation:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface Entity {}
Sample entity:
package de.scrum_master.app;
#Entity
public class MyEntity {}
Driver application:
package de.scrum_master.app;
import java.util.ArrayList;
import java.util.List;
public class Application {
List<Object> myList = new ArrayList<>();
public static void main(String[] args) {
Application application = new Application();
application.myList.add("foo");
application.myList.add(new MyEntity());
application.myList.add("bar");
application.myList.add(new MyEntity());
}
}
Aspect:
package de.scrum_master.aspect;
import de.scrum_master.app.Application;
import de.scrum_master.app.Entity;
public aspect EntityAddInterceptor {
pointcut addEntity(Object addedObject) :
within(Application) && call(* *.add(*)) && args(addedObject);
before(Object addedObject) : addEntity(addedObject) {
if (addedObject.getClass().isAnnotationPresent(Entity.class))
System.out.println(thisJoinPointStaticPart + " -> " + addedObject);
}
}
Output:
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#19dc6592
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#54906181
As for the control flow matching variant, I think from the naming perspective it makes sense to assume that getMyList() does not add anything, but just return a list. Probably you rather do something like application.getMyList().add("foo"), and in this case the add() is really outside (after) the control flow of getMyList() because it operates on its result.
If OTOH you have a hypothetical method addToList(Object element) which really calls add() you can use cflow(). Let us modify the code sample:
Modified driver application:
package de.scrum_master.app;
import java.util.ArrayList;
import java.util.List;
public class Application {
List<Object> myList = new ArrayList<>();
public void addToMyList(Object element) { reallyAddToMyList(element); }
private void reallyAddToMyList(Object element) { myList.add(element); }
public static void main(String[] args) {
Application application = new Application();
application.myList.add("foo");
application.myList.add(new MyEntity());
application.addToMyList("bar");
application.addToMyList(new MyEntity());
}
}
Modified aspect:
package de.scrum_master.aspect;
import de.scrum_master.app.Entity;
public aspect EntityAddInterceptor {
pointcut addEntity(Object addedObject) :
cflow(execution(* *.addToMyList(*))) && (call(* *.add(*)) && args(addedObject));
before(Object addedObject) : addEntity(addedObject) {
if (addedObject.getClass().isAnnotationPresent(Entity.class))
System.out.println(thisJoinPointStaticPart + " -> " + addedObject);
}
}
New output:
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#323ba00
As you can see, only one call is logged. It is the one from reallyAddToMyList(), not the one from main().
Update 2014-07-21 - better aspect modification:
Credits for this more elegant solution go to Andy Clement (AspectJ maintainer) who has mentioned it on the AspectJ mailing list. It shows both of my variants from above, but uses && #args(Entity) instead of if (addedObject.getClass().isAnnotationPresent(Entity.class)):
package de.scrum_master.aspect;
import de.scrum_master.app.Application;
import de.scrum_master.app.Entity;
public aspect EntityAddInterceptor {
pointcut addEntity(Object addedObject) :
within(Application) && call(* *.add(*)) && args(addedObject) && #args(Entity);
before(Object addedObject) : addEntity(addedObject) {
System.out.println(thisJoinPointStaticPart + " -> " + addedObject);
}
pointcut addEntitySpecial(Object addedObject) :
cflow(execution(* *.addToMyList(*))) && (call(* *.add(*)) && args(addedObject)) && #args(Entity);
before(Object addedObject) : addEntitySpecial(addedObject) {
System.out.println(thisJoinPointStaticPart + " -> " + addedObject + " [special]");
}
}
The output with both variants active looks like this:
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#229ff6d1
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#1976bf9e
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#1976bf9e [special]

Categories

Resources