SpringBoot中如何使用ObjectMapper,老鳥們都是這樣玩的?
1. 每次new一個
在SpringBoot項目中要實現(xiàn)對象與Json字符串的互轉(zhuǎn),每次都需要像如下一樣new 一個ObjectMapper對象:
public UserEntity string2Obj(String json) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, UserEntity.class);
}
public String obj2String(UserEntity userEntity) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(car)
}
這樣的代碼到處可見,有問題嗎?
你要說他有問題吧,確實能正常執(zhí)行;可你要說沒問題吧,在追求性能的同學眼里,這屬實算是十惡不赦的代碼了。
首先,讓我們用JMH對這段代碼做一個基準測試,讓大家對其性能有個大概的了解。
基準測試是指通過設(shè)計科學的測試方法、測試工具和測試系統(tǒng),實現(xiàn)對一類測試對象的某項性能指標進行定量的和可對比的測試。而JMH是一個用來構(gòu)建,運行,分析Java或其他運行在JVM之上的語言的 納秒/微秒/毫秒/宏觀 級別基準測試的工具。
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 3, time = 1)
public class JsonJMHTest {
String json = "{\"id\":122345667,\"email\":\"jianzh5@163.com\",\"price\":12.25}";
UserEntity userEntity = new UserEntity(13345L,"jianzh5@163.com", BigDecimal.valueOf(12.25));
/**
* 測試String to Object
*/
@Benchmark
public UserEntity objectMapper2ObjTest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, UserEntity.class);
}
/**
* 測試Object to String
*/
@Benchmark
public String objectMapper2StringTest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(userEntity);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JsonJMHTest.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
測試環(huán)境
# JMH version: 1.36
# VM version: JDK 17.0.3, OpenJDK 64-Bit Server VM, 17.0.3+7-LTS
# Mac AppleM1/16GB
測試結(jié)果
圖片
通過測試結(jié)果可以看出,每次new一個ObjectMapper,在實現(xiàn)字符串轉(zhuǎn)對象時每秒可以完成23萬多次,而實現(xiàn)對象轉(zhuǎn)Json字符串每秒僅可完成2.7萬次。
那該如何優(yōu)化,提升性能呢?
2. 單例化
老鳥們都知道,在創(chuàng)建工具類時要將工具類設(shè)置成單例的,這樣不僅可以保證線程安全,也可以保證在系統(tǒng)全局只能創(chuàng)建一個對象,避免頻繁創(chuàng)建對象的成本。
所以,我們可以在項目中構(gòu)建一個ObjectMapper的單例類。
@Getter
public enum ObjectMapperInstance {
INSTANCE;
private final ObjectMapper objectMapper = new ObjectMapper();
ObjectMapperInstance() {
}
}
再次使用JMH對其測試:
@Benchmark
public UserEntity singleten2ObjTest() throws JsonProcessingException {
ObjectMapper objectMapper = ObjectMapperInstance.INSTANCE.getObjectMapper();
return objectMapper.readValue(json, UserEntity.class);
}
@Benchmark
public String singleten2StringTest() throws JsonProcessingException {
ObjectMapper objectMapper = ObjectMapperInstance.INSTANCE.getObjectMapper();
return objectMapper.writeValueAsString(userEntity);
}
測試結(jié)果如下:
圖片
可以看到,使用單例模式,String轉(zhuǎn)對象的方法每秒可以執(zhí)行420多萬次,比new ObjectMapper的方式快了18倍;而對象轉(zhuǎn)String的方法每秒可以執(zhí)行830萬次,性能提升了300倍(看到結(jié)果的一瞬間我傻眼了,一度懷疑是寫錯代碼了)?。。?!
3. 個性化配置
當然,在項目中使用ObjectMapper時,有時候我們還需要做一些個性化配置,比如將Long和BigDemical類型的屬性都通過字符串格式進行轉(zhuǎn)換,防止前端使用時丟失數(shù)值精度。
這些類型轉(zhuǎn)換的格式映射都可以在單例類中配置,代碼如下:
@Getter
public enum ObjectMapperInstance {
INSTANCE;
private final ObjectMapper objectMapper;
ObjectMapperInstance() {
objectMapper = new ObjectMapper();
// 注冊自定義模塊
initialize();
}
private void initialize() {
CustomJsonModule customJsonModule = new CustomJsonModule();
objectMapper.registerModule(customJsonModule);
}
}
在initialize()方法中給ObjectMapper注冊自定義序列化轉(zhuǎn)換器。
圖片
第一行是使用注冊自定義序列換轉(zhuǎn)換器后的效果,給id和price字段都加上了引號。
再來一次JMH測試:
圖片
可以看到,給ObjectMapper額外注冊轉(zhuǎn)換類型以后性能會受到一定的影響,但對業(yè)務(wù)影響不大。(啥業(yè)務(wù)能這么高的請求~)
4. 小結(jié)
通過上面的測試,結(jié)論已經(jīng)很清晰了。使用單例模式進行字符串轉(zhuǎn)對象時性能可以提升18倍,而對象轉(zhuǎn)String性能快了驚人的290萬倍,所以在Spring中如何正確的使用ObjectMapper不用我再說了吧~