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-msgpack | Jackson 集成,注解驱动 | ⭐⭐⭐⭐⭐ |
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", "[email protected]");
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-msgpack | https://github.com/msgpack/msgpack-java |
| Jackson 注解文档 | https://github.com/FasterXML/jackson-annotations |
| JMH 基准测试 | https://openjdk.java.net/projects/code-tools/jmh/ |
| Spring Boot MessagePack | https://spring.io/guides/gs/rest-service/ |
📝 下一章 / Next: 第 7 章 - Rust 实践 / Rust Implementation — 在 Rust 中使用 rmp-serde 进行高性能序列化。