JAVA中的序列化與反序列化高級(jí)知識(shí)

一、序列化

1、序列化的作用

Java平臺(tái)允許我們?cè)趦?nèi)存中創(chuàng)建可復(fù)用的Java對(duì)象,但一般情況下,只有當(dāng)JVM處于運(yùn)行時(shí),這些對(duì)象才可能存在,即,這些對(duì)象的生命周期不會(huì)比JVM的生命周期更長(zhǎng)。但在現(xiàn)實(shí)應(yīng)用中,就可能要求在JVM停止運(yùn)行之后能夠保存(持久化)指定的對(duì)象,并在將來重新讀取被保存的對(duì)象。Java對(duì)象序列化就能夠幫助我們實(shí)現(xiàn)該功能。

2、序列化的原理

使用Java序列化,在保存對(duì)象時(shí)會(huì)將對(duì)象的狀態(tài)保存為一組字節(jié),在反序列化時(shí),又將這些字節(jié)組裝成對(duì)象,切記,對(duì)象序列化,序列化的是這個(gè)對(duì)象的狀態(tài),即,序列化的是他的成員變量,因?yàn)閷?duì)象的序列化是不關(guān)注類中的靜態(tài)變量的。在進(jìn)行序列化的時(shí)候,不會(huì)調(diào)用被序列化對(duì)象的任何構(gòu)造器

3、序列化使用的場(chǎng)景

  • 在持久化對(duì)象時(shí),使用序列化。
  • 使用RMI(遠(yuǎn)程方法調(diào)用),或在網(wǎng)絡(luò)中傳輸對(duì)象時(shí),進(jìn)行對(duì)象的序列化,

4、默認(rèn)的序列化機(jī)制

如果僅僅只是讓某個(gè)類實(shí)現(xiàn)Serializable接口,而沒有其它任何處理的話,則就是使用默認(rèn)序列化機(jī)制。使用默認(rèn)機(jī)制,在序列化對(duì)象時(shí),不僅會(huì)序列化當(dāng)前對(duì)象本身,還會(huì)對(duì)該對(duì)象引用的其它對(duì)象也進(jìn)行序列化,同樣地,這些其它對(duì)象引用的另外對(duì)象也將被序列化,以此類推。所以,如果一個(gè)對(duì)象包含的成員變量是容器類對(duì)象,而這些容器所含有的元素也是容器類對(duì)象,那么這個(gè)序列化的過程就會(huì)較復(fù)雜,開銷也較大。

5、序列化的影響因素

  • transient關(guān)鍵字

當(dāng)某個(gè)字段被聲明為transient后,默認(rèn)序列化機(jī)制就會(huì)忽略該字段。

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

被transient修飾的屬性,如果一定需要序列化,可以在對(duì)應(yīng)的類中添加兩個(gè)方法: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();  
    }  
} 

測(cè)試類

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(); // 沒有強(qiáng)制轉(zhuǎn)換到Person類型  
        oin.close();  
        System.out.println(newPerson);  
    }  
} 

writeObject()方法中會(huì)先調(diào)用ObjectOutputStream中的defaultWriteObject()方法,該方法會(huì)執(zhí)行默認(rèn)的序列化機(jī)制,在進(jìn)行序列化時(shí),會(huì)先忽略掉age字段。然后再調(diào)用writeInt()方法顯示地將age字段寫入到ObjectOutputStream中。readObject()的作用則是針對(duì)對(duì)象的讀取,其原理與writeObject()方法相同。

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

  • Externalizable接口

無論是使用transient關(guān)鍵字,還是使用writeObject()和readObject()方法,其實(shí)都是基于Serializable接口的序列化。JDK中提供了另一個(gè)序列化接口--Externalizable,使用該接口之后,之前基于Serializable接口的序列化機(jī)制就將失效.

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] 

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

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

另外,使用Externalizable進(jìn)行序列化時(shí),當(dāng)讀取對(duì)象時(shí),會(huì)調(diào)用被序列化類的無參構(gòu)造器去創(chuàng)建一個(gè)新的對(duì)象,然后再將被保存對(duì)象的字段的值分別填充到新對(duì)象中,因此,在實(shí)現(xiàn)Externalizable接口的類必須要提供一個(gè)無參的構(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()方法

當(dāng)我們使用Singleton模式時(shí),應(yīng)該是期望某個(gè)類的實(shí)例應(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()); // 保存單例對(duì)象  
        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); // 將獲取的對(duì)象與Person類中的單例對(duì)象進(jìn)行相等性比較  
    }  
} 

輸出結(jié)果:

arg constructor  
[John, 31, MALE]  
false 

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

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;  
    }  
    ...  
} 

當(dāng)再次執(zhí)行上方測(cè)試類的時(shí)候,執(zhí)行結(jié)果為:

arg constructor  
[John, 31, MALE]  
true 

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

二、序列化知識(shí)結(jié)晶

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

private static final long serialVersionUID = 1L;

虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個(gè)非常重要的一點(diǎn)是兩個(gè)類的序列化 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 {
            //初始時(shí)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,對(duì)于無法理解的讀者認(rèn)為,打印的 staticVar 是從讀取的對(duì)象里獲得的,應(yīng)該是保存時(shí)的狀態(tài)才對(duì)。之所以打印 10 的原因在于序列化時(shí),并不保存靜態(tài)變量,這其實(shí)比較容易理解,序列化保存的是對(duì)象的狀態(tài),靜態(tài)變量屬于類的狀態(tài),因此 序列化并不保存靜態(tài)變量。

3、父類序列化問題

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

在父類沒有實(shí)現(xiàn) Serializable 接口時(shí),虛擬機(jī)是不會(huì)序列化父對(duì)象的,而一個(gè) Java 對(duì)象的構(gòu)造必須先有父對(duì)象,才有子對(duì)象,反序列化也不例外。所以反序列化時(shí),為了構(gòu)造父對(duì)象,只能調(diào)用父類的無參構(gòu)造函數(shù)作為默認(rèn)的父對(duì)象。因此當(dāng)我們?nèi)「笇?duì)象的變量值時(shí),它的值是調(diào)用父類無參構(gòu)造函數(shù)后的值。如果你考慮到這種序列化的情況,在父類無參構(gòu)造函數(shù)中對(duì)變量進(jìn)行初始化,否則的話,父類變量值都是默認(rèn)聲明的值,如 int 型的默認(rèn)是 0,string 型的默認(rèn)是 null。

4、Transient序列化問題

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

5、對(duì)敏感字段加密

  • 場(chǎng)景

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

  • 原理

在序列化過程中,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類里的 writeObject 和 readObject 方法,進(jìn)行用戶自定義的序列化和反序列化,如果沒有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動(dòng)態(tài)改變序列化的數(shù)值?;谶@個(gè)原理,可以在實(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;
   }
 
   // 對(duì)密碼進(jìn)行了加密
   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();
       }
   }
 
   // 對(duì) password 進(jìn)行解密,只有擁有密鑰的客戶端,才可以正確的解析出密碼,確保了數(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();
       }
   }

三、序列化跟反序列化常識(shí)注意點(diǎn)

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

  • 2、通過ObjectOutputStream和ObjectInputStream對(duì)對(duì)象進(jìn)行序列化及反序列化

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

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

  • 5、要想將父類對(duì)象也序列化,就需要讓父類也實(shí)現(xiàn)Serializable 接口。

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

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

  • 8、在序列化過程中,如果被序列化的類中定義了writeObject 和 readObject 方法,虛擬機(jī)會(huì)試圖調(diào)用對(duì)象類里的 writeObject 和 readObject 方法,進(jìn)行用戶自定義的序列化和反序列化。如果沒有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動(dòng)態(tài)改變序列化的數(shù)值。

四、實(shí)例

1、 進(jìn)行反序列化,并忽略某些不需要反序列化的屬性

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;

/**
 * 報(bào)表統(tǒng)計(jì)服務(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)計(jì)來源來獲取統(tǒng)計(jì)元數(shù)據(jù)工廠
  private Map<Class<?>, MakerFactory> makerFactories = new HashMap<Class<?>, MakerFactory>();

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

  // 用于反序列化歷史統(tǒng)計(jì)數(shù)據(jù)對(duì)象
  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);
  }

  /**
   * 在反序列化時(shí),忽略Member中的: fetchParts 屬性 因?yàn)?fetchParts 對(duì)應(yīng)兩個(gè)set方法,反序列化會(huì)沖突
   * 
   * @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)計(jì)數(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)計(jì)歷史信息
    StatisticHistory statisticHistory = statisticHistoryDao.get(tenant, uuid);
    S last = restore(statisticHistory);

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

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

      // 獲取統(tǒng)計(jì)源
      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)計(jì)忽略。", //
            tenant, uuid, getClassType().getName());
        return;
      }

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

  }

  /**
   * 對(duì)獲取到的歷史統(tǒng)計(jì)數(shù)據(jù)信息進(jìn)行反序列化操作
   * 
   * @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;
  }

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

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

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

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

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

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

      // 如果第一次生成報(bào)表數(shù)據(jù)信息,則進(jìn)行插入操作,否則,先進(jìn)行數(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)計(jì)數(shù)據(jù)
   * 
   * @param tenant
   *          租戶
   * @param entity
   *          相應(yīng)實(shí)體
   * @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)計(jì)報(bào)表", null));
    context.setTime(new Date());
    return context;
  }

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

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容