对于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