I have a field set pointcut, which seems to do as I expect. Its defined as follows
before(Object newval): set(@Serviced private * *.*) && args(newval)
The above is meant to capture: whenever a private field attribute, annotated with @Serviced, is set call my before advice.
Everything seems to work fine, except for the one case in my code that sets a variable matching the above via java reflection ( ie via java.lang.reflect.Field.set(....).
Any idea's how I can catch that "set" also?
Thanks
As you have noticed, the set()
pointcut cannot intercept reflective field changes. But if you control (i.e. can weave aspects into) the code calling the Field.set*(..)
methods, you can work around that issue by also using reflection. Here is a complete, compileable code sample illustrating the solution:
Sample annotation:
package de.scrum_master.app;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Serviced {}
Sample entity class with main method:
package de.scrum_master.app;
public class Person {
@Serviced private int id;
@Serviced private String name;
private String country;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
public void setIdReflective(int id) throws Exception {
Person.class.getDeclaredField("id").setInt(this, id);
}
public void setNameReflective(String name) throws Exception {
Person.class.getDeclaredField("name").set(this, name);
}
public void setCountryReflective(String country) throws Exception {
Person.class.getDeclaredField("country").set(this, country);
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", country=" + country + "]";
}
public static void main(String[] args) throws Exception {
Person person = new Person();
person.setId(11);
person.setName("Tin Man");
person.setCountry("Oz");
System.out.println("Before reflective setters: " + person);
person.setIdReflective(22);
person.setNameReflective("Cowardly Lion");
person.setCountryReflective("The Land of Oz");
System.out.println("After reflective setters: " + person);
}
}
As you can see, only two out of three private fields have the @Serviced
annotation. Setters are called for all three fields twice: once normally and once via reflection.
Aspect intercepting both normal and reflective field changes:
package de.scrum_master.aspect;
import de.scrum_master.app.Serviced;
import java.lang.reflect.Field;
public aspect ServicedFieldChangeInterceptor {
before(Object newValue):
set(@Serviced private * *) && args(newValue)
{
System.out.println(thisJoinPointStaticPart + " -> " + newValue);
}
before(Object newValue, Field field):
call(public void Field.set*(Object, *)) && args(*, newValue) && target(field)
{
if (field.getAnnotation(Serviced.class) == null)
return;
System.out.println(thisJoinPointStaticPart + " -> " + field + ", " + newValue);
}
}
Sample output when running Person.main
:
set(int de.scrum_master.app.Person.id) -> 11
set(String de.scrum_master.app.Person.name) -> Tin Man
Before reflective setters: Person [id=11, name=Tin Man, country=Oz]
call(void java.lang.reflect.Field.setInt(Object, int)) -> private int de.scrum_master.app.Person.id, 22
call(void java.lang.reflect.Field.set(Object, Object)) -> private java.lang.String de.scrum_master.app.Person.name, Cowardly Lion
After reflective setters: Person [id=22, name=Cowardly Lion, country=The Land of Oz]
The output clearly shows that both advice only "do something" (in this case print information to standard output) for fields annotated with @Serviced
, whereas other fields are skipped. While the set()
pointcut applies statically, the reflective one needs to determine if the target field has a matching annotation dynamically.