JsonUtil(基於Jackson的實現)
前言:
其實,我一直想寫一個有關Util的系列。
其中有四個原因:
- Util包作為專案的重要組成,是幾乎每個專案不可或缺的一部分。並且Util包的Util往往具有足夠的通用性,可用於不同的專案。
- Util包中的程式碼封裝往往非常有意思,對他們的學習,也有助於自身程式碼水平與認知的提高。
- 目前網上對Util包的總結很少,或者說很零散,沒有做成一個系列的。我希望能做成一個系列,以後缺什麼Util都可以直接通過這個系列找到需要的Util。
- 藉此機會,可以更好地與外界進行技術的交流,獲得更多的指導。
場景:
- 由於業務的需要(如session集中儲存),我們需要將某個物件(如使用者資訊)儲存到Redis中,而Redis無法儲存物件。所以我們需要將物件進行序列化操作,從而將物件儲存起來,並在日後提取出來時,進行反序列化。
- 由於業務的需求(如訊息佇列的訊息),我們需要將一組物件(如訂單資訊)傳送到訊息佇列,而訊息佇列是無法傳送物件的(RabbitMQ後面是支援的,另外,序列化的訊息,便於後臺檢視)。所以我們需要將一組物件進行序列化操作,從而將物件儲存起來,並在日後提取出來時,進行反序列化。
作用:
JsonUtil就是用來進行單個或複數個物件的序列化與反序列化操作。
程式碼:
package top.jarry.learning.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.codehaus.jackson.type.JavaType;
import org.codehaus.jackson.type.TypeReference;
import java.io.IOException;
import java.text.SimpleDateFormat;
/**
* @Description:
* @Author: jarry
*/
@Slf4j
public class JsonUtil {
// 建立Jackson的ObjectMapper物件
private static ObjectMapper objectMapper = new ObjectMapper();
// 建立Json操作中的日期格式
private static final String JSON_STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
// DateTimeUtil.STANDARD_FORMAT = "yyyy-mm-dd HH:mm:ss";
// 日期格式如果設定為這個,會出現月份出錯的問題(先是5月變3月,然後就不斷增加,甚至超過12月),具體原因待查
static {
//物件的所有欄位全部列入
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.ALWAYS);
//取消預設轉換timestamps形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
//忽略空Bean轉json的錯誤
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
//所有的日期格式都統一為以下的樣式
objectMapper.setDateFormat(new SimpleDateFormat(JSON_STANDARD_FORMAT));
//反序列化
//忽略 在json字串中存在,但是在java物件中不存在對應屬性的情況。防止錯誤
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
* 完成物件序列化為字串
* @param obj 源物件
* @param <T>
* @return
*/
public static <T> String obj2String(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
} catch (Exception e) {
log.warn("Parse Object to String error", e);
return null;
}
}
/**
* 完成物件序列化為字串,但是字串會保證一定的結構性(提高可讀性,增加字串大小)
* @param obj 源物件
* @param <T>
* @return
*/
public static <T> String obj2StringPretty(T obj) {
if (obj == null) {
return null;
}
try {
return obj instanceof String ? (String) obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (Exception e) {
log.warn("Parse Object to String error", e);
return null;
}
}
/**
* 完成字串反序列化為物件
* @param str 源字串
* @param clazz 目標物件的Class
* @param <T>
* @return
*/
public static <T> T string2Obj(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz == null) {
return null;
}
try {
return (clazz == String.class) ? (T) str : objectMapper.readValue(str, clazz);
} catch (IOException e) {
log.warn("Parse String to Object error", e);
return null;
}
}
//jackson在反序列化時,如果傳入List,會自動反序列化為LinkedHashMap的List
//所以過載一下方法,解決之前String2Obj無法解決的問題
/**
* 進行復雜型別反序列化工作 (自定義型別的集合型別)
*
* @param str 源字串
* @param typeReference 包含elementType與CollectionType的typeReference
* @param <T>
* @return
*/
public static <T> T string2Obj(String str, TypeReference<T> typeReference) {
if (StringUtils.isEmpty(str) || typeReference == null) {
return null;
}
try {
return (T) ((typeReference.getType().equals(String.class)) ? str : objectMapper.readValue(str, typeReference.getClass()));
} catch (IOException e) {
log.warn("Parse String to Object error", e);
return null;
}
}
/**
* 進行復雜型別反序列化工作(可變型別數量的)
*
* @param str 需要進行反序列化的字串
* @param collectionClass 需要反序列化的集合型別 由於這裡的型別未定,且為了防止與返回值型別T衝突,故採用<?>表示泛型
* @param elementClasses 集合中的元素型別(可多個) 此處同上通過<?>...表示多個未知泛型
* @param <T> 返回值的泛型型別是由javatype獲取的
* @return
*/
public static <T> T string2Obj(String str, Class<?> collectionClass, Class<?>... elementClasses) {
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
try {
return objectMapper.readValue(str, javaType);
} catch (IOException e) {
log.warn("Parse String to Object error", e);
return null;
}
}
}
依賴:
- commons-lang3
- jackson(該jar包可能有點老,可以考慮更新,不過可以正常使用)
應用:
public void onInitializationInclinationMessage(String initializationInclinationStr,
@Headers Map<String, Object> headers, Channel channel) throws IOException {
log.info("InitializationInclinationConsumer/onInitializationInclinationMessage has received: {}", initializationInclinationStr);
// 1.接收資料,並反序列化出物件
InitializationInclination initializationInclination = JsonUtil.string2Obj(initializationInclinationStr, InitializationInclination.class);
// 2.資料校驗,判斷是否屬於該終端資料
if (initializationInclination == null) {
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
channel.basicAck(deliveryTag, false);
}
if (!GuavaCache.getKey(TERMINAL_ID).equals(initializationInclination.getTerminalId())) {
log.info("refuse target initializationInclination with terminalId({}).current_terminalId({})", initializationInclination.getTerminalId(), GuavaCache.getKey(TERMINAL_ID));
return;
}
// 3.將訊息傳入業務服務,進行消費
ServerResponse response = iInitializationInclinationService.receiveInitializationInclinationFromMQ(initializationInclination);
// 4.對成功消費的資料進行簽收
if (response.isSuccess()) {
//由於配置中寫的是手動簽收,所以這裡需要通過Headers來進行簽收
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
channel.basicAck(deliveryTag, false);
}
public void onInclinationTotalMessage(String inclinationTotalListStr, @Headers Map<String, Object> headers, Channel channel) throws Exception {
log.info("InclinationConsumer/onInclinationTotalMessage has received: {}", inclinationTotalListStr);
// 1.對訊息進行反序列化操作
List<InclinationTotal> inclinationTotalList = JsonUtil.string2Obj(inclinationTotalListStr, List.class, InclinationTotal.class);
// 2.對資料進行校驗
if (CollectionUtils.isEmpty(inclinationTotalList)){
//由於配置中寫的是手動簽收,所以這裡需要通過Headers來進行簽收
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
channel.basicAck(deliveryTag, false);
}
// 3.將訊息傳入業務服務,進行消費
ServerResponse response = iInclinationService.insertTotalDataByList(inclinationTotalList);
// 4.對成功消費的資料進行簽收
if (response.isSuccess()) {
//由於配置中寫的是手動簽收,所以這裡需要通過Headers來進行簽收
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
channel.basicAck(deliveryTag, false);
}
}
問題:
在使用這個JsonUtil的過程中,遇到過一個問題,就是日期序列化,反序列化,出現問題。
不過,經過一次次除錯與追蹤後,發現只要修改了日期格式就可以避免這個問題(其實當時真的沒有想到Util會出現這種問題,所以花了不少時間)。
具體原因,問了一圈,也沒有得到答案。看來只能留待日後了。
總結:
Json序列化的Util當然不止這一種。還有很多方式,乃至不是基於Jackson的,如基於Gson的。
有機會日後會進行補充的。
如果大家對這個系列有什麼意見或者期待,可以給我留言,謝謝。