动态的为Class的Property变化设置监听

对于Class中的Property变化,有时可能需要在Property改变时针对这个改变进行一些操作。比如说对新值进行重新拼装,对新值进行检验等等。如果这个拼装或校验规则是固定的,那么可以直接通过写一些逻辑代码来实现。但是如果规则是不固定需要在运行过程中动态的设置的话。直接写固定的逻辑代码就无法实现了。此时可以通过java.beans包提供的几个类来实现这个功能。
PropertyChangeSupport:可以用做属性变化的监听。你可以通过他监听到属性的原值和新值(或者其它你想让他监听到值)

PropertyChangeSupport提供了addPropertyChangeListener(PropertyChangeListener listener)方法来让使用者添加一个监听(PropertyChangeListener 实例)。使用者可以创建一个继承了PropertyChangeListener 的实例并完成propertyChange(PropertyChangeEvent event)方法来自定义自己的动作:

new PropertyChangeListener() {
	@Override
	public void propertyChange(PropertyChangeEvent event) {
		System.out.println(event.getPropertyName() + "开始改变");
	}
}

一个PropertyChangeSupport可以包含多个监听。通过PropertyChangeListenerMap来保存所有的监听,PropertyChangeSupport允许使用者通过addPropertyChangeListener(String propertyName,PropertyChangeListener listener)方法为一个PropertyChangeListener定义一个名字作为在PropertyChangeListenerMap中的key,如果用户不进行自定义名字,那么名字就是null。因为PropertyChangeListenerMap继承了ChangeListenerMap<PropertyChangeListener>,而ChangeListenerMap内部维护了一个Map<String, L[]>的泛型Map来保存所有的listener。使用者调用PropertyChangeSupport中的addPropertyChangeListener方法来新增listener时,就会通过add方法放到数组中。

public void addPropertyChangeListener(
			String propertyName,
			PropertyChangeListener listener) {
	if (listener == null || propertyName == null) {
		return;
	}
	listener = this.map.extract(listener);
	if (listener != null) {
		this.map.add(propertyName, listener);
	}
}

 可以看到除了null校验外还有有一个extract方法对PropertyChangeListenerProxy进行分解。

public final PropertyChangeListener extract(PropertyChangeListener listener) {
	while (listener instanceof PropertyChangeListenerProxy) {
		listener = ((PropertyChangeListenerProxy) listener).getListener();
	}
	return listener;
}

 PropertyChangeListenerProxy可以认为是一个拥有两个属性分别为propertyName和listener的Bean。这个地方会把listener剥离出来放弃PropertyChangeListenerProxy中的propertyName而使用addPropertyChangeListener的propertyName作为实际的propertyName。而如果使用addPropertyChangeListener(PropertyChangeListener listener)不自定义propertyName那么就会取PropertyChangeListenerProxy中的propertyName作为实际propertyName。具体实现是这样的

public void addPropertyChangeListener(PropertyChangeListener listener) {
	if (listener == null) {
		return;
	}
	if (listener instanceof PropertyChangeListenerProxy) {
		PropertyChangeListenerProxy proxy =
			   (PropertyChangeListenerProxy)listener;
		// Call two argument add method.
		addPropertyChangeListener(proxy.getPropertyName(),
								  proxy.getListener());
	} else {
		this.map.add(null, listener);
	}
}

PropertyChangeListenerMap的add方法就是把listener放到对应key的数组里,add方法是同步的,保证在不会再并发时导致listener丢失:

public final synchronized void add(String name, L listener) {
	if (this.map == null) {
		this.map = new HashMap<String, L[]>();
	}
	L[] array = this.map.get(name);
	int size = (array != null)
			? array.length
			: 0;

	L[] clone = newArray(size + 1);
	clone[size] = listener;
	if (array != null) {
		System.arraycopy(array, 0, clone, 0, size);
	}
	this.map.put(name, clone);
}

 removePropertyChangeListener方法可以把已经添加的listener去掉。通过removePropertyChangeListener(String propertyName,PropertyChangeListener listener)和removePropertyChangeListener(PropertyChangeListener listener)两个方法。如果指定propertyName就去指定propertyName的数组里面取删除,如果没指定就去propertyName为null的数组里面去删除。因为采用Map<String, L[]>这种方式存储listener,所以如果如果一个listener在add时没命名删除的时间又命名了或者add时命名了删除时没命名,这两种情况都是删不掉的。removePropertyChangeListener同样是同步的来避免并发问题。

PropertyChangeSupport还提供了hasListeners(String propertyName)方法用来判断指定的propertyName下有没有listener,getPropertyChangeListeners()方法获得所有的listener,getPropertyChangeListeners(String propertyName)方法获得指定propertyName下的listener。

PropertyChangeSupport提供了firePropertyChange(String propertyName, Object oldValue, Object newValue),fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue)和firePropertyChange(PropertyChangeEvent event)。其实第一个方法和第二方法在内部都是通过构造一个event(不同的是firePropertyChange(String, Object , Object )是构造了一个PropertyChangeEvent而fireIndexedPropertyChange是构造一个IndexedPropertyChangeEvent)然后调用第三个方法来实现的。而firePropertyChange(PropertyChangeEvent event)内部就是从PropertyChangeListenerMap中取出listener中然后for循环触发。不过在触发的过程中是先取的propertyName为null的,所以没有命名的listener即propertyName为null的是会比有命名的listener先触发的。

public void firePropertyChange(PropertyChangeEvent event) {
	Object oldValue = event.getOldValue();
	Object newValue = event.getNewValue();
	if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
		String name = event.getPropertyName();

		PropertyChangeListener[] common = this.map.get(null);
		PropertyChangeListener[] named = (name != null)
					? this.map.get(name)
					: null;

		fire(common, event);
		fire(named, event);
	}
}

private static void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) {
	if (listeners != null) {
		for (PropertyChangeListener listener : listeners) {
			listener.propertyChange(event);
		}
	}
}

另外可以看到fireIndexedPropertyChange是比firePropertyChange(String, Object , Object )多了第二个参数int index,这个参数的用处完全取决已使用者自己,因为listener的实际动作过程propertyChange(PropertyChangeEvent event)是由使用者自己定义的。而IndexedPropertyChangeEvent只是提供了一个getIndex来使得使用者可以获得到index。

VetoableChangeSupport:可以用在属性变化的校验。在赋值之前对值进行校验是否合乎规则。

VetoableChangeSupport整体的实现方式与使用方式和PropertyChangeSupport几乎一样。使用者可以通过new一个继承VetoableChangeListener的实例来实现对属性的校验。并且他提供了一个专属的PropertyVetoException来让使用者在校验失败时往外部抛异常。所以的触发listener的方法fireVetoableChange要求需要catch异常PropertyVetoException。

new VetoableChangeListener() {
	@Override
	public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
		if ("2".equals(evt.getNewValue())) {
			throw new PropertyVetoException(evt.getPropertyName() + " canot is \"2\"", evt);
		}
		
	}
}

try {
	vetoableChangeListeners.fireVetoableChange("name", this.name, name);
} catch (PropertyVetoException e) {
	e.printStackTrace();
}

PropertyEditorSupport:可以通过继承此类来实现自定义规则的属性值再次修正。如我想再原值的基础上再在后面加一个字母,或者把一个值进行类型转换后再进行赋值。

任何实现PropertyEditor接口的类都能实现这样的功能。PropertyEditorSupport更像是一个实现了PropertyEditor模板,在简单的实现上可以继承PropertyEditorSupport而不用再重写一些用不到的方法。PropertyEditor接口中有以下几个方法:

void setValue(Object value):赋值方法,使用者可以在setValue方法里面对value重新编译,然后再赋值到存储字段。PropertyEditorSupport对于和这个方法的实现除了this.value = value外还执行了一个firePropertyChange()方法,这个方法是PropertyEditorSupport特有的,PropertyEditor接口里面是没有的。

public void setValue(Object value) {
	this.value = value;
	firePropertyChange();
}

PropertyEditorSupport内部维护了一个Vector listeners来存储listener并触发。不过PropertyEditorSupport的firePropertyChange的实现所用的event是new PropertyChangeEvent(source, null, null, null)所以虽然触发了事件,但是实际上event里面并不能得到属性值相关的东西。

public void firePropertyChange() {
	java.util.Vector targets;
	synchronized (this) {
		if (listeners == null) {
			return;
		}
		targets = (java.util.Vector) listeners.clone();
	}
	// Tell our listeners that "everything" has changed.
	PropertyChangeEvent evt = new PropertyChangeEvent(source, null, null, null);

	for (int i = 0; i < targets.size(); i++) {
		PropertyChangeListener target = (PropertyChangeListener)targets.elementAt(i);
		target.propertyChange(evt);
	}
}

Object getValue():获取Value值方法。PropertyEditorSupport的实现就是简单的return value;虽然可以在getValue方法里面进行编辑。但是尽量不要那么做,将编辑方法放到setValue里面,保证value字段存储的为转换后的值。

isPaintable():这个属性编辑器是不是支持画图之类的输出。PropertyEditorSupport直接返回了false,不支持。

paintValue(java.awt.Graphics gfx, java.awt.Rectangle box):和上一个isPaintable方法对应的,如果使用者要通过Graphics画图的话可以通过这个方法实现。PropertyEditorSupport对这个方法无任何实现。

getJavaInitializationString():获得初始化字符串,就是这个属性编辑器的初始值是什么。使用者可以在这里定义初始值。比如颜色Color(127,127,34),数值0之类的。PropertyEditorSupport返回的是字符串"???".

getAsText():将value值以字符串的形式输出。PropertyEditorSupport直接返回了value.toString()方法。

setAsText(String text) throws java.lang.IllegalArgumentException:以字符串形式进行赋值,比如你传一个"0"然后在这里转换成0。PropertyEditorSupport对这个方法的实现就是如果value的类型继承自String的相关实例,就直接调用setValue,否则就抛出异常。

public void setAsText(String text) throws java.lang.IllegalArgumentException {
	if (value instanceof String) {
		setValue(text);
		return;
	}
	throw new java.lang.IllegalArgumentException(text);
}

String[] getTags():可选项。如果使用者要设置可选项的话可以通过这个方法来设置。通过这个方法获得所有的可选项。比如这种。PropertyEditorSupport的实现是直接返回了一个null.

supportsCustomEditor():是否支持外部组件。比如说如果支持调用C语言的组件,这里就返回true。PropertyEditorSupport的实现是直接返回false,不支持。

getCustomEditor():获取外部组件。和supportsCustomEditor对应,如果支持外部组件的话这个地方就是把组件实例返回。PropertyEditorSupport不支持组件就直接返回了null。

addPropertyChangeListener(PropertyChangeListener listener):增加属性监听事件。PropertyEditorSupport内部维护的为Vector listeners,所以PropertyEditorSupport的实现就是addElement(listener)

removePropertyChangeListener(PropertyChangeListener listener):移除属性监听事件。PropertyEditorSupport内部维护的为Vector listeners,所以PropertyEditorSupport的实现就是removeElement(listener)。

除了firePropertyChange方法之外,PropertyEditorSupport还扩展了其它几个方法,getSource(),setSource(Object source)让使用者可以将这个属性编辑器对应的属性所有者放进去。

 

下面是一个关于这三个类简单使用的小Demo:

继承自PropertyEditorSupport的属性编辑器:

package BeanInfo;

import java.beans.PropertyEditorSupport;

// 继承PropertyEditorSupport来实现自定义的编辑工作
public class TestPropertyEditorSupport extends PropertyEditorSupport {
    

    public TestPropertyEditorSupport(Object testEntity) {
        super(testEntity);
    }

    @Override
    public void setValue(Object value) {
        super.setValue(String.valueOf(value) + "AAA");
    }
}

一个Entity类:

package BeanInfo;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyEditorSupport;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;

public class TestEntity {

    private String name;
    
    // 监听
    private PropertyChangeSupport listeners = new PropertyChangeSupport(this);
    
    // 验证
    private VetoableChangeSupport vetoableChangeListeners = new VetoableChangeSupport(this);
    
    private PropertyEditorSupport propertyEditorSupport = new TestPropertyEditorSupport(this);

    public String getName() {
        return name;
    }

    public void setName(String name) {
        listeners.fireIndexedPropertyChange("name",0, this.name, name);
        try {
            vetoableChangeListeners.fireVetoableChange("name", this.name, name);
            propertyEditorSupport.setValue(name);
            this.name = (String) propertyEditorSupport.getValue();
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        listeners.removePropertyChangeListener(listener);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        listeners.addPropertyChangeListener(listener);
    }
    
    public void removeVetoableChangeListener(VetoableChangeListener listener) {
        vetoableChangeListeners.removeVetoableChangeListener(listener);
    }

    public void addVetoableChangeListener(VetoableChangeListener listener) {
        vetoableChangeListeners.addVetoableChangeListener(listener);
    }

    public PropertyEditorSupport getPropertyEditorSupport() {
        return propertyEditorSupport;
    }

    public void setPropertyEditorSupport(PropertyEditorSupport propertyEditorSupport) {
        this.propertyEditorSupport = propertyEditorSupport;
    }

    public PropertyChangeSupport getListeners() {
        return listeners;
    }

    public void setListeners(PropertyChangeSupport listeners) {
        this.listeners = listeners;
    }
}

执行Demo类:

package BeanInfo;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;

public class ListenerTest {
    // 这些都是可以直接在set里面实现的,但是其独特之处在于动态设置,而不是写在set里面一经写成在代码运行过程中无法改变
    public static void main(String[] args) {
        TestEntity te1 = new TestEntity();
        te1.setName("1");
        // 为属性变动增加监听事件1
        te1.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println(evt.getPropertyName() + "-oldVal:" + evt.getOldValue() + " newVal:" + evt.getNewValue());
            }
        });
        // 为属性变动增加监听事件2
        te1.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println(evt.getPropertyName() + "开始改变");

            }
        });

        // 为属性变动增加验证事件1  一个专门的属性验证异常PropertyVetoException
        te1.addVetoableChangeListener(new VetoableChangeListener() {
            
            @Override
            public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
                if ("2".equals(evt.getNewValue())) {
                    throw new PropertyVetoException(evt.getPropertyName() + " canot equals \"2\"", evt);
                }
                
            }
        });
        
        te1.getPropertyEditorSupport().addPropertyChangeListener(new PropertyChangeListener() {
                
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    // 因为PropertyEditorSupport的触发机制,这里无法获得PropertyName,NewValue和OldValue - 这个地方可以见源码,support的chaneEvent的构造为new PropertyChangeEvent(source, null, null, null);
                    System.out.println(evt.getPropertyName() + evt.getNewValue() + evt.getOldValue() + "EDITOR"); 
                }
            });

        te1.setName("2");   // 触发事件  TestEntity内部有属性编辑器,会自动做转换。
                            // 属性编辑器像是一个函数,setValue输入参数,getValue就得或者转换后的参数。
                            // 不过他作为一个类可以绑定到其他类上,通过继承PropertyEditorSupport并重写setValue或getValue方法来定义属于自己的编辑方式,
                            // 且setValue会触发PropertyEditorSupport的changeEvent,也就是说你可以为它绑定PropertyChangeListener(默认无法获得oldValue好newValue)。
                            // 来完成一个动静结合编辑器
        te1.setName("3");
        System.out.println(te1.getName());   
    }

}

执行结果:

name-oldVal:1AAA newVal:2
name开始改变
java.beans.PropertyVetoException: name canot equals "2"
	at BeanInfo.ListenerTest$3.vetoableChange(ListenerTest.java:37)
	at java.beans.VetoableChangeSupport.fireVetoableChange(VetoableChangeSupport.java:375)
	at java.beans.VetoableChangeSupport.fireVetoableChange(VetoableChangeSupport.java:271)
	at BeanInfo.TestEntity.setName(TestEntity.java:29)
	at BeanInfo.ListenerTest.main(ListenerTest.java:52)
name-oldVal:1AAA newVal:3
name开始改变
nullnullnullEDITOR
3AAA

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yue_hu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值