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 件のコメント:
コメントを投稿