动态代理应用:属性监听器

384 阅读4分钟

代理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的管理函数为:addPropertyChangeListenerremovePropertyChangeListener。显然,这两个方法和任何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