Guice的なAOP
まずはソースを見る方がわかりやすい。SetterInterceptorというclassがInterceptor。 Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
// 対象は以下。
// ClassMather: AbstractModel classのSubClass
// MethodMatche: "set"で始まるMethod
bindInterceptor(Matchers.subclassesOf(AbstractModel.class), new AbstractMatcher<Method>() {
public boolean matches(Method t) {
return t.getName().startsWith("set");
}
}, new SetterInterceptor());
}
});
ポイントはまず以下のメソッド。
- bindInterceptor(
Matcher<? super Class<?>> classMatcher, Matcher<? super Method> methodMatcher, MethodInterceptor... interceptors)
- Matcher
今回は自分で作ったMatcherも使用しているが、Matchersには以下のようなものも定義されている。
- 特定のPackage内のものを指定するinPackage()
- 特定のannotationで修飾されたものを指定するannotatedWith()
- 引数に指定したインスタンスとequals()が成立したインスタンスのみ反応するonly()。
- 上記のonly()は「equals()」での判断だが、「==」で判断するのがidenticalTo()。
- 返り値の型を指定するreturns()
この程度でAOPできるが、試したソースをこの下に書いておく。
PropertyChangeEventを発火するInterceptorの実装
Guiceでは、AOP Allianceに準拠したInterceptorが使える。Guice専用のInterceptorもある(com.google.inject.cglib.proxy.MethodInterceptor)。ここではAOP Allianceに準拠した実装を行ってみる。import java.beans.PropertyChangeEvent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class SetterInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
String methodName = invocation.getMethod().getName();
// setter method名からfield名を作成する。
String fieldName = methodName.replaceFirst("set", "");
fieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
Class<?> declaringClass = method.getDeclaringClass();
// java.lang.Fieldを取得する。
Field field = declaringClass.getDeclaredField(fieldName);
if (field.getModifiers() != Field.PUBLIC) {
field.setAccessible(true);
}
// setterの実行と、その前後の値を取得する。
Object oldValue = field.get(invocation.getThis());
Object result = invocation.proceed();
Object newValue = field.get(invocation.getThis());
try {
// "firePropertyChange"固定のmethod名でEventの発火を実行する。
Method fireMethod = declaringClass.getMethod("firePropertyChange", PropertyChangeEvent.class);
fireMethod.invoke(invocation.getThis(), new PropertyChangeEvent(invocation.getThis(), field.getName(),
oldValue, newValue));
} catch (NoSuchMethodException ex) {
// do nothing.
}
return result;
}
}
PropertyChangeEventを発するModelClass
もちろん、setter内ではPropertyEventのfireはしない。package model; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; public abstract class AbstractModel { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } List<PropertyChangeListener> listeners = new ArrayList<PropertyChangeListener>(); public void addListener(PropertyChangeListener l) { listeners.add(l); } public void removeListener(PropertyChangeListener l) { listeners.remove(l); } public void firePropertyChange(PropertyChangeEvent event) { for (PropertyChangeListener l : listeners) { l.propertyChange(event); } } }package model; public class Column extends AbstractModel { public String type; public Column() { } public String getType() { return type; } public void setType(String type) { this.type = type; } }package model; import java.util.ArrayList; import java.util.List; public class Table extends AbstractModel { private List<Column> columns = new ArrayList<Column>(); private String propertyString; public Table() { } public List<Column> getColumns() { return columns; } public void setColumns(List<Column> columns) { this.columns = columns; } public String getPropertyString() { return propertyString; } public void setPropertyString(String propertyString) { this.propertyString = propertyString; } }
0 件のコメント:
コメントを投稿