强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

MessagePack 序列化完全指南 / 06 - Java 实践 / Java Implementation

Java 实践 / Java Implementation

本章介绍如何在 Java 中使用 MessagePack,涵盖注解驱动序列化、模板系统、类型处理、流式操作以及与 JSON 库的性能对比。

This chapter covers using MessagePack in Java, including annotation-driven serialization, template system, type handling, streaming, and comparison with JSON libraries.


📖 库概览 / Library Overview

Java 生态中有两个主要的 MessagePack 实现:

库 / Library特点 / Features推荐度
msgpack-java (msgpack-core)官方库,低级 API,高性能⭐⭐⭐⭐
jackson-dataformat-msgpackJackson 集成,注解驱动⭐⭐⭐⭐⭐
org.msgpack (旧版)已废弃,不推荐

📝 推荐: 使用 jackson-dataformat-msgpack,它与 Jackson 生态完全兼容,支持所有 Jackson 注解。

Maven 依赖

<!-- jackson-dataformat-msgpack (推荐) -->
<dependency>
    <groupId>org.msgpack</groupId>
    <artifactId>jackson-dataformat-msgpack</artifactId>
    <version>0.9.8</version>
</dependency>

<!-- Jackson 核心 (自动传递依赖) -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

Gradle 依赖

implementation 'org.msgpack:jackson-dataformat-msgpack:0.9.8'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'

💻 基础使用 / Basic Usage

ObjectMapper 配置

import com.fasterxml.jackson.databind.ObjectMapper;
import org.msgpack.jackson.dataformat.MessagePackFactory;

public class MsgPackConfig {
    // 创建 MessagePack ObjectMapper
    private static final ObjectMapper mapper = new ObjectMapper(new MessagePackFactory());
    
    public static ObjectMapper getMapper() {
        return mapper;
    }
}

序列化 / 反序列化

import com.fasterxml.jackson.databind.ObjectMapper;
import org.msgpack.jackson.dataformat.MessagePackFactory;

public class BasicExample {
    private static final ObjectMapper mapper = new ObjectMapper(new MessagePackFactory());
    
    public static void main(String[] args) throws Exception {
        // ========== 序列化 ==========
        User user = new User(1001, "Alice", new int[]{95, 87, 92}, true);
        
        byte[] data = mapper.writeValueAsBytes(user);
        System.out.println("编码大小: " + data.length + " bytes"); // ~32 bytes
        
        // ========== 反序列化 ==========
        User decoded = mapper.readValue(data, User.class);
        System.out.println("解码结果: " + decoded);
        // User{id=1001, name='Alice', scores=[95, 87, 92], active=true}
    }
}

// POJO 类
class User {
    private int id;
    private String name;
    private int[] scores;
    private boolean active;
    
    // Jackson 需要无参构造函数
    public User() {}
    
    public User(int id, String name, int[] scores, boolean active) {
        this.id = id;
        this.name = name;
        this.scores = scores;
        this.active = active;
    }
    
    // Getters and Setters
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int[] getScores() { return scores; }
    public void setScores(int[] scores) { this.scores = scores; }
    public boolean isActive() { return active; }
    public void setActive(boolean active) { this.active = active; }
    
    @Override
    public String toString() {
        return String.format("User{id=%d, name='%s', scores=%s, active=%s}", 
            id, name, java.util.Arrays.toString(scores), active);
    }
}

使用 Map 和泛型

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.msgpack.jackson.dataformat.MessagePackFactory;
import java.util.*;

public class MapExample {
    private static final ObjectMapper mapper = new ObjectMapper(new MessagePackFactory());
    
    public static void main(String[] args) throws Exception {
        // 序列化 Map
        Map<String, Object> data = new HashMap<>();
        data.put("id", 1001);
        data.put("name", "Alice");
        data.put("scores", Arrays.asList(95, 87, 92));
        data.put("active", true);
        
        byte[] encoded = mapper.writeValueAsBytes(data);
        
        // 反序列化为 Map
        Map<String, Object> decoded = mapper.readValue(encoded, 
            new TypeReference<Map<String, Object>>() {});
        
        System.out.println(decoded);
        // {id=1001, name=Alice, scores=[95, 87, 92], active=true}
        
        // 嵌套泛型
        Map<String, List<User>> usersByGroup = new HashMap<>();
        usersByGroup.put("admin", Arrays.asList(new User(1, "Alice", null, true)));
        usersByGroup.put("editor", Arrays.asList(new User(2, "Bob", null, false)));
        
        byte[] groupData = mapper.writeValueAsBytes(usersByGroup);
        Map<String, List<User>> decodedGroups = mapper.readValue(groupData,
            new TypeReference<Map<String, List<User>>>() {});
        
        System.out.println(decodedGroups.get("admin").get(0).getName()); // Alice
    }
}

📖 注解系统 / Annotation System

Jackson 注解支持

由于使用了 Jackson 集成,所有 Jackson 注解均可使用:

import com.fasterxml.jackson.annotation.*;

public class Product {
    // 自定义字段名
    @JsonProperty("product_id")
    private int id;
    
    @JsonProperty("product_name")
    private String name;
    
    // 忽略字段
    @JsonIgnore
    private String internal;
    
    // 条件忽略
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private String description;
    
    // 条件忽略零值
    @JsonInclude(JsonInclude.Include.NON_DEFAULT)
    private int stock;
    
    // 只在序列化时使用
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    private Date createdAt;
    
    // 只在反序列化时使用
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String password;
    
    // 默认值
    @JsonProperty(defaultValue = "0")
    private double price;
    
    // 格式化
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updatedAt;
}

常用注解速查表

注解作用示例
@JsonProperty自定义字段名@JsonProperty("user_id")
@JsonIgnore忽略字段@JsonIgnore
@JsonInclude条件忽略@JsonInclude(NON_NULL)
@JsonFormat格式化@JsonFormat(pattern="yyyy-MM-dd")
@JsonCreator构造函数映射@JsonCreator
@JsonValue自定义值表示@JsonValue
@JsonGetter自定义 getter@JsonGetter("name")
@JsonSetter自定义 setter@JsonSetter("name")
@JsonAutoDetect自动检测@JsonAutoDetect(fieldVisibility=...)

使用 @JsonCreator

import com.fasterxml.jackson.annotation.*;

public class Money {
    private final String currency;
    private final long amount; // 用 long 避免浮点精度问题
    
    @JsonCreator
    public Money(
        @JsonProperty("currency") String currency,
        @JsonProperty("amount") long amount
    ) {
        this.currency = currency;
        this.amount = amount;
    }
    
    public String getCurrency() { return currency; }
    public long getAmount() { return amount; }
    
    @Override
    public String toString() {
        return currency + " " + (amount / 100.0);
    }
}

// 使用
Money money = new Money("CNY", 9999); // 99.99 元
byte[] data = mapper.writeValueAsBytes(money);
Money decoded = mapper.readValue(data, Money.class);
System.out.println(decoded); // CNY 99.99

💻 类型处理 / Type Handling

多态类型(Polymorphism)

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.msgpack.jackson.dataformat.MessagePackFactory;

// 定义基类
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = Circle.class, name = "circle"),
    @JsonSubTypes.Type(value = Rectangle.class, name = "rectangle")
})
abstract class Shape {
    public abstract double area();
}

class Circle extends Shape {
    @JsonProperty("radius")
    private double radius;
    
    public Circle() {}
    public Circle(double radius) { this.radius = radius; }
    
    @Override
    public double area() { return Math.PI * radius * radius; }
}

class Rectangle extends Shape {
    @JsonProperty("width")
    private double width;
    @JsonProperty("height")
    private double height;
    
    public Rectangle() {}
    public Rectangle(double w, double h) { this.width = w; this.height = h; }
    
    @Override
    public double area() { return width * height; }
}

// 使用
class Drawing {
    @JsonProperty("name")
    private String name;
    @JsonProperty("shapes")
    private List<Shape> shapes;
    
    // constructors, getters, setters...
}

// 序列化时自动包含类型信息
Drawing drawing = new Drawing("测试", Arrays.asList(
    new Circle(5.0),
    new Rectangle(10.0, 20.0)
));
byte[] data = mapper.writeValueAsBytes(drawing);

// 反序列化时自动还原类型
Drawing decoded = mapper.readValue(data, Drawing.class);
for (Shape shape : decoded.getShapes()) {
    System.out.println(shape.getClass().getSimpleName() + ": " + shape.area());
}
// Circle: 78.53981633974483
// Rectangle: 200.0

处理 Java 8+ 时间类型

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.msgpack.jackson.dataformat.MessagePackFactory;
import java.time.*;

public class TimeExample {
    private static final ObjectMapper mapper;
    
    static {
        mapper = new ObjectMapper(new MessagePackFactory());
        // 注册 Java 8 时间模块
        mapper.registerModule(new JavaTimeModule());
    }
    
    public static class Event {
        @JsonProperty("id")
        private int id;
        
        @JsonProperty("name")
        private String name;
        
        @JsonProperty("created_at")
        private LocalDateTime createdAt;
        
        @JsonProperty("event_date")
        private LocalDate eventDate;
        
        @JsonProperty("event_time")
        private LocalTime eventTime;
        
        // constructors, getters, setters...
    }
    
    public static void main(String[] args) throws Exception {
        Event event = new Event();
        event.setId(1);
        event.setName("会议");
        event.setCreatedAt(LocalDateTime.now());
        event.setEventDate(LocalDate.of(2024, 6, 15));
        event.setEventTime(LocalTime.of(14, 30));
        
        byte[] data = mapper.writeValueAsBytes(event);
        Event decoded = mapper.readValue(data, Event.class);
        
        System.out.println(decoded.getCreatedAt()); // 2024-06-15T14:30
    }
}

💻 流式处理 / Streaming

使用 MessagePacker 流式写入

import org.msgpack.core.MessagePack;
import org.msgpack.core.MessagePacker;
import org.msgpack.core.MessageUnpacker;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class StreamingExample {
    
    // 流式写入多个消息
    public static byte[] packMultiple(ObjectMapper mapper, List<Object> items) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        MessagePacker packer = MessagePack.newDefaultPacker(out);
        
        // 使用 jackson 写入每个对象
        for (Object item : items) {
            byte[] itemData = mapper.writeValueAsBytes(item);
            packer.writePayload(itemData);
        }
        
        packer.flush();
        packer.close();
        return out.toByteArray();
    }
    
    // 流式读取(使用长度前缀)
    public static <T> List<T> unpackMultiple(ObjectMapper mapper, byte[] data, 
                                              Class<T> type) throws IOException {
        List<T> results = new ArrayList<>();
        MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(data);
        
        while (unpacker.hasNext()) {
            // 读取每个消息的长度
            int len = unpacker.unpackArrayHeader();
            byte[] itemData = unpacker.readPayload(len);
            T item = mapper.readValue(itemData, type);
            results.add(item);
        }
        
        unpacker.close();
        return results;
    }
}

使用 Jackson Streaming API

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.msgpack.jackson.dataformat.MessagePackFactory;
import java.io.*;

public class JacksonStreamingExample {
    private static final ObjectMapper mapper = new ObjectMapper(new MessagePackFactory());
    
    // 流式写入
    public static void writeStream(OutputStream out, List<User> users) throws IOException {
        JsonGenerator gen = mapper.getFactory().createGenerator(out);
        
        gen.writeStartArray();
        for (User user : users) {
            gen.writeObject(user);
        }
        gen.writeEndArray();
        
        gen.flush();
        gen.close();
    }
    
    // 流式读取
    public static List<User> readStream(InputStream in) throws IOException {
        List<User> users = new ArrayList<>();
        JsonParser parser = mapper.getFactory().createParser(in);
        
        // 读取数组开始
        if (parser.nextToken() != JsonToken.START_ARRAY) {
            throw new IOException("Expected array");
        }
        
        // 逐个读取对象
        while (parser.nextToken() == JsonToken.START_OBJECT) {
            User user = parser.readValueAs(User.class);
            users.add(user);
        }
        
        parser.close();
        return users;
    }
}

💻 与 JSON 对比 / Comparison with JSON

性能基准测试

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.msgpack.jackson.dataformat.MessagePackFactory;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class MsgPackVsJsonBenchmark {
    
    private ObjectMapper jsonMapper;
    private ObjectMapper msgpackMapper;
    private User testUser;
    private byte[] jsonData;
    private byte[] msgpackData;
    
    @Setup
    public void setup() {
        jsonMapper = new ObjectMapper();
        jsonMapper.registerModule(new JavaTimeModule());
        
        msgpackMapper = new ObjectMapper(new MessagePackFactory());
        msgpackMapper.registerModule(new JavaTimeModule());
        
        testUser = new User(1001, "Alice", new int[]{95, 87, 92}, true);
        
        try {
            jsonData = jsonMapper.writeValueAsBytes(testUser);
            msgpackData = msgpackMapper.writeValueAsBytes(testUser);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    @Benchmark
    public byte[] jsonSerialize() throws Exception {
        return jsonMapper.writeValueAsBytes(testUser);
    }
    
    @Benchmark
    public byte[] msgpackSerialize() throws Exception {
        return msgpackMapper.writeValueAsBytes(testUser);
    }
    
    @Benchmark
    public User jsonDeserialize() throws Exception {
        return jsonMapper.readValue(jsonData, User.class);
    }
    
    @Benchmark
    public User msgpackDeserialize() throws Exception {
        return msgpackMapper.readValue(msgpackData, User.class);
    }
}

/*
典型结果 (ns/op):
jsonSerialize:       850
msgpackSerialize:    420  (快 2x)
jsonDeserialize:    1200
msgpackDeserialize:  580  (快 2x)
*/

体积对比

import com.fasterxml.jackson.databind.ObjectMapper;
import org.msgpack.jackson.dataformat.MessagePackFactory;

public class SizeComparison {
    public static void main(String[] args) throws Exception {
        ObjectMapper jsonMapper = new ObjectMapper();
        ObjectMapper mpMapper = new ObjectMapper(new MessagePackFactory());
        
        // 测试数据
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("id", 1001);
        data.put("name", "张三");
        data.put("email", "zhangsan@example.com");
        data.put("roles", Arrays.asList("admin", "editor", "viewer"));
        data.put("active", true);
        data.put("loginCount", 42);
        data.put("metadata", Map.of("city", "北京", "department", "技术部"));
        
        byte[] jsonBytes = jsonMapper.writeValueAsBytes(data);
        byte[] mpBytes = mpMapper.writeValueAsBytes(data);
        
        System.out.printf("JSON:       %d bytes%n", jsonBytes.length);
        System.out.printf("MessagePack: %d bytes%n", mpBytes.length);
        System.out.printf("节省:       %.1f%%%n", 
            (1 - (double) mpBytes.length / jsonBytes.length) * 100);
        
        // 典型输出:
        // JSON:       186 bytes
        // MessagePack: 118 bytes
        // 节省:       36.6%
    }
}

💻 Spring Boot 集成 / Spring Boot Integration

配置 MessagePack HttpMessageConverter

import com.fasterxml.jackson.databind.ObjectMapper;
import org.msgpack.jackson.dataformat.MessagePackFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.MediaType;
import java.util.List;

@Configuration
public class MsgPackConfig {
    
    @Bean
    public ObjectMapper msgPackMapper() {
        return new ObjectMapper(new MessagePackFactory());
    }
    
    @Bean
    public HttpMessageConverter<Object> msgPackConverter() {
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(msgPackMapper());
        converter.setSupportedMediaTypes(List.of(
            MediaType.parseMediaType("application/x-msgpack")
        ));
        return converter;
    }
}

Controller 使用

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class UserController {
    
    @GetMapping(value = "/users/{id}", produces = "application/x-msgpack")
    public User getUser(@PathVariable int id) {
        return new User(id, "Alice", new int[]{95, 87}, true);
    }
    
    @PostMapping(value = "/users", consumes = "application/x-msgpack")
    public Map<String, Object> createUser(@RequestBody User user) {
        // 处理 MessagePack 请求
        return Map.of("status", "ok", "id", user.getId());
    }
}

⚠️ 注意事项 / Pitfalls

1. 无参构造函数

// ❌ Jackson 需要无参构造函数
public class User {
    private int id;
    private String name;
    
    // 只有有参构造函数,反序列化会失败
    public User(int id, String name) { ... }
}

// ✅ 解决方案 1: 添加无参构造函数
public class User {
    private int id;
    private String name;
    public User() {} // 必须
    public User(int id, String name) { ... }
}

// ✅ 解决方案 2: 使用 @JsonCreator
public class User {
    private final int id;
    private final String name;
    
    @JsonCreator
    public User(@JsonProperty("id") int id, @JsonProperty("name") String name) {
        this.id = id;
        this.name = name;
    }
}

2. 枚举类型

public enum Status {
    ACTIVE, INACTIVE, DELETED;
}

public class User {
    private Status status; // 序列化为字符串 "ACTIVE"
}

// 如果要序列化为整数
public enum Status {
    @JsonProperty("0") ACTIVE,
    @JsonProperty("1") INACTIVE,
    @JsonProperty("2") DELETED;
}

3. BigDecimal 精度

// MessagePack 不原生支持 BigDecimal
// 需要自定义序列化
public class Money {
    @JsonSerialize(using = BigDecimalSerializer.class)
    @JsonDeserialize(using = BigDecimalDeserializer.class)
    private BigDecimal amount;
}

// 或者转换为字符串
public class Money {
    @JsonProperty("amount")
    private String amountStr; // "99.99"
}

4. null 处理

// Java 的 null 编码为 MessagePack nil
byte[] data = mapper.writeValueAsBytes(null);
// data = [0xc0] (1 byte)

Object decoded = mapper.readValue(data, Object.class);
// decoded = null

5. 循环引用

// ❌ 循环引用会导致 StackOverflowError
public class Node {
    private Node parent;
    private List<Node> children;
}

// ✅ 解决: 使用 @JsonIdentityInfo
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Node {
    private int id;
    private Node parent;
    private List<Node> children;
}

🔗 扩展阅读 / Further Reading

资源链接
jackson-dataformat-msgpackhttps://github.com/msgpack/msgpack-java
Jackson 注解文档https://github.com/FasterXML/jackson-annotations
JMH 基准测试https://openjdk.java.net/projects/code-tools/jmh/
Spring Boot MessagePackhttps://spring.io/guides/gs/rest-service/

📝 下一章 / Next: 第 7 章 - Rust 实践 / Rust Implementation — 在 Rust 中使用 rmp-serde 进行高性能序列化。