網頁

2019/7/24

SpotBugs Bug: Class [Class] defines non-transient non-serializable instance field [field]

當用SpotBugs檢測程式碼時,出現關於Serializable的警告訊息如下。

Bug: Class simple.People defines non-transient non-serializable instance field play

This Serializable class defines a non-primitive instance field which is neither transient, Serializable, or java.lang.Object, and does not appear to implement the Externalizable interface or the readObject() and writeObject() methods. Objects of this class will not be deserialized correctly if a non-Serializable object is stored in this field.

Rank: Troubling (14), confidence: High
Pattern: SE_NO_SUITABLE_CONSTRUCTOR_FOR_EXTERNALIZATION
Type: Se, Category: BAD_PRACTICE (Bad practice)


意思是說這個Class為可序列化的類別(Serializable class)(也就是此類別有實作Serializable或其繼承的父類別為可序列化類別),而其成員變數(field)中有不可序列化的實例。這樣的狀況以SpotBugs掃描程式碼時就會出現如上警示。


例如定義一個People類別實作Serializable介面如下,所以People為可序列化的類別。

如果有一成員變數Play為未序列化(Non-serializable)的類別。

People

import java.io.Serializable;

public class People implements Serializable{
    private static final long serialVersionUID = 1L;
    
    private String name;
    private Play play; // <-- SpotBugs
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Play getPlay() {
        return play;
    }
    public void setPlay(Play play) {
        this.play = play;
    }

}

下面是未實作Serializable介面的類別Play,為非序列化類別(Non-serializable class)。

Play

public class Play {
    ...
}

這Bug出現的原因是,序列化類別(People)中的成員變數都必須也是可序列化的類別,若有非序列化的成員變數(Play),可利用以下方法解決:

  • 該成員變數必須使其序列化;
  • 或在非序列化成員變數前加上transient關鍵字;
  • 或序列化類別實作private void readObject()private void writeObject()方法;
  • 或序列化類別實作Externalizable介面。

若未使用以上任一方法,則序程式進行序列化及反序列化的過程會出錯。


該成員變數必須使其序列化

Play

public class Play implements Serializable {
    ...
}


在非序列化成員變數前加上transient關鍵字

People

import java.io.Serializable;

public class People implements Serializable{
    private static final long serialVersionUID = 1L;
    
    private String name;
    private transient Play play; // <-- 加上 transient
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Play getPlay() {
        return play;
    }
    public void setPlay(Play play) {
        this.play = play;
    }

}


序列化類別實作private void readObject()private void writeObject()方法。實作此方法是當須要對序列化及反序列化做特殊處理實,則須要實作這兩個方法,請參考ObjectOutputStreamAPI文件說明如下:

Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:

private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException;
private void writeObject(java.io.ObjectOutputStream stream) throws IOException
private void readObjectNoData() throws ObjectStreamException;

下面範例是將此序列化類別變成不可序列化,否則會拋出NotSerializableException

import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class People implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String name;
    private Play play;
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        throw new NotSerializableException("Not Allowed");
    }
    
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        throw new NotSerializableException("Not Allowed!");
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Play getPlay() {
        return play;
    }
    public void setPlay(Play play) {
        this.play = play;
    }

}


序列化類別實作Externalizable介面。

實作Externalizable的類別必須提供不帶參數的建構式,否則會出現java.io.InvalidClassException錯誤。

People

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class People implements Externalizable {

    private String name;
    private Play play;
    
    // 不帶參數的建構式
    public People() {
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(this.name);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.name = in.readObject().toString();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Play getPlay() {
        return play;
    }

    public void setPlay(Play play) {
        this.play = play;
    }

}

參考:

沒有留言:

張貼留言