代理bean的setter方法,在执行后触发属性改变事件。对于属性的监听,这里借助JDK自带的PropertyChangeSupport,管理监听器和事件触发。 在不使用动态代理前,手动触发属性改变事件,这样的代码高度冗余,除了属性不一样,剩下的基本都一样
public class SomeBean {
private PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
public void setProperty(String newValue) {
String oldValue = property;
property = newValue;
changeSupport.firePropertyChange("propertyName", oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener l) {
changeSupport.add(l);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
changeSupport.remove(l);
}
}
这里我们可以借助cglib,动态对setter方法进行代理,从而简化属性监听器的写法,减少重复代码。
其中,最关键的是代理类方法拦截的实现。在拦截器中,需要实现:property listener管理,获取属性变成前后数据,property change event触发。
public class BeanInterceptor implements MethodInterceptor {
private PropertyChangeSupport pcs;
/**
* DI
* @param target
*/
public void setPcs(Object target) {
this.pcs = new PropertyChangeSupport(target);
...
拦截器中引用了PropertyChangeSupport去管理listener以及fire change event,关于这两部分将在下文中进行说明。此外PropertyChangeSupport在构造时需要持有事件源(属性改变事件触发者)引用,所以将被代理bean作为依赖传入。
对于setter操作,最好可以记录变更前后的值。由于getter和setter函数名相似,不妨用对函数名进行处理,通过反射调用getter
private Object tryForGetter(String methodName, Object target) {
String getName = "get" + methodName.substring(3);
try {
return target.getClass().getMethod(getName, new Class[]{}).invoke(target, (Object[]) null);
}catch (Exception e){
System.out.println("not such getter method:"+getName);
}
return null;
}
因为不能保证getter方法总是以这种命名方式出现,避免奇怪getter命名影响正常的setter调用,这里对抛出的异常进行捕获.虽然变更前的值丢失了,至少其他大部分功能都是正常的,降低异常影响面。
接下来是intercept方法实现,也是管理listener和property change event触发的具体实现
public static final String ADD = "addPropertyChangeListener";
public static final String REMOVE = "removePropertyChangeListener";
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if(pcs==null){
throw new NullPointerException("PropertyChangeSupport must not null!");
}
Object targetReturn = null;
// deal the call with listener
String methodName = method.getName();
if (ADD.equals(methodName)) {
// add property change listener
Class<?>[] parameterTypes = method.getParameterTypes();
if(parameterTypes.length == 1 && parameterTypes[0].equals(PropertyChangeListener.class)){
pcs.addPropertyChangeListener((PropertyChangeListener) objects[0]);
}
return null;
}
if (REMOVE.equals(methodName)) {
// remove property change listener
Class<?>[] parameterTypes = method.getParameterTypes();
if(parameterTypes.length == 1 && parameterTypes[0].equals(PropertyChangeListener.class)){
pcs.removePropertyChangeListener((PropertyChangeListener) objects[0]);
}
return null;
}
// do real setter or getter
Object oldVaule = null;
boolean iSetter = (methodName.startsWith("set") && objects.length==1 && method.getReturnType().equals(Void.TYPE));
if (iSetter) {
// get oldValue
oldVaule = tryForGetter(methodName, o);
}
// true if the integer argument includes the abstract modifier, false otherwise.
if(!Modifier.isAbstract(method.getModifiers())) {
targetReturn = methodProxy.invokeSuper(o, objects);
}
if (iSetter) {
String propName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
pcs.firePropertyChange(propName, oldVaule, objects[0]);
}
return targetReturn;
}
关于listener管理方法,将在拦截器中拦截并实现。 我们知道cglib通过继承的方式实现动态代理,如果不做修改的话,显然是不能调用父类中没定义的方法。所以这是否意味着被实现类都要实现addPropertyChangeListener方法呢,这样和手动引入PropertyChangeSupport不就一样了么?关于这一点肯定是要避免的,可以令代理类实现接口,从而避免冗余代码和对原有bean的侵入式改动。具体实现后续说明。
拦截器另外一个功能,是对调用方法进行试别,如果是setter方法,则获取修改前数据,并在修改后触发property change event
在实现拦截器之后,就是通过配置cglib实现动态代理
BeanInterceptor interceptor = new BeanInterceptor();
Enhancer e = new Enhancer();
e.setSuperclass(Bean1.class);
e.setCallback(interceptor);
e.setInterfaces(new Class[]{Observable.class});
Bean bean = (Bean) e.create();
interceptor.setPcs(bean);
((Observable) bean).addPropertyChangeListener(new LogPropertyChangeListener());
bean.setTmp("誰にだって");
bean.setTmp("訳があって");
bean.setTmp("今を生きて");
在拦截器中我们设定了listener的管理函数为:addPropertyChangeListener和removePropertyChangeListener。显然,这两个方法和任何bean没有关系,在bean中实现这两个方法是多余,而且实现内容大多是重复的。为了避免冗余代码,这里在生成动态代理时设定代理类实现Observable接口。
public interface Observable {
void addPropertyChangeListener(PropertyChangeListener listener);
void removePropertyChangeListener(PropertyChangeListener listener);
}
Observable接口中定义bean没有实现,但是可以被拦截器拦截的方法。在拦截器中消费这个调用事件,而不会传递到具体bean中。
property Listener实现如下
public class LogPropertyChangeListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println("Property change: "+evt.getPropertyName()+"|old value is " +
evt.getOldValue()+"====new value is "+evt.getNewValue());
}
}
最后运行结果
Property change: tmp|old value is null====new value is 誰にだって
Property change: tmp|old value is 誰にだって====new value is 訳があって
Property change: tmp|old value is 訳があって====new value is 今を生きて
参考:
[1]: Dynamic Class Enhancement with CGLib [2]: A Better Data Binding Model [3]: CGLib Proxies and Hibernate Lazy Fetching