[Java] 印出物件內容的好幫手 – ReflectionToStringBuilder (3) 客製化輸出格式 ToStringStyle 範例

ReflectionToStringBuilder 除了能排除特定屬性名稱,還能對印出內容的格式進行客製化,達到更靈活的效果。

例如:自訂日期格式長相、敏感欄位資料馬賽克等等。

要達到輸出客製化,需要實作 ToStringStyle 抽象類別。本篇將就自訂日期輸出格式長相以及敏感欄位資料馬賽克這兩個效果進行示範。

利用 ToStringStyle 自訂輸出的日期格式長相

以下面這個例子作示範:

EX1: 原始範例

public class Demo1
{
    class Mobile extends BaseObj
    {
        private String brand = "Apple";
        private String os = "iOS 10";
        private String type = "iPhone 7";
        private String owner;
        private Date buyDate = new Date();
        private String manufacturer = "OneJar Inc.";
    }

    class BaseObj
    {
        @Override
        public String toString()
        {
            return ReflectionToStringBuilder.toString( this );
        }
    }

    @Test
    public void ex01_ReflectionToStringBuilder_toStringStyleExample()
    {
        Mobile myPhone = new Mobile();
        System.out.println( myPhone );
    }
}

輸出結果:

[email protected][brand=Apple,os=iOS 10,type=iPhone 7,owner=<null>,buyDate=Sun Jun 18 14:55:04 CST 2017,manufacturer=OneJar Inc.]

Date 類別原生的 toString() 的輸出結果如上所見。

如果我們希望自行定義輸出格式,例如典型的「yyyy-MM-dd」,可以透過實作 ToStringStyle 抽象類別加以自訂。

EX2-a: 實作 ToStringStyle 客製化輸出格式類別

import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.builder.ToStringStyle;

@SuppressWarnings("serial")
public class OneJarToStringStyle extends ToStringStyle
{
    public OneJarToStringStyle() {}

    @Override
    protected void appendDetail(StringBuffer buffer, String fieldName, Object valueObj)
    {
        buffer.append( getProcessedString( fieldName, valueObj ) );
    }

    private String getProcessedString(String fieldName, Object valueObj)
    {
        if (valueObj instanceof Date) // 如果物件是 Date 類型
        {
            return new SimpleDateFormat( "yyyy-MM-dd" ).format( valueObj );
        }
        else
        {
            return String.valueOf( valueObj ); // 呼叫物件的 toString();若為 null 則回傳 "null"
        }
    }
}

客製化輸出格式類別實作的基本要點:

  1. 繼承 ToStringStyle 抽象類別。
  2. 覆寫 appendDetail() 方法。當欲輸出的物件不為 null 時,會呼叫此方法,允許輸出格式進行加工。加工內容由開發者在方法內自行定義。

然後呼叫 ReflectionToStringBuilder API 時,將客製的 ToStringStyle 類別加入參數即可:

EX2-b: 呼叫 ReflectionToStringBuilder API 加入客製 ToStringStyle 類別

/* Example 1 */
System.out.println( ReflectionToStringBuilder.toString( myPhone, new OneJarToStringStyle() ) );

/* Example 2 */
ReflectionToStringBuilder builder = new ReflectionToStringBuilder( myPhone, new OneJarToStringStyle() );
System.out.println( builder.toString() );

Example 1 和 2 分別是靜態呼叫和建立實體的寫法,效果一樣:

輸出結果:
[email protected][brand=Apple,os=iOS 10,type=iPhone 7,owner=<null>,buyDate=2017-06-18,manufacturer=OneJar Inc.]

透過上述寫法,成功對 buyDate 這個 Date 類別的屬性進行格式客製化。

此外,此例也證實了,若輸出對象是 null (例如 owner 屬性),則不會觸發到 appendDetail()。否則輸出結果應該是「owner=null」,而不會是預設風格的「<null>」。

利用 ToStringStyle 實現敏感欄位馬賽克的效果

專案開發時,有時候類別屬性會記錄一些敏感資訊,例如客戶隱私資料、產品機密資訊。log 需要紀錄這個屬性有值,但不方便明碼紀錄於 log 中,需要在輸出物件時自動對特定欄位的內容進行加密或是馬賽克。透過 ToStringStyle 能輕易實現這個效果。

延續前面的例子,對客製的 ToStringStyle 類別增加功能:

EX3-a: 在 ToStringStyle 客製化類別增加敏感欄位功能

import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.builder.ToStringStyle;

@SuppressWarnings("serial")
public class OneJarToStringStyle extends ToStringStyle
{
    private String[] sensitiveFieldAry = new String[0]; // 紀錄敏感資訊欄位名稱的變數

    public OneJarToStringStyle() {}
    
    public OneJarToStringStyle(String[] sensitiveFieldAry) // 增加建構子,允許設定敏感欄位名稱
    {
        this.sensitiveFieldAry = sensitiveFieldAry;
    }

    @Override
    protected void appendDetail(StringBuffer buffer, String fieldName, Object valueObj)
    {
        buffer.append( getProcessedString( fieldName, valueObj ) );
    }

    private String getProcessedString(String fieldName, Object valueObj)
    {
        if (ArrayUtils.contains( sensitiveFieldAry, fieldName )) // 如果是敏感資訊欄位,則資料加以馬賽克
        {
            return "***";
        }

        if (valueObj instanceof Date)
        {
            return new SimpleDateFormat( "yyyy-MM-dd" ).format( valueObj );
        }
        else
        {
            return String.valueOf( valueObj );
        }
    }
}

呼叫 ReflectionToStringBuilder API 時,對客製 ToStringStyle 類別增加參數,指定敏感欄位的名稱:

EX3-b: ReflectionToStringBuilder 對自訂類別的應用

String[] sensitiveFields = new String[] { "manufacturer" };
System.out.println( ReflectionToStringBuilder.toString( myPhone, new OneJarToStringStyle( sensitiveFields ) ) );
輸出結果:
[email protected][brand=Apple,os=iOS 10,type=iPhone 7,owner=<null>,buyDate=2017-06-18,manufacturer=***]

敏感欄位的性質和排除欄位一樣,通常都是在開發設計階段就會決定,而且每個類別都可能有此需求。因此實務開發時,可比照上一篇介紹排除欄位 toStringExclude 實作的設計方式,利用 BaseObj 進行實現。

小結

本篇對 ToStringStyle 的寫法和應用進行簡易的示範,相信已經能感受到 ToStringStyle 在支援各種效果上的靈活性。本篇示範的兩個應用是我個人認為非常實用的兩種效果。

業界實際的專案,因應業務可能有更多各式各樣的需求情境,相信 ToStringStyle 有機會實現更有趣的應用。

發表留言