大纲:
1.1 JavaBean
是什么,为什么我们需要它
?
1.2 JavaBean 命名约定
1.2 JavaBean 命名约定
1.3 BeanInfo
提取
1.4 PropertyDescriptor & PropertyEditor
1.5
样例:让
JavaBean
支持
Eclipse Visual Editor
1.1: 为什么需要它
Java Beans 这个词汇在平时开发中没有人注意它有什么特别的地方,一般我们当它是 POJO,不过要说起来它还有不少可以利用的特性,虽然我们在平时很少用到它。在这里我简单地介绍一下 Java Beans 的一些信息。
在我毕业大学学习中,我们就看过别人用 VB 开发一些程序,可视化的,窗口随便摆一下,再加几个属性值就基本上可以做些简单实用的程序,看上去多简单。Java 为了推动应用开发和社区进程,也制定了 Java Beans 相关的规范,用于为 IDE 支持提供一个标准化的方法。
1.2:JavaBean 命名约定
JavaBean 的一些约定:
- 默认的构造方法(无参数的public构造方法) 实现java.io.Serializable接口
- 属性都应该用public的 getter/setter来访问
Getter
方法
:
- 方法名应该是 get / is + <属性名但要大写第一个字母>;返回值应该是相应的属性的值;方法访问修饰符应该是public。
Setter
方法
:
- 方法名应该是set + <属性名但要大写第一个字母>;返回类型是void, 参数类型与属性一样的;方法访问修饰符应该是public
例如这样一个类,在这里我们实现了
Serializable
接口,
拥有默认构造方法
(
没有明确定义一个构造方法编译器会
为我们准备一个的
)
,
Getter/Setter
方法与属性
name
之
间符合约定
:
public class Person implements Serializable {
private String name;
public String getName(){ return name;}
public void setName(String name){
this.name=name;
}
}
1.3:
BeanInfo
是什么?
§
BeanInfo
提供了对一个
JavaBean
的描述,包括属性和相应的
Read/Write
方法
(
也就相当于
getter/setter)
,如果这个
JavaBean
支持可
视化,比如
JFrame
类,它们还会有可用的事件集,用于
IDE
自动提取可
用事件列表,自动生成代码
§
BeanInfo
如何提取
?
JDK 为我们准备了一个 java.beans.Introspector 类来提取 JavaBean 的 BeanInfo ,常用方法如下:
§
public BeanInfo Introspector.getBeanInfo(Class);
§
Introspector
还可以在提取
BeanInfo
时按所需要的类继承层次进行过滤,
它是通过两个参数来做的,方法签名如下
,
第一个类是
JavaBean
类,第
二个是继承层次上的某个父类,所有包含在第二个类中的东西都被过滤
掉
:
§
public BeanInfo Introspector.getBeanInfo(Class,Class);
- 如何提供我们自己的BeanInfo给Introspector?
§
Introspector
会探索一个类,如果它找到了就会使用这个
BeanInfo
类,这个类符合下面的条件
:
•与JavaBean类在同一个包中
•类名是<JavaBean类名>+BeanInfo
•实现了 java.beans.BeanInfo 接口
我们只要提供具有上面所说特征的类就可以挂接
BeanInfo
类给
Introspector
•挂接了我们自己的BeanInfo类后,Introspector.getBeanInfo 返回的是我们自己的这个实现吗?
答案是否定的,请注意前面提到的
“
BeanInfo
也需要按所需层次过
滤,所以需要合成。它返回的类型是java.beans.GenericBeanInfo
下面给一个样例图片,可视化窗口中有一个复合型组件(Money Field),它包括两个基本组件,一个是货币代码(下拉框),另外一个是金额数字输入框。在下面 Properties 视图中列出了我们额外添加的属性。
下面给一个完整的 BeanInfo 源码作参考,其中有关于 setPropertyEditorClass 的地方,它将用到下一节讲到的 PropertyEditor:
/* CustomizedEntryBeanInfo.java Created on Sep 30, 2006
* Default file encoding :GB18030 (GB2312/GBK)
*
*/
package net.vicp.atreides.training.j2eebasis.first.javabeans;
import java.awt.Image;
import java.beans.*;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import net.vicp.atreides.training.j2eebasis.first.javabeans.propertyeditors.NamePropertyEditor;
import net.vicp.atreides.training.j2eebasis.first.javabeans.propertyeditors.PasswordPropertyEditor;
/**
* The name of this class is as following style :
* <targetClassName>+'BeanInfo', Introspector will test if the class exist.
*
* @author Dan yang
*/
public class CustomizedEntryBeanInfo extends SimpleBeanInfo{
private PropertyDescriptor[] properties;
private EventSetDescriptor[] events;
{
System.out.println("About to initialize PropertyDescriptor.");
try {
{// construct PropertyDescriptors.
Method getter = CustomizedEntry.class.getDeclaredMethod("readPassword", new Class[0]);
Method setter = CustomizedEntry.class.getDeclaredMethod("writePassword", new Class[] { String.class });
PropertyDescriptor name = new PropertyDescriptor("name" , CustomizedEntry.class);
PropertyDescriptor password = new PropertyDescriptor("password.clear.type" , getter , setter);
PropertyDescriptor email = new PropertyDescriptor("email" , CustomizedEntry.class);
PropertyDescriptor credentials = new PropertyDescriptor("credentials" , CustomizedEntry.class);
name.setPropertyEditorClass(NamePropertyEditor.class);
credentials.setPropertyEditorClass(PasswordPropertyEditor.class);
System.out.println("read :" + getter + ", write :" + setter);
properties = new PropertyDescriptor[] { name, password, email, credentials };
}// end of construct PropertyDescriptors.
List eventList = new LinkedList();
{// construct EventSetDescriptors.
EventSetDescriptor guard = null;
EventSetDescriptor notifier = null;
{// ValueCheck eventSet.
MethodDescriptor desc = new MethodDescriptor(//
VetoableChangeListener.class.getDeclaredMethod("vetoableChange",
new Class[] { PropertyChangeEvent.class }));
Method addMethod = CustomizedEntry.class.getDeclaredMethod(//
"addRollbackableChangeListener", new Class[] { VetoableChangeListener.class });
Method removeMethod = CustomizedEntry.class.getDeclaredMethod(//
"removeRollbackableChangeListener", new Class[] { VetoableChangeListener.class });
guard = new EventSetDescriptor(//
"ValueCheck" , VetoableChangeListener.class ,//
new MethodDescriptor[] { desc } ,//
addMethod , removeMethod);
eventList.add(guard);
}
{// Notifier eventSet.
MethodDescriptor desc = new MethodDescriptor(//
PropertyChangeListener.class.getDeclaredMethod("propertyChange",
new Class[] { PropertyChangeEvent.class }));
Method addMethod = CustomizedEntry.class.getDeclaredMethod(//
"addPropertyChangeListener", new Class[] { PropertyChangeListener.class });
Method removeMethod = CustomizedEntry.class.getDeclaredMethod(//
"removePropertyChangeListener", new Class[] { PropertyChangeListener.class });
notifier = new EventSetDescriptor(//
"ValueNotify" , PropertyChangeListener.class ,//
new MethodDescriptor[] { desc } ,//
addMethod , removeMethod);
eventList.add(notifier);
}
{// Revision eventSet.
MethodDescriptor desc = new MethodDescriptor(//
ReviseListener.class.getDeclaredMethod("logRevision", new Class[] { ReviseEvent.class }));
Method addMethod = CustomizedEntry.class.getDeclaredMethod("addReviseListener",
new Class[] { ReviseListener.class });
Method removeMethod = CustomizedEntry.class.getDeclaredMethod("removeReviseListener",
new Class[] { ReviseListener.class });
EventSetDescriptor revise = new EventSetDescriptor(//
"Revision" , ReviseListener.class , new MethodDescriptor[] { desc } , addMethod , removeMethod);
eventList.add(revise);
}
events = (EventSetDescriptor[]) eventList.toArray(new EventSetDescriptor[eventList.size()]);
}// end of construct EventSetDescriptors.
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public PropertyDescriptor[] getPropertyDescriptors() {
return properties;
}
public EventSetDescriptor[] getEventSetDescriptors() {
return events;
}
public Image getIcon(int iconKind) {
return loadImage("bmp.gif");
}
}
package net.vicp.atreides.training.j2eebasis.first.javabeans;
import java.beans.*;
import java.util.HashSet;
/**
* @version 1.1
* @author Daniel
*/
public class CustomizedEntry{
private char[] credentials;
private char[] password;
private String email;
private String name;
private final VetoableChangeSupport guard = new VetoableChangeSupport(this);
private final PropertyChangeSupport notifier = new PropertyChangeSupport(this);
private final HashSet revisions = new HashSet();
public char[] getCredentials() {
return credentials;
}
public String getEmail() {
return email;
}
public String getName() {
return name;
}
public char[] getPassword() {
return password;
}
public String readPassword() {
return password == null ? null : new String(password);
}
public void setCredentials(char[] credentials) {
this.credentials = credentials;
}
public void setEmail(String email) {
String old = this.email;
try {
guard.fireVetoableChange("email", old, email);
this.email = email;
notifier.firePropertyChange("email", old, email);
} catch (PropertyVetoException e) {
System.out.println("Value of property 'email' is invalid :" + e.getMessage());
}
}
public void setName(String name) {
String old = this.name;
try {
guard.fireVetoableChange("name", old, name);
this.name = name;
notifier.firePropertyChange("name", old, name);
} catch (PropertyVetoException e) {
System.out.println("Value of property 'name' is invalid :" + e.getMessage());
}
}
public void setPassword(char[] password) {
char[] old = this.password;
try {
guard.fireVetoableChange("password", old, password);
this.password = password;
notifier.firePropertyChange("password", old, password);
} catch (PropertyVetoException e) {
System.out.println("Value of property 'password' is invalid :" + e.getMessage());
}
}
public void writePassword(String password) {
this.password = password == null ? null : password.toCharArray();
}
// 以下省略很多 addXXXListener / removeXXXListener 方法,是委托给 PropertyChangeListener 的方法
}
1.4:PropertyDescriptor & PropertyEditor
- PropertyDescriptor 提供了关系一个Property的描述,比如属性名,读这个属性的方法和写这个属性的方法,以及自定义的编辑器
- 对于非基本类型和非String,Font,Color等类型的Property我们一般需要自定义的PropertyEditor
- 我们这里所说的Property不等于Field
- Field是类中明确定义的
- Property是我们告诉使用JavaBean的用户这里有什么可用,哪怕这个Property 名字并非任何已存在的Field名也没有关系,我们可以通过 ReadMethod 和WriteMethod 来委托这种操作,甚至名字中有时可以有圆点
- 默认情况下如果我们没有提供自定义的BeanInfo,这些Property 都将根据反射得到类中的所有Field 来生成它们,这时 Property 名字自然也就等于Field名字
•PropertyEditor 何时使用?
- IDE 在自动生成代码时
§
IDE
会探测这个
JavaBean
的
BeanInfo
类,让它给出这个
JavaBean
的
PropertyEditor
,如果没有自定义的
PropertyEditor
,就直接使用默认反射
§
- IDE如何探测到自定义的PropertyEditor?
§
IDE
得到
BeanInfo
类后,列举出所有
PropertyDescriptor
,
然后再通过
PropertyDescriptor.getPropertyEditorClass
方
法获得
PropertyEditor
类名
1.5:样例,让 Eclipse Visual Editor 支持我们的组件(这个样例中 CustomerEntry 不是一个可视化的组件,可参照前面图片中的可视化的 Money Field 组件)。
我们常用的开发工具比如 Eclipse Visual Editor 就是一个可视化的 UI 开发工具,它可以通过像 VB 一样的方式摆放可视化的组件并设置属性来生成界面代码。下面这张图片中我用 Eclipse VE 创建了一个类 TestVisualClass,然后用右键 "Open With > Visual Editor" 打开它。这时,VE 会出现画布给我画界面,首先我创建一个 java bean,它的名字叫 entry,这时VE 会自动为我们生成一个 getEntry() 方法,也就是说 entry 是作为 TestVisualClass 的一个属性。当然,这里一个 CustomerEntry 类不是一个可视化的所有没有自然地出现像 JTextField 那样的图形效果。但是你可以像我一样在下面 Properties 视图中给列出的属性赋值,确定后 VE 会自动去更新 TestVisualClass.java 的源码,就像图片中的源代码一样。
这里像大家介绍一个学习调试的小技巧:注意到左下角有一些日志打印在 Console 视图中,为了了解 Java Beans 在支持 IDE 生成代码中是如何使用我们提供的 PropertyEditor,我准备了另外一个我用 Multicast (TCP 多播) 实现的 Log4J 自定义 Appender,IDE 调用 PropertyEditor 的方法时,PropertyEditor 通过 Log4J 发送的日志。我们打开另外一个接收端接收 Eclipse 调用 PropertyEditor 时的 Log4J 事件。我们直接通过 Console 是看不到 Eclipse VE 发送的日志,因为它打印到 Eclipse 自己的 Console log 里面去了,Eclipse 的 Console 视图捕获不了。在我们开始并不是很熟悉一个 API 工作原理时多一些 log 是一个观察事情发生次序和参数的好办法。
这里准备一个例子,它是上面图片中的 name 这个 property 使用的下拉列表编辑器,这里不细讲一个 IDE 在使用我们提供的 BeanInfo 和 PropertyEditor 时按什么次序调用,我们结合相应的 Java Interface 了解其中的可以用方法,再按上面我提到的小技巧,通过打印日志来详细观察。
package net.vicp.atreides.training.j2eebasis.first.javabeans.propertyeditors;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JComboBox;
/**
* PropertyEditor for drop down list.
*
* @version 1.1
* @author Dan Yang@atreides.vicp.net)
*/
public class ListPropertyEditor extends AbstractPropertyEditor{
private final JComboBox nameList = new JComboBox();
private final String[] ITEMS;
public ListPropertyEditor(String[] items, boolean debug) {
super(debug);
if (items == null || items.length == 0) {
throw new IllegalArgumentException("Items should not be empty.");
}
this.ITEMS = items;
{
for (int i = 0; i < ITEMS.length; i++) {
nameList.addItem(ITEMS[i]);
}
}
}
public ListPropertyEditor(String[] items) {
this(items, true);
}
public Component getCustomEditor() {
log("getCustomEditor.");
return nameList;
}
public Object getValue() {
log("getValue.");
return this.nameList.getSelectedItem();
}
public void setValue(Object value) {
log("setValue :" + value);
String old = (String) this.nameList.getSelectedItem();
this.nameList.setSelectedItem(value);
notifier.firePropertyChange("name", old, value);
}
public String getAsText() {
log("getAsText.");
return (String) getValue();
}
public String getJavaInitializationString() {
if (nameList != null) {
String value = "/"" + nameList.getSelectedItem() + "/"";
log("initializationString :" + value);
return value;
} else {
log("initializationString :/"/"");
return "/"/"";
}
}
public String[] getTags() {
log("getTags.");
return ITEMS;
}
public void setAsText(String text) throws IllegalArgumentException {
log("setAsText :" + text);
setValue(text);
}
public void paintValue(Graphics gfx, Rectangle box) {
log("paintValue.");
}
}
/* AbstractPropertyEditor.java Created on Sep 30, 2006
* Default file encoding :GB18030 (GB2312/GBK)
*
*/
package net.vicp.atreides.training.j2eebasis.first.javabeans.propertyeditors;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyEditor;
import net.vicp.atreides.training.j2eebasis.first.serialization.ObjectSender;
/**
* Abstract property editor, providing debug function,
* you can run 'ObjectReceiver' for debug.
*
* @see {@link net.vicp.atreides.training.j2eebasis.first.serialization.ObjectReceiver}
* @see {@link net.vicp.atreides.training.j2eebasis.first.serialization.ObjectSender}
*
* @version 1.1
* @author Dan Yang(dan.yang@atreides.vicp.net)
*/
public abstract class AbstractPropertyEditor implements PropertyEditor{
private final ObjectSender logger = new ObjectSender();
private final boolean debug;
protected final PropertyChangeSupport notifier = new PropertyChangeSupport(this);
protected AbstractPropertyEditor(boolean debug) {
this.debug = debug;
}
public boolean isPaintable() {
return true;
}
public boolean supportsCustomEditor() {
return true;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
notifier.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
notifier.removePropertyChangeListener(listener);
}
protected void log(Object message) {
if (debug) {
logger.send(message);
}
}
}