JavaBeansクラスのBeans Binding対応自動化
Beans Binding(JSR 295)でJavaBeansを監視できる条件は、クラスがPropertyChangeEventをサポートしていることです。つまり
public class MyModel { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
みたいな単純なPOJOは監視対象にすることはできず、
public class MyModel { private String name; private PropertyChangeSupport pcs = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener l) { pcs.addPropertyChangeListener(l); } public void removePropertyChangeListener(PropertyChangeListener l) { pcs.removePropertyChangeListener(l); } public String getName() { return name; } public void setName(String name) { String oldName = this.name; this.name = name; pcs.firePropertyChange("name", oldName, this.name); } }
と書き換える必要があります。こんなこといちいちやってられないので、自動化しました。
- addPropertyChangeListener()とremovePropertyChangeListener()、firePropertyChange()の各メソッドを追加するInterType
- 各プロパティのsetterにfirePropertyChange()の呼び出しを挿入するInterceptor
の2つを作って、これでエンハンスしたBeansをS2Containerから取得するようにしました。以下、そのソースの本体部分です。
- PropertyChangeInterType.java
public class PropertyChangeInterType extends AbstractInterType { @Override protected void introduce() throws CannotCompileException, NotFoundException { introducePropertyChangeSupport(); introduceAddPropertyChangeListener(); introduceRemovePropertyChangeListener(); introduceFirePropertyChange(); } private void introducePropertyChangeSupport() { addField(Modifier.PUBLIC, PropertyChangeSupport.class, "pcs", CtField.Initializer.byExpr("new " + PropertyChangeSupport.class.getName() + "(this)")); } private void introduceAddPropertyChangeListener() { addMethod("addPropertyChangeListener", new Class[] { PropertyChangeListener.class }, "{ pcs.addPropertyChangeListener($1); }"); } private void introduceRemovePropertyChangeListener() { addMethod("removePropertyChangeListener", new Class[] { PropertyChangeListener.class }, "{ pcs.removePropertyChangeListener($1); }"); } private void introduceFirePropertyChange() { addMethod("firePropertyChange", new Class[] { String.class, Object.class, Object.class }, "{ pcs.firePropertyChange($1, $2, $3); }"); } }
- PropertyChangeInterceptor.java
public class PropertyChangeInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { String methodName = invocation.getMethod().getName(); if(!methodName.startsWith("set")) { return invocation.proceed(); } if(invocation.getArguments().length != 1) { return invocation.proceed(); } String propertyName = StringUtil.decapitalize(methodName.substring(3)); BeanDesc beanDesc = BeanDescFactory.getBeanDesc(invocation.getThis().getClass()); PropertyDesc propDesc = beanDesc.getPropertyDesc(propertyName); if(!propDesc.isReadable()) { return invocation.proceed(); } Object oldValue = propDesc.getValue(invocation.getThis()); Object result = invocation.proceed(); Object newValue = propDesc.getValue(invocation.getThis()); Method firePropChangeMethod = beanDesc.getMethod("firePropertyChange", new Class[] { String.class, Object.class, Object.class }); MethodUtil.invoke(firePropChangeMethod, invocation.getThis(), new Object[] { propertyName, oldValue, newValue }); return result; } }