[size=medium]Bean Serializable Interface 的接口让BEAN可以串行化,将其变成一个可保存为以后使用的二进制流。当一个BEAN被系列化到磁盘上或者其他任何地方,其状态被保存起来,其中的属性值也不会改变。在BEAN的规范中,JSP并没有要求BEAN实现Serializable接口。但是,如果您希望自己控制您所创建的组件的serialization进程,或者您想serialize并不是标准组件扩展的组件,您必须了解serialization and deserialization的细节。
有几个原因你会把BEAN冷藏起来以备后用。有些服务器通过将所有的SESSION 数据(包括BEAN)写入磁盘来支持任意长的SESSION生命期,即使服务器停机也不会丢失。当服务器重新启动后,串行化的数据被恢复。同样的理由,在重负载的站点上支持服务器分簇的环境中,许多服务器通过串行化来复制SESSION。如果你的BEAN不支持串行化,服务器就不能正确地保存和传输类。
通过同样的策略,你可以选择将BEAN保存在磁盘上或者数据库中,以备后用。例如,也许可以将客户的购物车实现为一个BEAN,在访问期间将其保存在数据库中。
如果BEAN需要特殊的复杂的初始设置,可以将BEAN设置好后串行化保存在磁盘上。这个BEAN的“快照”可以用在任何需要的地方,包括在$#@60;jsp:useBean$#@62;中用beanName属性的调用。
$#@60;jsp:useBean$#@62;标签中的beanName属性,用来实例化一个串行化的BEAN,而不是用来从一个类创建一个全新的实例。如果BEAN还没有创建,beanName属性传给java.beans.Bean.instantiate()方法,由类装载器对类进行实例化。它首先假定存在一个串行化的BEAN(带有扩展名.ser),然后会将其激活。如果这个操作失败,它就会实例化一个新的实例。
下面简单介绍一下这个接口:
对象能包含其它的对象,而这其它的对象又可以包含另外的对象。JAVA serialization能够自动的处理嵌套的对象。对于一个对象的简单的域,writeObject()直接将值写入流。而,当遇到一个对象域时,writeObject()被再次调用,如果这个对象内嵌另一个对象,那么,writeObject() 又被调用,直到对象能被直接写入流为止。程序员所需要做的是将对象传入ObjectOutputStream 的writeObject() 方法,剩下的将又系统自动完成。下面的例子创建了一个调用mine对象的PersonalData对象。代码实现的是将一个串和mine 对象输出到一个流,并存入一个文件:
public class PersonalData implements Serializable {
public int id
public int yearOfBirth;
public float yearlySalary;
}
PersonalData mine = new PersonalData(101, 1956, 46500.00);
FileOutputStream outstream = new FileOutputStream("PersonalData.ser");
ObjectOutputStream out = new ObjectOutputStream(outstream);
out.writeObject("My personal data"); //将一个串写入流
out.writeObject(mine); //将这个对象写入流
out.close(); // 清空并关闭流
...
一个FileOutputStream对象被创建且传到一个ObjectOutputStream。当out.writeObject() 被调用,这个串和mine 对象被objects are serializ顺序加入一个存入文件PersonalData.ser的字节对列。
您应该注意上述类是实现的java.io.Serializable接口。因为它并未指定要实现的方法,所以Serializable被称为"tagging interface" ,但是它仅仅"tags"它自己的对象是一个特殊的类型。任一个您希望serialize的对象都应该实现这个接口。这是必须的。否则,用到流技术时将根本不工作。例如,如果您试着去serialize 一个没有实现这个接口的对象,一个 NotSerializableException将产生。
类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
Java的"对象序列化"能让你将一个实现了Serializable接口的对象转换成一组byte,这样日后要用这个对象时候,你就能把这些byte数据恢复出来,并据此重新构建那个对象了。
要想序列化对象,你必须先创建一个OutputStream,然后把它嵌进ObjectOutputStream。这时,你就能用writeObject( )方法把对象写入OutputStream了。
writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以还原它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。
读的时候,你得把InputStream嵌到ObjectInputStream里面,然后再调用readObject( )方法。不过这样读出来的,只是一个Object的reference,因此在用之前,还得先下传。readObject 方法负责从流中读取并还原类字段。它可以调用 in.defaultReadObject 来调用默认机制,以还原对象的非静态和非瞬态字段。
defaultReadObject 方法使用流中的信息来分配流中通过当前对象中相应命名字段保存的对象的字段。这用于处理类发展后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。
看一个列子:
import java.io. * ;
class tree implements java.io.Serializable {
public tree left;
public tree right;
public int id;
public int level;
private static int count = 0 ;
public tree( int depth) {
id = count ++ ;
level = depth;
if (depth > 0 ) {
left = new tree(depth - 1 );
right = new tree(depth - 1 );
}
}
public void print( int levels) {
for ( int i = 0 ; i < level; i ++ )
System.out.print( " " );
System.out.println( " node " + id);
if (level <= levels && left != null )
left.print(levels);
if (level <= levels && right != null )
right.print(levels);
}
public static void main (String argv[]) {
try {
/**/ /* 创建一个文件写入序列化树。 */
FileOutputStream ostream = new FileOutputStream( " tree.tmp " );
/**/ /* 创建输出流 */
ObjectOutputStream p = new ObjectOutputStream(ostream);
/**/ /* 创建一个二层的树。 */
tree base = new tree( 2 );
p.writeObject(base); // 将树写入流中。
p.writeObject( " LiLy is 惠止南国 " );
p.flush();
ostream.close(); // 关闭文件。
/**/ /* 打开文件并设置成从中读取对象。 */
FileInputStream istream = new FileInputStream( " tree.tmp " );
ObjectInputStream q = new ObjectInputStream(istream);
/**/ /* 读取树对象,以及所有子树 */
tree new_tree = (tree)q.readObject();
new_tree.print( 2 ); // 打印出树形结构的最上面 2级
String name = (String)q.readObject();
System.out.println( " \n " + name);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
最后结果如下:
node 0
node 1
node 2
node 3
node 4
node 5
node 6
LiLy is 惠止南国
可以看到,在序列化的时候,writeObject与readObject之间的先后顺序。readObject将最先write的object read出来。用数据结构的术语来讲就姑且称之为先进先出吧!
在序列化时,有几点要注意的:
1:当一个对象被序列化时,只保存对象的非静态成员变量,不能保存任何的成员方法和静态的成员变量。
2:如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存。
3:如果一个可序列化的对象包含对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。我们可以将这个引用标记为transient,那么对象仍然可以序列化
还有我们对某个对象进行序列化时候,往往对整个对象全部序列化了,比如说类里有些数据比较敏感,不希望序列化,一个方法可以用transient来标识,另一个方法我们可以在类里重写
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException;
private void writeObject(java.io.ObjectOutputStream stream)
throws IOException
这二个方法!
示例:
import java.io. * ;
class ObjectSerialTest
{
public static void main(String[] args) throws Exception
{
Employee e1 = new Employee( " zhangsan " , 25 , 3000.50 );
Employee e2 = new Employee( " lisi " , 24 , 3200.40 );
Employee e3 = new Employee( " wangwu " , 27 , 3800.55 );
FileOutputStream fos = new FileOutputStream( " employee.txt " );
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(e1);
oos.writeObject(e2);
oos.writeObject(e3);
oos.close();
FileInputStream fis = new FileInputStream( " employee.txt " );
ObjectInputStream ois = new ObjectInputStream(fis);
Employee e;
for ( int i = 0 ;i < 3 ;i ++ )
{
e = (Employee)ois.readObject();
System.out.println(e.name + " : " + e.age + " : " + e.salary);
}
ois.close();
}
}
class Employee implements Serializable
{
String name;
int age;
double salary;
transient Thread t = new Thread();
public Employee(String name, int age, double salary)
{
this .name = name;
this .age = age;
this .salary = salary;
}
private void writeObject(java.io.ObjectOutputStream oos) throws IOException
{
oos.writeInt(age);
oos.writeUTF(name);
System.out.println( " Write Object " );
}
private void readObject(java.io.ObjectInputStream ois) throws IOException
{
age = ois.readInt();
name = ois.readUTF();
System.out.println( " Read Object " );
}
}
--(add on 2006/6/28)
参考资料:JDK1.5 API DOC 孙鑫老师资料
1、实现Serializable回导致发布的API难以更改,并且使得package-private和private
这两个本来封装的较好的咚咚也不能得到保障了
2、Serializable会为每个类生成一个序列号,生成依据是类名、类实现的接口名、
public和protected方法,所以只要你一不小心改了一个已经publish的API,并且没有自
己定义一个long类型的叫做serialVersionUID的field,哪怕只是添加一个getXX,就会
让你读原来的序列化到文件中的东西读不出来(不知道为什么要把方法名算进去?)
3、不用构造函数用Serializable就可以构造对象,看起来不大合理,这被称为
extralinguistic mechanism,所以当实现Serializable时应该注意维持构造函数中所维
持的那些不变状态
4、增加了发布新版本的类时的测试负担
5、1.4版本后,JavaBeans的持久化采用基于XML的机制,不再需要Serializable
6、设计用来被继承的类时,尽量不实现Serializable,用来被继承的interface也不要
继承Serializable。但是如果父类不实现Serializable接口,子类很难实现它,特别是
对于父类没有可以访问的不含参数的构造函数的时候。所以,一旦你决定不实现
Serializable接口并且类被用来继承的时候记得提供一个无参数的构造函数
7、内部类还是不要实现Serializable好了,除非是static的,(偶也觉得内部类不适合
用来干这类活的)
8、使用一个自定义的序列化方法
看看下面这个保存一个双向链表的例子:
public class StringList implements Serializable
{
private int size = 0;
private Entry head = null;
private static class Entry implements Serializable
{
String data;
Entry next;
Entry previous;
}
...//Remainder ommitted
}
这样会导致链表的每个元素以及元素之间的关系(双向链表之间的连接)
都保存下来,更好的方法是提供一个自定义的序列化如下:
//String List with a resonable custom serialized form
class StringList implements Serializable
{
private transient int size = 0; //!transient
private transient Entry head = null; //!transient
//no longer serializable!
private static class Entry
{
String data;
Entry next;
Entry previous;
}
//Appends the specified string to the list
public void add(String s) {/*...*/};
/**
* Serialize this StringList instance
* @author yuchifang
* @serialData The size of the list (the number of strings
* it contains) is emitted(int), in the proper sequence
*/
private void writeObject(ObjectOutputStream s)
throws IOException
{
s.defaultWriteObject();
s.writeInt(size);
//Write out all elements in the proper order
for (Entry e = head; e != null; e = e.next)
s.writeObject(e.data);
}
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
{
int numElements = s.readInt();
//Read in all elements andd insert them in list
for (int i = 0; i < numElements; i++)
add((String)s.readObject());
}
//...remainder omitted
}
9、不管你选择什么序列化形式,声明一个显式的UID:
private static final long serialVersionUID = randomLongValue;
10、不需要序列化的东西使用transient注掉它吧,别什么都留着
11、writeObject/readObject重载以完成更好的序列化
readResolve 与 writeReplace重载以完成更好的维护invariant controllers
MarshalByRefObject和Serializable
最近在看web sevice 方面的东西,顺便看了下序列化,懂了不少啊 :
从MarshalByRefObject派生的类和有[Serializable]的类都可以跨越应用程序域作为参数传递。
从MarshalByRefObject派生的类按引用封送,有[Serializable]标志的类,按值封送。
如果此类即从MarshalByRefObject派生,也有[Serializable]标志也是按引用封送。
序列化有3种情况:
序列化为XML格式:
在webservice里,写个web method,传个自定义类做参数,就是这种情况。系统会帮你搞定,把自定义的类转换为默认XML格式。
序列化为2进制:
要加[Serializable]标志,可以把私有变量和公共变量都序列化。
序列化为soap格式:
需要实现ISerializable接口,定义序列化函数ISerializable.GetObjectData,和还原序列化的构造函数。
一个soap参数类的sample:
[Serializable]
public class serialze:ISerializable
{
// 序列化函数,由 SoapFormatter 在序列化过程中调用
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext
ctxt)
{
// 向 SerializationInfo 对象中添加每个字段
info.AddValue("UserName", UserName);
info.AddValue("UserID",UserID);
}
// 还原序列化构造函数,由 SoapFormatter 在还原序列化过程中调用
public serialze(SerializationInfo info, StreamingContext ctxt)
{
// 从 SerializationInfo 对象中还原序列化出各个字段
UserName = (string)info.GetValue("UserName", typeof(string));
UserID = (int) info.GetValue("UserID",typeof(int));
}
public serialze()
{}
public string UserName;
public int UserID;
}
是的,如果Session要存到数据库中就必须添加Serializable标记 [/size]