JavaBean 学习笔记

大纲:

1.1 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);
  • 如何提供我们自己的BeanInfoIntrospector?
§ Introspector 会探索一个类,如果它找到了就会使用这个 BeanInfo 类,这个类符合下面的条件 :
JavaBean类在同一个包中
类名是<JavaBean类名>+BeanInfo
实现了 java.beans.BeanInfo 

我们只要提供具有上面所说特征的类就可以挂接 BeanInfo 类给 Introspector
挂接了我们自己的BeanInfo类后,Introspector.getBeanInfo 返回的是我们自己的这个实现吗?
答案是否定的,请注意前面提到的 BeanInfo 也需要按所需层次过 滤,所以需要合成。它返回的类型是java.beans.GenericBeanInfo

下面给一个样例图片,可视化窗口中有一个复合型组件(Money Field),它包括两个基本组件,一个是货币代码(下拉框),另外一个是金额数字输入框。在下面 Properties 视图中列出了我们额外添加的属性。

BeanInfo support in Eclipse
下面给一个完整的 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
  1. Field是类中明确定义的
  2. Property是我们告诉使用JavaBean的用户这里有什么可用,哪怕这个Property 名字并非任何已存在的Field名也没有关系,我们可以通过 ReadMethod WriteMethod 来委托这种操作,甚至名字中有时可以有圆点
  3. 默认情况下如果我们没有提供自定义的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 是一个观察事情发生次序和参数的好办法。

Property Editor in Eclipse

这里准备一个例子,它是上面图片中的 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);
    }
  }
}

 

com.alibaba.fastjson.JSONException 是一个 JSON 解析/生成异常类,通常是由 fastjson 库在处理 JSON 字符串时发生错误导致的。 出现 "com.alibaba.fastjson.JSONException: write JavaBean error" 错误一般是由以下原因引起的: 1. JavaBean 对象和 JSON 字符串之间的属性不匹配。例如,JavaBean 对象缺少某些属性或 JSON 字符串包含了多余的属性,导致无法正确地将 JSON 字符串转换成 JavaBean 对象,或者无法将 JavaBean 对象转换成相应的 JSON 字符串。 解决方法:检查 JavaBean 对象和 JSON 字符串之间的属性是否一一对应并正确设置。 2. JavaBean 对象的属性和 JSON 字符串的属性类型不匹配。例如,JavaBean 对象中的属性是长整型,但是 JSON 字符串中对应的属性是字符串类型,导致类型转换错误。 解决方法:确保 JavaBean 对象的属性类型和 JSON 字符串中对应的属性类型一致。 3. JavaBean 对象中存在循环引用。即 JavaBean 对象的属性中包含了对自身的引用,导致 fastjson 无法正确处理。 解决方法:避免在 JavaBean 对象中出现循环引用,可以通过在 JavaBean 对象中使用 @JSONField(serialize=false) 注解来标记该属性不被序列化。 总之,"com.alibaba.fastjson.JSONException: write JavaBean error" 错误通常是由于 fastjson 在处理 JSON 字符串和 JavaBean 对象之间的转换时出现了问题。要解决这个错误,需要检查 JavaBean 对象和 JSON 字符串之间的属性是否匹配,并且确保属性类型一致。另外,要避免在 JavaBean 对象中出现循环引用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值