提示,重点:JavaBeans的Property和 Events;PropertyEditor极其注册和查找机制。
从目前来看,JavaBeans 更像是源自GUI的需求。
使用NetBeans新建一个Java Application,然后新建一个jFrame form,右边会有组件面板,如下:
这些组件,本质上都是bean!所以可被复用、可被引用、可被通知!
将任意一个组件拖进去安置,例如Button(按钮)。如下:
在右下角会显示该组件的属性,如下:
如果是英文版的NetBeans,你就会发现这里的属性就是Properties。你在这里进行的所有设置,如设置图标、字体、边框、背景等等操作,本质上都是调用相应的SETTER!(在SETTER之前会先调用PropertyEditor)
官方文档有这么一句:
A bean is a Java class with method names that follow the JavaBeans guidelines. A bean builder tool uses introspection to examine the bean class. Based on this inspection, the bean builder tool can figure out the bean‘s properties, methods, and events. -- 原文
类似Netbeans这样的builder tool,会识别并展示所有的property name and type,以及其他,以方便设计者在设计时进行操作。
JavaBeans’ property 中有两个特殊的:bound property、constrained property,如下:
bound property,当其值发生改变时,会通知监听器!两个含义:
1,该bean类包含两个方法:
addPropertyChangeListener()
andremovePropertyChangeListener()
。2,当值发生改变时,该bean会发送
PropertyChangeEvent
至注册过的监听器。
PropertyChangeEvent
and PropertyChangeListener
都在 java.beans
package中。
该包中还有一个类,PropertyChangeSupport,负责多数的bound properties工作。
代码示例如下:
import java.beans.*; public class FaceBean { private int mMouthWidth = 90; private PropertyChangeSupport mPcs = new PropertyChangeSupport(this); // 这里 public int getMouthWidth() { return mMouthWidth; } public void setMouthWidth(int mw) { int oldMouthWidth = mMouthWidth; // mMouthWidth = mw; mPcs.firePropertyChange("mouthWidth", oldMouthWidth, mw); // 这里 } // 这里 public void addPropertyChangeListener(PropertyChangeListener listener) { mPcs.addPropertyChangeListener(listener); } // 这里 public void removePropertyChangeListener(PropertyChangeListener listener) { mPcs.removePropertyChangeListener(listener); } }
constrained property,一种特殊的 bound property。bean会持续跟踪 a set of veto listeners。当constrained property 将要发生改变时,会先咨询监听器。任何一个veto listener都有机会否决这次改变,就是说,维持原值不变。
veto listeners独立于 property change listener。所幸, java.beans 包包含了一个 VetoableChangeSupport 类,可以极大的简化 constrained properties。
import java.beans.*; public class FaceBean { private int mMouthWidth = 90; private PropertyChangeSupport mPcs = new PropertyChangeSupport(this); private VetoableChangeSupport mVcs = new VetoableChangeSupport(this); // 这里 public int getMouthWidth() { return mMouthWidth; } public void setMouthWidth(int mw) throws PropertyVetoException { int oldMouthWidth = mMouthWidth; mVcs.fireVetoableChange("mouthWidth", oldMouthWidth, mw); // 这里 mMouthWidth = mw; mPcs.firePropertyChange("mouthWidth", oldMouthWidth, mw); } public void addPropertyChangeListener(PropertyChangeListener listener) { mPcs.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { mPcs.removePropertyChangeListener(listener); }
// 这里 public void addVetoableChangeListener(VetoableChangeListener listener) { mVcs.addVetoableChangeListener(listener); } // 这里
public void removeVetoableChangeListener(VetoableChangeListener listener) { mVcs.removeVetoableChangeListener(listener); } }
JavaBeans’ method: A bean‘s methods are the things it can do. Any public method that is not part of a property definition is a bean method.
JavaBeans’ events: A bean class can fire off any type of event, including custom events.
和property一样,event也有自己的惯例:
public void add<Event>Listener(<Event>Listener a)
public void remove<Event>Listener(<Event>Listener a)
注意,listener必须是 java.util.EventListener 的子类。
类似NetBeans的builder tool可以识别 bean events。
使用BeanInfo
Beans,特别是图形组件,可能拥有大量的properties,难以快速的找到正确的properties进行编辑 -- 特别是对于新手来说。
BeanInfo可以改变bean在builder tool中的展示形式。Builder tool可以查询BeanInfo以找出哪些properties优先展示,哪些应该隐藏。
BeanInfo和bean的名字一样,但以 BeanInfo 结尾。例如,FaceBean与FaceBeanBeanInfo。
虽然可以手动创建BeanInfo,但是使用工具(如NetBeans)更简洁。
NetBeans中创建Bean的BeanInfo很简单,右键 - BeanInfo Editor… 即可。第一次会提示创建,同意即可。
默认进入源码编辑界面,记得切入设计界面。如下:
可以针对property进行选择,主要就是是否优先展示、是否隐藏、是否bound property、是否constrained property。
builder tool在发现一个bean类之后,会自动查找同路径下的BeanInfo,以决定如何展示bean。
--------------------------
JavaBeans入门很简单,但这货其实是很有深度的。现在来看点高级货,包括 如何持久化保存一个bean、如何为定制的类型提供定制的editor。
Bean Persistence (持久化)
想了下,这个也是源自GUI吧,总不能每次打开的界面都是最原始的界面。
The mechanism that makes persistence possible is called serialization. Object serialization means converting an object into a data stream and writing it to storage. Any applet, application, or tool that uses that bean can then "reconstitute" it by deserialization. The object is then restored to its original state.
囧了,居然是序列化和反序列化。
All beans must persist. To persist, your beans must support serialization by implementing either the
java.io.Serializable
(in the API reference documentation) interface, or thejava.io.Externalizable
(in the API reference documentation) interface. These interfaces offer you the choices of automatic serialization and customized serialization.
原来有两个接口,涨姿势了。
实现 Serializable的bean,可以自动序列化/反序列化,会自动忽略transient和static的字段。
The Java Object Serialization API automatically serializes most fields of a Serializable object to the storage stream. This includes primitive types, arrays,and strings. The API does not serialize or deserialize fields that are marked transient or static.
你可以控制你的bean的序列化level!三种途径:
- 自动序列化,实现Serializable接口即可。序列化整个对象,除了transient 和 static 字段。
- 定制序列化。将不想序列化的自动使用transient 或 static 修饰。— 囧。
- 定制文件格式,实现 Externalizable和其两个方法。Bean会被以那种文件格式写入。
默认序列化:Serializable接口,就是一个标记,告诉JVM希望进行默认序列化。有几个关键点需要知道:
类必须有一个针对父类无参构造的访问。
如果父类实现了Serializable接口,子类可以继承。
会忽略transient 和 static。 -- 各种重复啊
选择性序列化:writeObject 和 readObject
如果你的serializable class含有下面两个方法中的任意一个,默认的序列化就不会发生:
private void writeObject(java.io.ObjectOutputStream out) throws IOException;
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
通过这两个方法,你可以控制如何序列化/反序列化。
Externalizable接口
完全控制bean的序列化/反序列化(例如,以一种特定文件格式进行读写)。
需要实现两个方法:writeExternal和readExternal。
实现类必须有一个无参构造。
Long Term Persistence 长期持久化
Long-term persistence is a model that enables beans to be saved in XML format.
居然只支持XML格式。
Encoder 和 Decoder
XMLEncoder类用于输出Serializable对象。例如:
XMLEncoder encoder = new XMLEncoder( new BufferedOutputStream( new FileOutputStream("Beanarchive.xml"))); encoder.writeObject(object); encoder.close();
XMLDecoder类读取XMLEncoder创建的XML:
XMLDecoder decoder = new XMLDecoder( new BufferedInputStream( new FileInputStream("Beanarchive.xml"))); Object object = decoder.readObject(); decoder.close();
下面是SimpleBean的XML形式:
<?xml version="1.0" encoding="UTF-8" ?> <java> <object class="javax.swing.JFrame"> <void method="add"> <object class="java.awt.BorderLayout" field="CENTER"/> <object class="SimpleBean"/> </void> <void property="defaultCloseOperation"> <object class="javax.swing.WindowConstants" field="DISPOSE_ON_CLOSE"/> </void> <void method="pack"/> <void property="visible"> <boolean>true</boolean> </void> </object> </java>
Bean Customization 定制
修改一个bean的appearance和behaviour,两种方式:
- 通过一个property editor。
- 通过customizer。 同property editor的区别是,customizer关联到bean,property editor关联到property。
PropertyEditor
这里的属性设值,都是调用了相应的PropertyEditor!-- 因为你输入的都是字符串啦。
例如,点击 toolTipText 后面的【…】,就会打开一个StringEditor window:
PropertyEditorSupport提供PropertyEditor接口的空实现,继承它,按需覆盖方法即可完成自己的PropertyEditor。
Property Editors 是如何关联到 Properties 的? (重点)
- 通过一个BeanInfo对象 显式的关联。
- 通过
java.beans.PropertyEditorManager.registerEditor
方法 显式的注册。 - 名字查找。如果一个类没有显式的关联PropertyEditor,那 PropertyEditorManager 会按以下顺序搜索其editor:
同包路径下以类的名字开始,以’Editor’结束。
在classpath中搜索(以类的名字开始,以’Editor’结束)!
Customizers (可以忽略)
在bean层次上configure或edit一个bean。
所有的customizer 必须:
- 继承 java.awt.Component 或其子类。
- 实现 java.beans.Customizer 接口。就是实现注册 PropertyChangeListener对象的方法。
- 默认构造。
- 通过BeanInfo.getBeanDescriptor将customizer关联到目标类。
参考文档:
The Java™ Tutorials,Trail: JavaBeans(TM)