お気づきのように、set()
ポイントカットは反射フィールドの変化を傍受できません。しかし、メソッドを呼び出すコードを制御する (つまり、アスペクトを織り込むことができる)Field.set*(..)
場合は、リフレクションも使用してその問題を回避できます。以下は、ソリューションを示す完全でコンパイル可能なコード サンプルです。
注釈の例:
package de.scrum_master.app;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Serviced {}
main メソッドを含むエンティティ クラスの例:
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);
}
}
ご覧のとおり、3 つのプライベート フィールドのうち 2 つだけが@Serviced
アノテーションを持っています。セッターは 3 つのフィールドすべてに対して 2 回呼び出されます。1 回は通常どおり、もう 1 回はリフレクションによって呼び出されます。
通常フィールドと反射フィールドの両方の変化を遮るアスペクト:
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);
}
}
実行時の出力例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]
出力は、両方のアドバイスが で注釈が付けられたフィールドに対してのみ「何かを行う」(この場合は情報を標準出力に出力する) だけであることを明確に示しています@Serviced
が、他のフィールドはスキップされます。ポイントカットは静的に適用されますが、set()
リフレクティブはターゲット フィールドに一致する注釈があるかどうかを動的に判断する必要があります。