Star 历史趋势
数据来源: GitHub API · 生成自 Stargazers.cn
README.md

dubbo-go-hessian2

Build Status codecov GoDoc Go Report Card license


A Go implementation of the Hessian 2.0 serialization protocol, enabling seamless cross-language communication between Go and Java applications. Primarily used by Apache Dubbo-Go for RPC serialization.

Table of Contents

Features

Note: From v1.6.0+, the decoder skips non-existent fields (matching Java hessian behavior). Versions before v1.6.0 returned errors for non-existent fields.

Installation

go get github.com/apache/dubbo-go-hessian2

Requires Go 1.21+.

Quick Start

// Define a struct and implement the POJO interface type User struct { Name string Age int32 } func (User) JavaClassName() string { return "com.example.User" } // Register the type before encoding/decoding hessian.RegisterPOJO(&User{}) // Encode user := &User{Name: "Alice", Age: 30} encoder := hessian.NewEncoder() encoder.Encode(user) data := encoder.Buffer() // Decode obj, _ := hessian.NewDecoder(data).Decode() decoded := obj.(*User)

Core Concepts

The POJO Interface

Any Go struct that needs to be serialized as a Java object must implement the POJO interface:

type POJO interface { JavaClassName() string // Returns the fully qualified Java class name }

This establishes the mapping between your Go struct and the corresponding Java class.

Registering Types

Before encoding or decoding custom types, you must register them. This is typically done in an init() function:

func init() { // Register a single type hessian.RegisterPOJO(&MyStruct{}) // Register multiple types at once hessian.RegisterPOJOs(&TypeA{}, &TypeB{}, &TypeC{}) // Register with a custom Java class name (overrides JavaClassName()) hessian.RegisterPOJOMapping("com.example.CustomName", &MyStruct{}) }

If a type is not registered, the decoder will:

  • Default mode: Decode unknown objects as map[interface{}]interface{}
  • Strict mode: Return an error

Type Mapping (Java <-> Go)

Primitive Types

Hessian TypeJava TypeGo Type
nullnullnil
binarybyte[][]byte
booleanbooleanbool
datejava.util.Datetime.Time
doubledoublefloat64
intintint32
longlongint64
stringjava.lang.Stringstring
listjava.util.Listslice
mapjava.util.Mapmap
objectcustom classstruct

Java Wrapper Types

Java TypeGo Type
java.lang.Integer*int32
java.lang.Long*int64
java.lang.Boolean*bool
java.lang.Short*int16
java.lang.Byte*uint8
java.lang.Float*float32
java.lang.Double*float64
java.lang.Character*hessian.Rune

Extended Types

Java TypeGo Type / Package
java.math.BigDecimalgithub.com/dubbogo/gost/math/big Decimal
java.math.BigIntegergithub.com/dubbogo/gost/math/big Integer
java.sql.Dategithub.com/apache/dubbo-go-hessian2/java_sql_time Date
java.sql.Timegithub.com/apache/dubbo-go-hessian2/java_sql_time Time
java.util.UUIDgithub.com/apache/dubbo-go-hessian2/java_util UUID
java.util.Localegithub.com/apache/dubbo-go-hessian2/java_util Locale
Java 8 time types (LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period, etc.)github.com/apache/dubbo-go-hessian2/java8_time

Tip: Avoid defining objects that only exist in one language. Use error codes/messages instead of Java exceptions for cross-language communication.

API Reference

Encoder

// Create a new encoder encoder := hessian.NewEncoder() // Encode a value (primitives, slices, maps, structs, etc.) err := encoder.Encode(value) // Get the encoded bytes data := encoder.Buffer() // Reset encoder state for reuse encoder.Clean() // Reset encoder state but reuse the underlying buffer encoder.ReuseBufferClean()

Special encoding methods:

// Encode a map as a typed Java object using "_class" key in the map encoder.EncodeMapClass(map[string]interface{}{"_class": "com.example.Foo", "name": "bar"}) // Encode a map as a specific Java class encoder.EncodeMapAsClass("com.example.Foo", map[string]interface{}{"name": "bar"})

Decoder

// Standard decoder decoder := hessian.NewDecoder(data) // Strict mode - returns error for unregistered types decoder := hessian.NewStrictDecoder(data) // Decode the next value obj, err := decoder.Decode() // Reset decoder with new data (for reuse with object pools) decoder.Reset(newData)

Decoder modes:

ConstructorBehavior
NewDecoder(data)Decodes unknown objects as maps
NewStrictDecoder(data)Returns error for unregistered objects
NewDecoderWithSkip(data)Skips non-existent fields
NewCheapDecoderWithSkip(data)Poolable decoder, use with Reset()

Registration Functions

hessian.RegisterPOJO(&MyStruct{}) // Register a POJO type hessian.RegisterPOJOs(&A{}, &B{}) // Register multiple POJOs hessian.RegisterPOJOMapping("com.example.Name", &Struct{}) // Register with custom Java class name hessian.RegisterJavaEnum(&MyEnum{}) // Register a Java enum type hessian.UnRegisterPOJOs(&A{}, &B{}) // Unregister POJOs hessian.SetCollectionSerialize(&MyHashSet{}) // Register a Java collection type hessian.SetSerializer("com.example.Foo", &FooSerializer{}) // Register a custom serializer

Configuration Functions

// Change the struct tag used for field name mapping (default: "hessian") hessian.SetTagIdentifier("json") // Look up a registered custom serializer serializer, ok := hessian.GetSerializer("com.example.Foo") // Find class info in the decoder classInfo := hessian.FindClassInfo("com.example.Foo")

Usage Examples

Encoding and Decoding Custom Objects

type Circular struct { Value Previous *Circular Next *Circular } type Value struct { Num int } func (Circular) JavaClassName() string { return "com.company.Circular" } func init() { hessian.RegisterPOJO(&Circular{}) } // Encode c := &Circular{} c.Num = 12345 c.Previous = c // circular reference - handled automatically c.Next = c e := hessian.NewEncoder() if err := e.Encode(c); err != nil { panic(err) } data := e.Buffer() // Decode obj, err := hessian.NewDecoder(data).Decode() if err != nil { panic(err) } circular := obj.(*Circular) fmt.Println(circular.Num) // 12345

Customizing Field Names with Tags

The encoder converts Go field names to lowerCamelCase by default. Use the hessian tag to override:

type MyUser struct { UserFullName string `hessian:"user_full_name"` // encoded as "user_full_name" FamilyPhoneNumber string // encoded as "familyPhoneNumber" (default) } func (MyUser) JavaClassName() string { return "com.company.myuser" }

Decoding Field Name Matching Rules

When decoding, fields are matched in the following order:

  1. Tag match - matches the hessian tag value
  2. lowerCamelCase - e.g., mobilePhone matches MobilePhone
  3. Exact case - e.g., MobilePhone matches MobilePhone
  4. Lowercase - e.g., mobilephone matches MobilePhone
type MyUser struct { MobilePhone string `hessian:"mobile-phone"` } // Incoming field "mobile-phone" -> matched via tag (rule 1) // Incoming field "mobilePhone" -> matched via lowerCamelCase (rule 2) // Incoming field "MobilePhone" -> matched via exact case (rule 3) // Incoming field "mobilephone" -> matched via lowercase (rule 4)

Using a Custom Tag Identifier

Use SetTagIdentifier to read field names from a different struct tag (e.g., json):

hessian.SetTagIdentifier("json") type MyUser struct { UserFullName string `json:"user_full_name"` FamilyPhoneNumber string } func (MyUser) JavaClassName() string { return "com.company.myuser" }

Specifying Java Parameter Types (Inheritance)

When a Java method expects a parent class but you send a subclass, implement the Param interface:

Java side:

public abstract class User {} public class MyUser extends User implements Serializable { private String userFullName; private String familyPhoneNumber; } public interface UserProvider { String GetUser(User user); // accepts parent type }

Go side:

type MyUser struct { UserFullName string `hessian:"userFullName"` FamilyPhoneNumber string } func (m *MyUser) JavaClassName() string { return "com.company.MyUser" } // JavaParamName tells the encoder to use the parent class name in the method signature func (m *MyUser) JavaParamName() string { return "com.company.User" }

Working with Java Collections

Map a Java collection class (e.g., HashSet) to a Go struct:

type JavaHashSet struct { value []interface{} } func (j *JavaHashSet) Get() []interface{} { return j.value } func (j *JavaHashSet) Set(v []interface{}) { j.value = v } func (j *JavaHashSet) JavaClassName() string { return "java.util.HashSet" } func init() { hessian.SetCollectionSerialize(&JavaHashSet{}) }

Without this registration, Java collections are decoded as []interface{}.

Working with Java Enums

type Color int32 const ( RED Color = 0 GREEN Color = 1 BLUE Color = 2 ) var colorNames = map[Color]string{ RED: "RED", GREEN: "GREEN", BLUE: "BLUE", } var colorValues = map[string]Color{ "RED": RED, "GREEN": GREEN, "BLUE": BLUE, } func (c Color) JavaClassName() string { return "com.example.Color" } func (c Color) String() string { return colorNames[c] } func (c Color) EnumValue(s string) hessian.JavaEnum { return hessian.JavaEnum(colorValues[s]) } func init() { hessian.RegisterJavaEnum(RED) }

Custom Serializer

Implement the Serializer interface for full control over encoding/decoding:

type MySerializer struct{} func (s *MySerializer) EncObject(encoder *hessian.Encoder, obj hessian.POJO) error { // Custom encoding logic return nil } func (s *MySerializer) DecObject(decoder *hessian.Decoder, typ reflect.Type, cls *hessian.ClassInfo) (interface{}, error) { // Custom decoding logic return nil, nil } func init() { hessian.SetSerializer("com.example.MyClass", &MySerializer{}) }

Strict Mode

By default, unregistered objects are decoded as maps. Use strict mode to get errors instead:

decoder := hessian.NewDecoder(data) decoder.Strict = true // returns error for unregistered types // Or use the convenience constructor: decoder = hessian.NewStrictDecoder(data)

Reusing Encoder/Decoder (Object Pool)

For high-performance scenarios, reuse encoder/decoder instances:

// Encoder reuse encoder := hessian.NewEncoder() encoder.Encode(obj1) data1 := encoder.Buffer() encoder.Clean() // or encoder.ReuseBufferClean() to keep the buffer encoder.Encode(obj2) data2 := encoder.Buffer() // Decoder reuse (poolable decoder) decoder := hessian.NewCheapDecoderWithSkip(data1) obj1, _ := decoder.Decode() decoder.Reset(data2) // reuse with new data obj2, _ := decoder.Decode()

Struct Inheritance

Go struct embedding is supported for modeling Java inheritance:

type Animal struct { Name string } func (Animal) JavaClassName() string { return "com.example.Animal" } type Dog struct { Animal // embedded parent struct Breed string } func (Dog) JavaClassName() string { return "com.example.Dog" }

Avoid these patterns:

  1. Duplicate field names across parents - ambiguous field resolution:

    type A struct { Name string } type B struct { Name string } type C struct { A; B } // which Name?
  2. Pointer embedding - nil parent at initialization, not supported:

    type Dog struct { *Animal // will be nil in Dog{}, not supported }

Tools

gen-go-enum

A code generation tool for creating Go enum types compatible with Java enums. See tools/gen-go-enum/README.md for details.

Reference

关于 About

caucho hessian2 implementation in Go for [apache/dubbo-go](https://github.com/apache/dubbo-go) which is compatible with [dubbo-hessian-lite](https://github.com/apache/dubbo-hessian-lite)
apache-dubbo-gocauchodubbodubbo-godubbo-hessiandubbo-hessian-litedubbogodubboxhessianhessian2

语言 Languages

Go83.1%
Java16.6%
Shell0.2%
Makefile0.2%

提交活跃度 Commit Activity

代码提交热力图
过去 52 周的开发活跃度
21
Total Commits
峰值: 7次/周
Less
More

核心贡献者 Contributors