JAVA中的序列化與反序列化高級知識

一疹鳄、序列化

1、序列化的作用

Java平臺允許我們在內(nèi)存中創(chuàng)建可復(fù)用的Java對象篓冲,但一般情況下李破,只有當JVM處于運行時,這些對象才可能存在壹将,即嗤攻,這些對象的生命周期不會比JVM的生命周期更長。但在現(xiàn)實應(yīng)用中诽俯,就可能要求在JVM停止運行之后能夠保存(持久化)指定的對象妇菱,并在將來重新讀取被保存的對象。Java對象序列化就能夠幫助我們實現(xiàn)該功能惊畏。

2恶耽、序列化的原理

使用Java序列化,在保存對象時會將對象的狀態(tài)保存為一組字節(jié)颜启,在反序列化時偷俭,又將這些字節(jié)組裝成對象,切記缰盏,對象序列化涌萤,序列化的是這個對象的狀態(tài),即口猜,序列化的是他的成員變量负溪,因為對象的序列化是不關(guān)注類中的靜態(tài)變量的。在進行序列化的時候济炎,不會調(diào)用被序列化對象的任何構(gòu)造器川抡。

3、序列化使用的場景

  • 在持久化對象時须尚,使用序列化崖堤。
  • 使用RMI(遠程方法調(diào)用),或在網(wǎng)絡(luò)中傳輸對象時耐床,進行對象的序列化密幔,

4、默認的序列化機制

如果僅僅只是讓某個類實現(xiàn)Serializable接口撩轰,而沒有其它任何處理的話胯甩,則就是使用默認序列化機制昧廷。使用默認機制,在序列化對象時偎箫,不僅會序列化當前對象本身木柬,還會對該對象引用的其它對象也進行序列化,同樣地淹办,這些其它對象引用的另外對象也將被序列化弄诲,以此類推。所以娇唯,如果一個對象包含的成員變量是容器類對象齐遵,而這些容器所含有的元素也是容器類對象,那么這個序列化的過程就會較復(fù)雜塔插,開銷也較大梗摇。

5、序列化的影響因素

  • transient關(guān)鍵字

當某個字段被聲明為transient后想许,默認序列化機制就會忽略該字段伶授。

public class Person implements Serializable {  
    ...  
    transient private Integer age = null;  
    ...  
} 
  • writeObject()方法與readObject()方法

被transient修飾的屬性,如果一定需要序列化流纹,可以在對應(yīng)的類中添加兩個方法:writeObject()與readObject()

public class Person implements Serializable {  
    ...  
    transient private Integer age = null;  
    ...  
 
    private void writeObject(ObjectOutputStream out) throws IOException {  
        out.defaultWriteObject();  
        out.writeInt(age);  
    }  
 
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {  
        in.defaultReadObject();  
        age = in.readInt();  
    }  
} 

測試類

public class SimpleSerial {  
 
    public static void main(String[] args) throws Exception {  
        File file = new File("person.out");  
 
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));  
        Person person = new Person("John", 101, Gender.MALE);  
        oout.writeObject(person);  
        oout.close();  
 
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));  
        Object newPerson = oin.readObject(); // 沒有強制轉(zhuǎn)換到Person類型  
        oin.close();  
        System.out.println(newPerson);  
    }  
} 

writeObject()方法中會先調(diào)用ObjectOutputStream中的defaultWriteObject()方法糜烹,該方法會執(zhí)行默認的序列化機制,在進行序列化時,會先忽略掉age字段漱凝。然后再調(diào)用writeInt()方法顯示地將age字段寫入到ObjectOutputStream中疮蹦。readObject()的作用則是針對對象的讀取,其原理與writeObject()方法相同茸炒。

writeObject()與readObject()都是private方法愕乎,是通過反射來調(diào)用的。

  • Externalizable接口

無論是使用transient關(guān)鍵字壁公,還是使用writeObject()和readObject()方法感论,其實都是基于Serializable接口的序列化。JDK中提供了另一個序列化接口--Externalizable紊册,使用該接口之后比肄,之前基于Serializable接口的序列化機制就將失效.

public class Person implements Externalizable {  
 
    private String name = null;  
 
    transient private Integer age = null;  
 
    private Gender gender = null;  
 
    public Person() {  
        System.out.println("none-arg constructor");  
    }  
 
    public Person(String name, Integer age, Gender gender) {  
        System.out.println("arg constructor");  
        this.name = name;  
        this.age = age;  
        this.gender = gender;  
    }  
 
    private void writeObject(ObjectOutputStream out) throws IOException {  
        out.defaultWriteObject();  
        out.writeInt(age);  
    }  
 
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {  
        in.defaultReadObject();  
        age = in.readInt();  
    }  
 
    @Override 
    public void writeExternal(ObjectOutput out) throws IOException {  
 
    }  
 
    @Override 
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {  
 
    }  
    ...  
} 

輸出結(jié)果為:

arg constructor  
none-arg constructor  
[null, null, null] 

注意:在上列的序列化中,你會發(fā)現(xiàn)囊陡,沒有獲取到任何的序列化信息芳绩,且調(diào)用了Person類的午餐構(gòu)造器

Externalizable繼承于Serializable,當使用該接口時关斜,序列化的細節(jié)需要由我們自己來完成示括。如上所示的代碼铺浇,由于writeExternal()與readExternal()方法未作任何處理痢畜,那么該序列化行為將不會保存/讀取任何一個字段。這也就是為什么輸出結(jié)果中所有字段的值均為空。

另外丁稀,使用Externalizable進行序列化時吼拥,當讀取對象時,會調(diào)用被序列化類的無參構(gòu)造器去創(chuàng)建一個新的對象线衫,然后再將被保存對象的字段的值分別填充到新對象中凿可,因此,在實現(xiàn)Externalizable接口的類必須要提供一個無參的構(gòu)造器授账,且它的訪問權(quán)限為public枯跑。

public class Person implements Externalizable {  
 
    private String name = null;  
 
    transient private Integer age = null;  
 
    private Gender gender = null;  
 
    public Person() {  
        System.out.println("none-arg constructor");  
    }  
 
    public Person(String name, Integer age, Gender gender) {  
        System.out.println("arg constructor");  
        this.name = name;  
        this.age = age;  
        this.gender = gender;  
    }  
 
    private void writeObject(ObjectOutputStream out) throws IOException {  
        out.defaultWriteObject();  
        out.writeInt(age);  
    }  
 
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {  
        in.defaultReadObject();  
        age = in.readInt();  
    }  
 
    @Override 
    public void writeExternal(ObjectOutput out) throws IOException {  
        out.writeObject(name);  
        out.writeInt(age);  
    }  
 
    @Override 
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {  
        name = (String) in.readObject();  
        age = in.readInt();  
    }  
    ...  
} 

輸出結(jié)果為:

arg constructor  
none-arg constructor  
[John, 31, null] 
  • readResolve()方法

當我們使用Singleton模式時,應(yīng)該是期望某個類的實例應(yīng)該是唯一的白热,但如果該類是可序列化的敛助,那么情況可能略有不同。

public class Person implements Serializable {  
 
    private static class InstanceHolder {  
        private static final Person instatnce = new Person("John", 31, Gender.MALE);  
    }  
 
    public static Person getInstance() {  
        return InstanceHolder.instatnce;  
    }  
 
    private String name = null;  
 
    private Integer age = null;  
 
    private Gender gender = null;  
 
    private Person() {  
        System.out.println("none-arg constructor");  
    }  
 
    private Person(String name, Integer age, Gender gender) {  
        System.out.println("arg constructor");  
        this.name = name;  
        this.age = age;  
        this.gender = gender;  
    }  
    ...  
} 
public class SimpleSerial {  
 
    public static void main(String[] args) throws Exception {  
        File file = new File("person.out");  
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));  
        oout.writeObject(Person.getInstance()); // 保存單例對象  
        oout.close();  
 
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));  
        Object newPerson = oin.readObject();  
        oin.close();  
        System.out.println(newPerson);  
 
        System.out.println(Person.getInstance() == newPerson); // 將獲取的對象與Person類中的單例對象進行相等性比較  
    }  
} 

輸出結(jié)果:

arg constructor  
[John, 31, MALE]  
false 

此時屋确,你會發(fā)現(xiàn)纳击,它已經(jīng)不再是一個單利對象,失去了單利的性質(zhì)攻臀,為了能在序列化過程仍能保持單例的特性焕数,可以在Person類中添加一個readResolve()方法,在該方法中直接返回Person的單例對象

public class Person implements Serializable {  
 
    private static class InstanceHolder {  
        private static final Person instatnce = new Person("John", 31, Gender.MALE);  
    }  
 
    public static Person getInstance() {  
        return InstanceHolder.instatnce;  
    }  
 
    private String name = null;  
 
    private Integer age = null;  
 
    private Gender gender = null;  
 
    private Person() {  
        System.out.println("none-arg constructor");  
    }  
 
    private Person(String name, Integer age, Gender gender) {  
        System.out.println("arg constructor");  
        this.name = name;  
        this.age = age;  
        this.gender = gender;  
    }  
 
    private Object readResolve() throws ObjectStreamException {  
        return InstanceHolder.instatnce;  
    }  
    ...  
} 

當再次執(zhí)行上方測試類的時候刨啸,執(zhí)行結(jié)果為:

arg constructor  
[John, 31, MALE]  
true 

無論是實現(xiàn)Serializable接口狗超,或是Externalizable接口,當從I/O流中讀取對象時暇屋,readResolve()方法都會被調(diào)用到炫隶。實際上就是用readResolve()中返回的對象直接替換在反序列化過程中創(chuàng)建的對象。

二仑荐、序列化知識結(jié)晶

1雕拼、序列化ID(serialVersionUID )的作用

private static final long serialVersionUID = 1L;

虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致粘招,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)

2啥寇、靜態(tài)變量序列化問題

public class Test implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    public static int staticVar = 5;
 
    public static void main(String[] args) {
        try {
            //初始時staticVar為5
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream("result.obj"));
            out.writeObject(new Test());
            out.close();
 
            //序列化后修改為10
            Test.staticVar = 10;
 
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
                    "result.obj"));
            Test t = (Test) oin.readObject();
            oin.close();
             
            //再讀取,通過t.staticVar打印新的值
            System.out.println(t.staticVar);
             
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
// 輸出結(jié)果為10

最后的輸出是 10洒扎,對于無法理解的讀者認為辑甜,打印的 staticVar 是從讀取的對象里獲得的,應(yīng)該是保存時的狀態(tài)才對袍冷。之所以打印 10 的原因在于序列化時磷醋,并不保存靜態(tài)變量,這其實比較容易理解胡诗,序列化保存的是對象的狀態(tài)邓线,靜態(tài)變量屬于類的狀態(tài)淌友,因此 序列化并不保存靜態(tài)變量。

3骇陈、父類序列化問題

要想將父類對象也序列化震庭,就需要讓父類也實現(xiàn)Serializable 接口。如果父類不實現(xiàn)的話的你雌,就 需要有默認的無參的構(gòu)造函數(shù)器联。

在父類沒有實現(xiàn) Serializable 接口時,虛擬機是不會序列化父對象的婿崭,而一個 Java 對象的構(gòu)造必須先有父對象拨拓,才有子對象,反序列化也不例外氓栈。所以反序列化時千元,為了構(gòu)造父對象,只能調(diào)用父類的無參構(gòu)造函數(shù)作為默認的父對象颤绕。因此當我們?nèi)「笇ο蟮淖兞恐禃r幸海,它的值是調(diào)用父類無參構(gòu)造函數(shù)后的值。如果你考慮到這種序列化的情況奥务,在父類無參構(gòu)造函數(shù)中對變量進行初始化物独,否則的話,父類變量值都是默認聲明的值氯葬,如 int 型的默認是 0挡篓,string 型的默認是 null。

4帚称、Transient序列化問題

Transient 關(guān)鍵字的作用是控制變量的序列化官研,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中闯睹,在被反序列化后戏羽,transient 變量的值被設(shè)為初始值,如 int 型的是 0楼吃,對象型的是 null始花。

5、對敏感字段加密

  • 場景

服務(wù)器端給客戶端發(fā)送序列化對象數(shù)據(jù)孩锡,對象中有一些數(shù)據(jù)是敏感的酷宵,比如密碼字符串等,希望對該密碼字段在序列化時躬窜,進行加密浇垦,而客戶端如果擁有解密的密鑰,只有在客戶端進行反序列化時荣挨,才可以對密碼進行讀取男韧,這樣可以一定程度保證序列化對象的數(shù)據(jù)安全朴摊。

  • 原理

在序列化過程中,虛擬機會試圖調(diào)用對象類里的 writeObject 和 readObject 方法煌抒,進行用戶自定義的序列化和反序列化,如果沒有這樣的方法厕倍,則默認調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法寡壮。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動態(tài)改變序列化的數(shù)值讹弯】黾龋基于這個原理,可以在實際應(yīng)用中得到使用组民,用于敏感字段的加密工作

private static final long serialVersionUID = 1L;
 
   private String password = "pass";
 
   public String getPassword() {
       return password;
   }
 
   public void setPassword(String password) {
       this.password = password;
   }
 
   // 對密碼進行了加密
   private void writeObject(ObjectOutputStream out) {
       try {
           PutField putFields = out.putFields();
           System.out.println("原密碼:" + password);
           password = "encryption";//模擬加密
           putFields.put("password", password);
           System.out.println("加密后的密碼" + password);
           out.writeFields();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
 
   // 對 password 進行解密,只有擁有密鑰的客戶端棒仍,才可以正確的解析出密碼,確保了數(shù)據(jù)的安全臭胜。
   private void readObject(ObjectInputStream in) {
       try {
           GetField readFields = in.readFields();
           Object object = readFields.get("password", "");
           System.out.println("要解密的字符串:" + object.toString());
           password = "pass";//模擬解密,需要獲得本地的密鑰
       } catch (IOException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
 
   }
 
   public static void main(String[] args) {
       try {
           ObjectOutputStream out = new ObjectOutputStream(
                   new FileOutputStream("result.obj"));
           out.writeObject(new Test());
           out.close();
 
           ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
                   "result.obj"));
           Test t = (Test) oin.readObject();
           System.out.println("解密后的字符串:" + t.getPassword());
           oin.close();
       } catch (FileNotFoundException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
   }

三莫其、序列化跟反序列化常識注意點

  • 1、在Java中耸三,只要一個類實現(xiàn)了java.io.Serializable接口乱陡,那么它就可以被序列化。

  • 2仪壮、通過ObjectOutputStream和ObjectInputStream對對象進行序列化及反序列化

  • 3憨颠、虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致积锅,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID)

  • 4爽彤、序列化并不保存靜態(tài)變量。

  • 5缚陷、要想將父類對象也序列化适篙,就需要讓父類也實現(xiàn)Serializable 接口。

  • 6箫爷、Transient 關(guān)鍵字的作用是控制變量的序列化匙瘪,在變量聲明前加上該關(guān)鍵字,可以阻止該變量被序列化到文件中蝶缀,在被反序列化后丹喻,transient 變量的值被設(shè)為初始值,如 int 型的是 0翁都,對象型的是 null碍论。

  • 7、服務(wù)器端給客戶端發(fā)送序列化對象數(shù)據(jù)柄慰,對象中有一些數(shù)據(jù)是敏感的鳍悠,比如密碼字符串等税娜,希望對該密碼字段在序列化時,進行加密藏研,而客戶端如果擁有解密的密鑰敬矩,只有在客戶端進行反序列化時,才可以對密碼進行讀取蠢挡,這樣可以一定程度保證序列化對象的數(shù)據(jù)安全弧岳。

  • 8、在序列化過程中业踏,如果被序列化的類中定義了writeObject 和 readObject 方法禽炬,虛擬機會試圖調(diào)用對象類里的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化勤家。如果沒有這樣的方法腹尖,則默認調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程伐脖,比如可以在序列化的過程中動態(tài)改變序列化的數(shù)值热幔。

四、實例

1讼庇、 進行反序列化断凶,并忽略某些不需要反序列化的屬性

package com.qianfan123.mbr.service.report;

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.hd123.rumba.commons.biz.entity.EntityNotFoundException;
import com.hd123.rumba.commons.biz.entity.OperateContext;
import com.hd123.rumba.commons.biz.entity.OperateInfo;
import com.hd123.rumba.commons.biz.entity.Operator;
import com.hd123.rumba.commons.biz.entity.StandardEntity;
import com.hd123.rumba.commons.biz.entity.VersionConflictException;
import com.hd123.rumba.commons.lang.Assert;
import com.qianfan123.mbr.annotation.Tx;
import com.qianfan123.mbr.api.MbrException;
import com.qianfan123.mbr.api.common.Nsid;
import com.qianfan123.mbr.api.consumption.Consumption;
import com.qianfan123.mbr.api.member.Member;
import com.qianfan123.mbr.dao.report.ReportEntity;
import com.qianfan123.mbr.dao.report.statistic.StatisticHistory;
import com.qianfan123.mbr.dao.report.statistic.StatisticHistoryDao;
import com.qianfan123.mbr.service.report.target.MemberDataTargetMarker;
import com.qianfan123.mbr.service.report.target.MemberOccuredAtDataTargetMarker;
import com.qianfan123.mbr.service.report.target.OccuredAtConsumptionDataTargetMarker;
import com.qianfan123.mbr.service.report.target.OccuredAtDailyConsumptionDataTargetMarker;
import com.qianfan123.mbr.service.report.target.OccuredAtDailyMemberDataTargetMarker;
import com.qianfan123.mbr.service.report.target.OccuredAtDailyRegisteredDataTargetMarker;
import com.qianfan123.mbr.service.report.target.OccuredAtMemberDataTargetMarker;
import com.qianfan123.mbr.service.report.target.TargetMarker;
import com.qianfan123.mbr.service.report.target.TenantConsumptionDataTargerMarker;
import com.qianfan123.mbr.service.report.target.TenantMemberDataTargerMarker;
import com.qianfan123.mbr.service.report.util.StatisticDataReverseUtils;
import com.qianfan123.mbr.utils.EntityUtils;

/**
 * 報表統(tǒng)計服務(wù)抽象類
 * 
 * @author huzexiong
 *
 */
@Component
public abstract class AbstractStatisticService<S extends StandardEntity> //
    implements StatisticService, ApplicationContextAware {

  private final Logger logger = LoggerFactory.getLogger(AbstractStatisticService.class);

  private ApplicationContext applicationContext;

  // 根據(jù)統(tǒng)計來源來獲取統(tǒng)計元數(shù)據(jù)工廠
  private Map<Class<?>, MakerFactory> makerFactories = new HashMap<Class<?>, MakerFactory>();

  // 報表目標數(shù)據(jù)
  private List<TargetMarker> targetMarkers = new ArrayList<TargetMarker>();

  // 用于反序列化歷史統(tǒng)計數(shù)據(jù)對象
  private static final ObjectMapper MAPPER;

  static {
    MAPPER = new ObjectMapper();
    MAPPER.setSerializationInclusion(NON_NULL);
    MAPPER.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
    MAPPER.addMixIn(Member.class, MemberMixIn.class);
    final SimpleModule module = new SimpleModule();
    module.addDeserializer(Nsid.class, new JsonDeserializer<Nsid>() {
      @Override
      public Nsid deserialize(JsonParser jp, DeserializationContext ctxt)
          throws IOException, JsonProcessingException {
        JsonNode node = jp.readValueAsTree();
        return new Nsid(node.get("namespace").asText(), node.get("id").asText());
      }
    });
    MAPPER.registerModule(module);
  }

  /**
   * 在反序列化時,忽略Member中的: fetchParts 屬性 因為 fetchParts 對應(yīng)兩個set方法巫俺,反序列化會沖突
   * 
   * @author wangxiaofeng
   *
   */
  static abstract class MemberMixIn {
    @JsonIgnore
    abstract Set<String> getFetchParts();
  }

  @Autowired
  private StatisticHistoryDao statisticHistoryDao;

  @PostConstruct
  public void init() {
    makerFactories.put(Member.class, applicationContext.getBean(MemberMakerFactory.class));
    makerFactories.put(Consumption.class,
        applicationContext.getBean(ConsumptionMakerFactory.class));

    targetMarkers.add(applicationContext.getBean(MemberDataTargetMarker.class));
    targetMarkers.add(applicationContext.getBean(TenantConsumptionDataTargerMarker.class));
    targetMarkers.add(applicationContext.getBean(TenantMemberDataTargerMarker.class));
    targetMarkers.add(applicationContext.getBean(OccuredAtConsumptionDataTargetMarker.class));
    targetMarkers.add(applicationContext.getBean(OccuredAtMemberDataTargetMarker.class));
    targetMarkers.add(applicationContext.getBean(OccuredAtDailyConsumptionDataTargetMarker.class));
    targetMarkers.add(applicationContext.getBean(OccuredAtDailyMemberDataTargetMarker.class));
    targetMarkers.add(applicationContext.getBean(OccuredAtDailyRegisteredDataTargetMarker.class));
    targetMarkers.add(applicationContext.getBean(MemberOccuredAtDataTargetMarker.class));
  }

  /**
   * 獲取統(tǒng)計數(shù)據(jù)源
   * 
   * @param tenant
   *          租戶认烁, not null
   * @param uuid
   *          UUID, not null
   * @return
   */
  public abstract S get(String tenant, String uuid);

  public abstract Class<?> getClassType();

  @Tx
  @Override
  public void run(String tenant, String uuid) throws MbrException, VersionConflictException {
    Assert.hasText(tenant);
    Assert.hasText(uuid);

    // 查詢統(tǒng)計歷史信息
    StatisticHistory statisticHistory = statisticHistoryDao.get(tenant, uuid);
    S last = restore(statisticHistory);

    // 根據(jù)不同的統(tǒng)計來源獲取對應(yīng)的元數(shù)據(jù)工廠
    MakerFactory makerFactory = makerFactories.get(getClassType());

    try {
      // 先對歷史數(shù)據(jù)進行反向操作介汹,并持久化
      if (null != last) {
        builder(last, makerFactory, -1);
      }

      // 獲取統(tǒng)計源
      S source = get(tenant, uuid);
      if (null != source) {
        builder(source, makerFactory, 1);
      }
      if (null == last && null == source) {
        logger.info("未查詢到源數(shù)據(jù)和歷史數(shù)據(jù):tenant[{}],uuid[{}],class[{}],統(tǒng)計忽略却嗡。", //
            tenant, uuid, getClassType().getName());
        return;
      }

      // 統(tǒng)計完成,記錄歷史嘹承。
      if (null == last) {// 新增
        insertHistory(tenant, source);
      } else if (null != source) {// 更新
        modifyHistory(statisticHistory, source);
      } else {// 刪除
        removeHistory(statisticHistory);
      }
    } catch (Exception e) {
      e.printStackTrace();
      logger.error("報表統(tǒng)計異常:" + e.getMessage());
      throw new MbrException(e);
    }

  }

  /**
   * 對獲取到的歷史統(tǒng)計數(shù)據(jù)信息進行反序列化操作
   * 
   * @param statisticHistory
   * @return
   * @throws MbrException
   */
  private S restore(StatisticHistory statisticHistory) throws MbrException {
    if (null == statisticHistory) {
      return null;
    }
    S last = null;
    try {
      last = (S) MAPPER.readValue(statisticHistory.getData(), getClassType());
    } catch (IOException e) {
      throw new MbrException(e);
    }
    return last;
  }

  /**
   * 生成報表數(shù)據(jù)并進行持久化操作
   * 
   * @param source
   *          統(tǒng)計數(shù)據(jù)源窗价,not null
   * @param makerFactory
   *          相應(yīng)的統(tǒng)計元數(shù)據(jù)工廠 , not null
   * @param base
   *          1 或 -1 叹卷,表示是歷史數(shù)據(jù)還是新的統(tǒng)計源數(shù)據(jù)
   */
  private void builder(S source, MakerFactory makerFactory, int base) throws Exception,
      VersionConflictException, IllegalArgumentException, EntityNotFoundException {

    // 根據(jù)傳入的統(tǒng)計來源撼港,產(chǎn)生相應(yīng)的元數(shù)據(jù)列表
    List<Object> metaDatas = makerFactory.makeValues(source);

    // base = -1 表示是歷史表中的統(tǒng)計數(shù)據(jù)
    if (-1 == base) {
      StatisticDataReverseUtils.reverse(metaDatas, -1);
    }

    // 進行報表數(shù)據(jù)更新與保存等操作
    for (TargetMarker targetMarker : targetMarkers) {
      if (getClassType() != targetMarker.getSourceType()) {
        continue;
      }

      // 根據(jù)元統(tǒng)計數(shù)據(jù)和來源數(shù)據(jù)生成報表數(shù)據(jù)
      ReportEntity report = targetMarker.make(source, metaDatas);
      if (null == report) {
        continue;
      }

      // 獲取數(shù)據(jù)庫統(tǒng)計數(shù)據(jù)信息
      ReportEntity db = targetMarker.getReportEntityDao().getBy(report);

      // 如果第一次生成報表數(shù)據(jù)信息,則進行插入操作骤竹,否則帝牡,先進行數(shù)據(jù)合并,再更新
      if (null == db) {
        EntityUtils.setOperateInfo(report, getOperationContext());
        targetMarker.getReportEntityDao().insert(report);
      } else {
        // 合并數(shù)據(jù)
        ReportEntity merge = targetMarker.merge(report, db);
        EntityUtils.setOperateInfo(merge, getOperationContext());
        targetMarker.getReportEntityDao().update(merge);
      }
    }
  }

  /**
   * 插入歷史統(tǒng)計數(shù)據(jù)
   * 
   * @param tenant
   *          租戶
   * @param entity
   *          相應(yīng)實體
   * @return
   * @throws JsonProcessingException
   */
  private StatisticHistory insertHistory(String tenant, S entity) throws JsonProcessingException {
    StatisticHistory his = new StatisticHistory();
    his.setTenant(tenant);
    his.setStatisticUuid(entity.getUuid());
    his.setLastUpdated(getLastUpdated(entity));
    his.setData(MAPPER.writeValueAsString(entity));
    EntityUtils.setOperateInfo(his, getOperationContext());
    statisticHistoryDao.insert(his);
    return his;
  }

  private Date getLastUpdated(S entity) {
    OperateInfo operateInfo = entity.getLastModifyInfo();
    Assert.notNull(operateInfo);
    return operateInfo.getTime();
  }

  private void modifyHistory(StatisticHistory his, S entity)
      throws JsonProcessingException, VersionConflictException {
    his.setLastUpdated(getLastUpdated(entity));
    his.setData(MAPPER.writeValueAsString(entity));
    EntityUtils.setLastModifyInfo(his, getOperationContext());
    statisticHistoryDao.update(his);
  }

  private void removeHistory(StatisticHistory his) throws VersionConflictException {
    statisticHistoryDao.delete(his);
  }

  protected OperateContext getOperationContext() {
    OperateContext context = new OperateContext();
    context.setOperator(new Operator("統(tǒng)計報表", null));
    context.setTime(new Date());
    return context;
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒙揣,一起剝皮案震驚了整個濱河市靶溜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖罩息,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗤详,死亡現(xiàn)場離奇詭異,居然都是意外死亡瓷炮,警方通過查閱死者的電腦和手機葱色,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娘香,“玉大人苍狰,你說我怎么就攤上這事∶┲鳎” “怎么了舞痰?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵土榴,是天一觀的道長诀姚。 經(jīng)常有香客問我,道長玷禽,這世上最難降的妖魔是什么赫段? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮矢赁,結(jié)果婚禮上糯笙,老公的妹妹穿的比我還像新娘。我一直安慰自己撩银,他們只是感情好给涕,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著额获,像睡著了一般够庙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抄邀,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天耘眨,我揣著相機與錄音,去河邊找鬼境肾。 笑死剔难,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的奥喻。 我是一名探鬼主播偶宫,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼环鲤!你這毒婦竟也來了读宙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤楔绞,失蹤者是張志新(化名)和其女友劉穎结闸,沒想到半個月后唇兑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡桦锄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年扎附,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片结耀。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡留夜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出图甜,到底是詐尸還是另有隱情碍粥,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布黑毅,位于F島的核電站嚼摩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏矿瘦。R本人自食惡果不足惜枕面,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缚去。 院中可真熱鬧潮秘,春花似錦、人聲如沸易结。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搞动。三九已至躏精,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間滋尉,已是汗流浹背玉控。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狮惜,地道東北人高诺。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像碾篡,于是被迫代替她去往敵國和親虱而。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容

  • JAVA序列化機制的深入研究 對象序列化的最主要的用處就是在傳遞,和保存對象(object)的時候,保證對象的完整...
    時待吾閱讀 10,868評論 0 24
  • 官方文檔理解 要使類的成員變量可以序列化和反序列化开泽,必須實現(xiàn)Serializable接口牡拇。任何可序列化類的子類都是...
    獅_子歌歌閱讀 2,411評論 1 3
  • 一、 序列化和反序列化概念 Serialization(序列化)是一種將對象以一連串的字節(jié)描述的過程;反序列化de...
    步積閱讀 1,443評論 0 10
  • 感恩朋友慷慨大度中午請吃信陽菜惠呼,吃完午飯一起逛街导俘,慷慨大方的為自己買了喜歡的包、羊絨大衣和漂亮的圍巾及帽子剔蹋÷帽。花錢的...
    開荒者cx閱讀 197評論 0 1
  • 有一天小鴨子出去玩兒。 不小心掉到了坑里泣崩,這時小兔和小貓看見了少梁,他們想辦法把小鴨子救出來。他們從小河里弄水回來了矫付,...
    北極星的眼淚_4aa7閱讀 275評論 0 0