当前位置: 首页 > article >正文

从内存到网络:深入理解对象序列化

1、概述

        前面一篇文章我们实现了一个自定义协议Cheese协议,文章的末尾提到了序列化和反序列化的问题,今天我们就深入的分析一下什么是序列化、为什么要序列化以及序列化的最佳技术选型。最后我们将理论落地成一款实用的序列化框架,可以通过配置选择不同的序列化方式并且集成到Cheese 中。

2、什么是序列化

        首先我们的编写的代码最终都会变成计算机能够执行的机器码指令,编译型的语言一步到位生成机器码,解释型的语言通过解释器或者虚拟机来翻译成机器码。那么我们的程序处理的数据是怎么从内存里进入到网络呢

        毫无疑问,数据进入网络或者磁盘肯定是通过IO操作实现的,比如Java中的InputStream和 OutputStream以及C++中的ifstream和ofstream函数 等等 。

        数据在内存中的组织结构千奇百怪,有各种简单类型的数据,也有复杂类型的对象以及各种奇奇怪怪的数据结构。因此我们需要一种通用的算法,将这些千奇百怪的数据按照统一的格式进行编排,这一类的算法就叫序列化算法,这个步骤叫做序列化。

3、常见的序列化解决方案

3.1、序列化需要干什么

        首先我们需要明确序列化需要干什么,换而言之就是序列化需要提供什么功能,放到代码层面上讲的话 我们的序列化方法 入参是什么 出参是什么,逻辑怎么实现。我们通过上一章节的介绍至少可以粗糙的知道序列化是“将对象转换成字节数组”,反序列化是将“字节数组转换成数据对象”,具体实现有多种办法,这里我们先定义一个接口,后面一次为大家介绍常见的实现方案

/**
 * @Description
 * @Author wcan
 * @Date 2025/1/21 上午 10:18
 * @Version 1.0
 */
public interface SerializeStrategy<T> {

    public byte[] serialize(T object);

    public T deserialize(byte[] bytes);
}

3.2、JDK原生序列化

在Java中,JDK自带了一个序列化的接口 Serializable。API如下:

java.io.Serializable

我们将需要序列化的类实现这个接口,然后就可以使用 ObjectOutputStream 序列化

public interface Externalizable extends java.io.Serializable {
    /**
     * The object implements the writeExternal method to save its contents
     * by calling the methods of DataOutput for its primitive values or
     * calling the writeObject method of ObjectOutput for objects, strings,
     * and arrays.
     *
     * @serialData Overriding methods should use this tag to describe
     *             the data layout of this Externalizable object.
     *             List the sequence of element types and, if possible,
     *             relate the element to a public/protected field and/or
     *             method of this Externalizable class.
     *
     * @param     out the stream to write the object to
     * @throws    IOException Includes any I/O exceptions that may occur
     */
    void writeExternal(ObjectOutput out) throws IOException;

    /**
     * The object implements the readExternal method to restore its
     * contents by calling the methods of DataInput for primitive
     * types and readObject for objects, strings and arrays.  The
     * readExternal method must read the values in the same sequence
     * and with the same types as were written by writeExternal.
     *
     * @param     in the stream to read data from in order to restore the object
     * @throws    IOException if I/O errors occur
     * @throws    ClassNotFoundException If the class for an object being
     *            restored cannot be found.
     */
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

同样的这个接口还有一个子接口 Externalizable,我么也可以实现 Externalizable 这个接口,和 Serializable 接口不同的是我们需要实现 writeExternal 和 readExternal 这两个抽象方法,也就是序列化和反序列化的实现。例如下面这段代码

public class Jerry implements Externalizable {
    private String name;
    private int age;
    
    // getter and setter ...

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
    }
}

Externalizable 接口国内使用的并不多,下面我们使用原生的API来编写一个完整的案例,相关代码比较简单,如下所示

/**
 * @Description Java 原生的序列化方式
 * @Author wcan
 * @Date 2025/1/21 上午 10:15
 * @Version 1.0
 */
public class SimpleSerialize<T> implements SerializeStrategy<T> {

    public SimpleSerialize() {
    }

    public SimpleSerialize(Class<T> objectClass) {
    }

    @Override
    public byte[] serialize(T object) {
        if (object == null) {
            throw new IllegalArgumentException("Object 不能为空 ");
        }
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
            objectOutputStream.writeObject(object);
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            // 使用日志记录异常信息
            throw new RuntimeException("serialize failed: ", e);
        }
    }

    @Override
    public T deserialize(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            throw new IllegalArgumentException("bytes 不能为空 ");
        }
        try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
             ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) {
            @SuppressWarnings("unchecked")
            T obj = (T) objectInputStream.readObject();
            return obj;
        } catch (IOException | ClassNotFoundException e) {
            // 使用日志记录异常信息
            throw new RuntimeException("deserialize failed: ", e);
        }
    }
}

这种方案就是 使用 ObjectOutputStream将数据对象转换成字节数组,使用 ObjectInputStream 将字节数据转成换数据对象。 

3.3、JSON 序列化

        这种序列化方式也很常见,我们在之前的Cheese中 使用的就是Json序列化的方式,也就是将对象转换成Json字符串,然后再转成字节数组。之前的cheese中使用的时SpringBoot自带的jackson,这里给大家再介绍一种更好用的Json库,谷歌开源的 gson 

首先我们先导入坐标,

<dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.8</version>
        </dependency>

接着就可以编写代码了,相关代码如下 

package org.wcan.serialize.strategy;

import com.google.gson.Gson;
import org.wcan.serialize.SerializeStrategy;

import java.nio.charset.Charset;

/**
 * @Description
 * @Author wcan
 * @Date 2025/1/21 上午 10:30
 * @Version 1.0
 */
public class JsonSerialize<T> implements SerializeStrategy<T> {

    private Gson gson = new Gson();
    private Class<T> type;

    public JsonSerialize(Class<T> type) {
        this.type = type;
    }
    @Override
    public byte[] serialize(T object) {
        String json = gson.toJson(object);
        return json.getBytes(Charset.defaultCharset());
    }

    @Override
    public T deserialize(byte[] bytes){
        String string = new String(bytes, Charset.defaultCharset());
        return gson.fromJson(string, type);
    }
}

3.4、Hessian 序列化

        JSON序列化的方式很直观,但是效率并不高,尤其是再处理大对象的时候。相比较而言 Hessian序列化效率更高,他提供了一种二进制格式,并且优化了对象的序列化过程,某些场景下比Java原生的序列化方式更高效。下面是代码示例

引入依赖

        <dependency>
            <groupId>com.caucho</groupId>
            <artifactId>hessian</artifactId>
            <version>4.0.63</version>
        </dependency>
package org.wcan.serialize.strategy;

import com.caucho.hessian.io.HessianInput;
import com.caucho.hessian.io.HessianOutput;
import org.wcan.serialize.SerializeStrategy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * @Description
 * @Author wcan
 * @Date 2025/1/21 上午 11:13
 * @Version 1.0
 */
public class HessianSerialize<T> implements SerializeStrategy<T> {
    
    public HessianSerialize() {
    }
    public HessianSerialize(Class<T> objectClass) {
    }

    @Override
    public byte[] serialize(T object) {
        try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            HessianOutput hessianOutput = new HessianOutput(outputStream);
            hessianOutput.writeObject(object);
            return outputStream.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public T deserialize(byte[] bytes) {
        try(ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) {
            HessianInput hessianInput = new HessianInput(inputStream);
            return  (T)hessianInput.readObject();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

3.5、Kryo序列化

Kryo也是一种高效的序列化方式,数据也是以二进制格式编码的。得益于其高效的实现方式,序列化后的数据相比Hessian更小,所以在网络中传输的时候更有优势,下面再给大家提供一段简单的代码案例

依赖

        <dependency>
            <groupId>com.esotericsoftware</groupId>
            <artifactId>kryo</artifactId>
            <version>5.6.2</version>
        </dependency>

示例代码

package org.wcan.serialize.strategy;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.wcan.serialize.SerializeStrategy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * @ClassName KryoSerialize
 * @Description TODO
 * @Author wcan
 * @Date 2025/2/8 下午 15:03
 * @Version 1.0
 */
public class KryoSerialize<T> implements SerializeStrategy<T> {

    private final Kryo kryo = new Kryo();
    private final Class<T> clazz;

    public KryoSerialize(Class<T> clazz) {
        this.clazz = clazz;
        kryo.register(clazz);
        kryo.register(Class[].class);
        kryo.register(Class.class);
        kryo.register(Object[].class);
//        kryo.setReferences(true);
    }

    @Override
    public byte[] serialize(T object) {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             Output output = new Output(byteArrayOutputStream)) {
            kryo.writeObject(output, object);
            output.flush();
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException("Serialization failed", e);
        }
    }

    @Override
    public T deserialize(byte[] bytes) {
        try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
             Input input = new Input(byteArrayInputStream)) {
            return kryo.readObject(input, clazz);
        } catch (IOException e) {
            throw new RuntimeException("Deserialization failed", e);
        }
    }
}

3.6、Protobuf 序列化

       在Java中 Kryo的性能已经很优秀了,当然也有更优秀的存在 那就是 Protobuf了。

       Protobuf 是由 Google 开发的一个高效的、平台中立的序列化协议。它允许你定义数据结构,并提供了一种机制,通过将这些结构序列化为紧凑的二进制格式,从而实现高效的数据存储和传输。我们再使用的时候 需要定义proto文件,在该文件中定义好对象的格式。然后使用 protoc 编译器 将这个文件编译成pojo类。 例如:

新建一个文件 Person.proto 

// Person.proto
syntax = "proto3";

message Person {
    string name = 1;
    int32 age = 2;
    string email = 3;
}

使用下面的命令编译这个文件

protoc --java_out=./tmp Person.proto

这个命令执行完成后会在当前目录的/tmp 下生成一个Persion.java 的文件,就可以放到项目里面去使用了 ,例如将对象写入文件中

import com.example.protobuf.PersonOuterClass.Person;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ProtobufExample {

    public static void main(String[] args) {
        // 创建一个 Person 对象并设置字段
        Person person = Person.newBuilder()
                .setName("Tom")
                .setAge(10)
                .setEmail("tom@qq.com")
                .build();

        // 序列化:将 person 对象写入文件
        try (FileOutputStream output = new FileOutputStream("person.txt")) {
            person.writeTo(output);
            System.out.println("Person object serialized to person.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化:从文件中读取 person 对象
        try (FileInputStream input = new FileInputStream("person.txt")) {
            Person deserializedPerson = Person.parseFrom(input);
            System.out.println("Deserialized Person: ");
            System.out.println("Name: " + deserializedPerson.getName());
            System.out.println("Age: " + deserializedPerson.getAge());
            System.out.println("Email: " + deserializedPerson.getEmail());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

protobuf 这种序列化方式很高效,但是也存在局限性,因为pojo类 都是固定的,所以不能灵活的集成到项目中使用,只有在特定的场景下使用

3.7、Protostuff序列化

前面我们知道了 Protobuf 性能虽然优秀,但是不够灵活,所以在他的基础上衍生出了 Protostuff。protostuff 拥有了等同于 protobuf 的性能,并且 不需要使用 protoc编译器,缺点就是仅仅适用于Java。下面我们给出相关的代码案例

依赖

        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.8.0</version>
        </dependency>

        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.8.0</version>
        </dependency>

代码

package org.wcan.serialize.strategy;

import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
import org.wcan.serialize.SerializeStrategy;

/**
 * @Description
 * @Author wcan
 * @Date 2025/1/21 上午 11:13
 * @Version 1.0
 */
public class ProtostuffSerialize<T> implements SerializeStrategy<T> {

    private Class<T> inputClass;
    private Schema<T> schema ;

    public ProtostuffSerialize(Class<T> inputClass) {
        this.inputClass = inputClass;
        this.schema = RuntimeSchema.getSchema(inputClass);
    }

    @Override
    public byte[] serialize(T object) {
        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        byte[] byteArray = ProtostuffIOUtil.toByteArray(object, schema, buffer);
        return byteArray;
    }

    @Override
    public T deserialize(byte[] bytes) {
        T object = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(bytes, object, schema);
        return object;
    }
}

 4、技术选型最佳实践

        本章节我们讨论一下常见的序列化解决方案的技术选型,首先我们排除掉JDK原生的方式,你想想 如果原生的方式足够的好,那么还会出来这么多第三方的解决方案吗。

        这里先来给出一张表格。从性能的方面罗列一下各种序列化方案的优缺点

序列化方式序列化速度反序列化速度序列化数据大小优缺点总结
JDK序列化中等中等较大易用但性能差,不适合大规模系统
JSON较慢较慢较大跨平台支持好,但性能差
Hessian较快较快较小跨语言支持,但性能不如Kryo和Protobuf
Kryo非常快非常快较小高效,但跨语言支持差
Protobuf非常快非常快很小性能最好,跨语言支持优秀
Protostuff非常快非常快更小性能最优,但社区较小

        假如 你对性能有很高的要求 那么肯定建议使用 protostuff 或者 Protobuf 。如果你的业务系统都是基于Java技术栈的,那么最佳技术选型就是  protostuff 了。如果你的业务系统技术栈比较庞杂那就可以选择Kroy或者 Protobuf。当然如果你的业务系统涉及到多种类型的平台那就建议选择JSON了。

        关于其他方面,比如 WebApi 用于前后端数据交换,尤其是 RESTful API 最佳的技术选型肯定就是JSON了。

        在大数据领域Kroy则使用的比较多,Kroy 适用于需要高效序列化的高性能应用,如大数据计算框架(例如Apache Spark)

        至于hessian 在一下RPC框架中使用的比较多 例如Dubbo和SpringCloud 

        最后总结一下,没有完美的技术,所以也就没有最优的技术选型,引入一种解决方案 同时也会引入该方案存在的问题,我们最佳实践就是根据自己业务系统中不同的场景 权衡利弊,选择合适的方案就行了 利大于弊 就是好的选型。

5、应用集成

        前面我们分析了Java中一些常用的序列化方案,各有各的优缺点,我们下面就将上述的代码示例落地成可以与应用集成的工程化项目,让用户可以用过配置指定不同的序列化方案。

5.1、搭建工程

        首先新建一个项目,serialize-store 添加依赖如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.wcan.serialize</groupId>
    <artifactId>serialize-store</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.8</version>
        </dependency>

        <dependency>
            <groupId>com.caucho</groupId>
            <artifactId>hessian</artifactId>
            <version>4.0.63</version>
        </dependency>

        <dependency>
            <groupId>com.esotericsoftware</groupId>
            <artifactId>kryo</artifactId>
            <version>5.6.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/io.protostuff/protostuff-core -->
        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.8.0</version>
        </dependency>

        <dependency>
            <groupId>io.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.8.0</version>
        </dependency>
    </dependencies>

</project>

将前面的代码加入到项目中,项目结构如下

5.2、设计一个优雅的工厂 

        我们前面已经有5种策略了,所以我们需要编写一个工厂类来实现具体策略对象的创建。简单、高效、简洁、明了。我认为这就是最好的代码了。

public class SerializeFactory {

    public static <T> SerializeStrategy<T> getSerialize(String strategy, Class<T> clazz) {
        if ("json".equals(strategy))
            return new JsonSerialize<>(clazz);
        if ("java".equals(strategy))
            return new SimpleSerialize<>();
        if ("hessian".equals(strategy))
            return new HessianSerialize<>();
        if ("kry".equals(strategy))
            return new KryoSerialize<>(clazz);
        if ("proto".equals(strategy))
            return new ProtostuffSerialize<>(clazz);
        return null;
    }
}

        虽然我觉得这样写挺好的。但是 有些 大聪明 总觉的这样写很low,  哪有在工厂里直接new的,而且还写这么多 if 。好吧,那我们再改改。我们使用 函数式接口和方法引用 来实现

相关代码如下

package org.wcan.serialize;

import org.wcan.serialize.strategy.*;
import java.util.HashMap;
import java.util.Map;

public class SerializeFactory {


    private static final Map<String, SerializeStrategyFactory<?>> strategyMap = new HashMap<>();

    static {
        strategyMap.put("json", JsonSerialize::new);
        strategyMap.put("java", SimpleSerialize::new);
        strategyMap.put("hessian", HessianSerialize::new);
        strategyMap.put("kry", KryoSerialize::new);
        strategyMap.put("proto", ProtostuffSerialize::new);
    }

    public static <T> SerializeStrategy<T> getSerialize(String strategy, Class<T> clazz) {
        SerializeStrategyFactory<T> factory = (SerializeStrategyFactory<T>) strategyMap.get(strategy);
        if (factory != null) {
            return factory.create(clazz);
        }
        return null;
    }

    @FunctionalInterface
    private interface SerializeStrategyFactory<T> {
        SerializeStrategy<T> create(Class<T> clazz);
    }
}

        为了让某些大聪明和领导感觉高大上, 本来几行代码的事我们生生的改成了几十行,有效的提高的自己的工作量,无形之中就增加了自己的产出。工厂有了 我们还需要一个通用的方法,方便集成到项目中使用类名加方法名直接进行序列化和反序列化

下面是对外暴露的静态方法 

package org.wcan.serialize;

/**
 * @Description
 * @Author wcan
 * @Date 2025/1/22 上午 10:30
 * @Version 1.0
 */
public class SerializePlus {
    private SerializePlus() {
    }
    private static <T> SerializeStrategy<T> getSerializeStrategy(String strategy, Class<T> clazz) {
        return SerializeFactory.getSerialize(strategy, clazz);
    }
    public static <T> byte[] serializer(String strategy, T object, Class<T> clazz) {
        return getSerializeStrategy(strategy, clazz).serialize(object);
    }
    public static <T> T deserialize(String strategy, byte[] bytes, Class<T> clazz) {
        return getSerializeStrategy(strategy, clazz).deserialize(bytes);
    }
}

5.3、引入Pojo测试

        前面我们已经搭建好了一个通用的序列化框架了,下面我们再来引入一组Pojo对象进行测试,我们直接把cheese 里面请求和响应对象搬过来吧

package org.wcan.cheese;

import java.io.Serializable;

/**
 * @Description
 * @Author wcan
 * @Date 2025/1/16 下午 23:37
 * @Version 1.0
 */
public interface CheeseProtocol extends Serializable {
     public void setResponseCode(Integer responseCode);
     public void setServerIp(String serverIp);
}


package org.wcan.cheese;

/**
 * @Description
 * @Author wcan
 * @Date 2025/1/17 下午 17:12
 * @Version 1.0
 */
public class AbstractCheeseProtocol implements CheeseProtocol {

    public  Integer responseCode = 200;

    public String serverIp = null;

    public  Integer getResponseCode() {
        return responseCode;
    }

    public String getServerIp() {
        return serverIp;
    }

    @Override
    public void setResponseCode(Integer responseCode) {
        this.responseCode= responseCode;
    }

    @Override
    public void setServerIp(String serverIp) {
        this.serverIp = serverIp;
    }
}
package org.wcan.cheese;

/**
 * @Description Cheese 请求对象
 * @Author wcan
 * @Date 2025/1/16 下午 23:15
 * @Version 1.0
 */
public class CheeseResponse extends AbstractCheeseProtocol {

    private Object returnValue;
    private Exception exceptionValue;


    public CheeseResponse() {
    }

    public CheeseResponse(Object returnValue, Exception exceptionValue) {
        this.returnValue = returnValue;
        this.exceptionValue = exceptionValue;
    }

    public Object getReturnValue() {
        return returnValue;
    }

    public void setReturnValue(Object returnValue) {
        this.returnValue = returnValue;
    }

    public Exception getExceptionValue() {
        return exceptionValue;
    }

    public void setExceptionValue(Exception exceptionValue) {
        this.exceptionValue = exceptionValue;
    }

    @Override
    public String toString() {
        return "CheeseResponse{" +
                "returnValue=" + returnValue +
                ", responseCode=" + responseCode +
                ", exceptionValue=" + exceptionValue +
                '}';
    }
}
package org.wcan.cheese;

import java.util.Arrays;
import java.util.Objects;

/**
 * @Description Cheese 请求对象
 * @Author wcan
 * @Date 2025/1/16 下午 23:15
 * @Version 1.0
 */
public class CheeseRequest extends AbstractCheeseProtocol{

    public String className;
    public String methodName;
    private Class<?> returnValueType;
    private Class[] parameterTypes;
    private Object[] parameterValue;

    public CheeseRequest() {

    }
    public CheeseRequest(String className, String methodName, Class<?> returnValueType, Class[] parameterTypes, Object[] parameterValue) {
        this.className = className;
        this.methodName = methodName;
        this.returnValueType = returnValueType;
        this.parameterTypes = parameterTypes;
        this.parameterValue = parameterValue;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Class<?> getReturnValueType() {
        return returnValueType;
    }

    public void setReturnValueType(Class<?> returnValueType) {
        this.returnValueType = returnValueType;
    }

    public Class[] getParameterTypes() {
        return parameterTypes;
    }

    public void setParameterTypes(Class[] parameterTypes) {
        this.parameterTypes = parameterTypes;
    }

    public Object[] getParameterValue() {
        return parameterValue;
    }

    public void setParameterValue(Object[] parameterValue) {
        this.parameterValue = parameterValue;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CheeseRequest that = (CheeseRequest) o;
        return Objects.equals(className, that.className) && Objects.equals(methodName, that.methodName) && Objects.equals(returnValueType, that.returnValueType) && Arrays.equals(parameterTypes, that.parameterTypes) && Arrays.equals(parameterValue, that.parameterValue);
    }

    @Override
    public int hashCode() {
        int result = Objects.hash(className, methodName, returnValueType);
        result = 31 * result + Arrays.hashCode(parameterTypes);
        result = 31 * result + Arrays.hashCode(parameterValue);
        return result;
    }

    @Override
    public String toString() {
        return "CheeseRequest{" +
                "className='" + className + '\'' +
                ", methodName='" + methodName + '\'' +
                ", returnValueType=" + returnValueType +
                ", parameterTypes=" + Arrays.toString(parameterTypes) +
                ", parameterValue=" + Arrays.toString(parameterValue) +
                '}';
    }
}

添加后工程结构如下

最后我们编写测试方法 就能看到效果了

public class Startup {

     @Test
     public void serializePlusTest() throws InterruptedException {
        CheeseRequest cheeseRequest = new CheeseRequest("org.wcan.test.TestDemo", "demoTest", String.class,
                new Class[]{String.class, Integer.class}, new Object[]{"乔峰", 35});
        CheeseResponse cheeseResponse = new CheeseResponse();
        cheeseResponse.setReturnValue("哈哈哈");
        cheeseResponse.setResponseCode(200);
//        byte[] bytes = SerializePlus.serializer("java", cheeseRequest, CheeseRequest.class);
//        CheeseRequest deserialize = SerializePlus.deserialize("java", bytes, CheeseRequest.class);
        byte[] javas = SerializePlus.serializer("java", cheeseResponse, CheeseResponse.class);
        CheeseResponse deserialize = SerializePlus.deserialize("java", javas, CheeseResponse.class);
        System.out.println(deserialize);
    }
}

5.4、引入Socket测试

5.4.1、流程分析

下面我们来编写一个Socket传输的案例,这个过程就是一个简单的RPC调用

1、客户端将对象序列化后发送给服务端

2、服务端接收到对象后进行反序列化解析出对象的数据

3、服务端进行反射调用目标方法,获取到执行结果

4、将执行结果封装成响应对象,经过序列化后发送给客户端

5、客户端通过反序列化解析出响应的数据

5.4.2、服务端实现

服务端代码如下

public class TestDemo {
    public String demoTest(String userName,Integer age){
        return "hi "+ userName +" 你的 age is : "+age;
    }
}
package org.wcan.server;


import org.wcan.cheese.CheeseRequest;
import org.wcan.cheese.CheeseResponse;

import java.lang.reflect.Method;

/**
 * @Description
 * @Author wcan
 * @Date 2025/1/16 下午 23:26
 * @Version 1.0
 */
public class ReflectionExecute {

    public static CheeseResponse execute(CheeseRequest cheeseRequest) {
        CheeseResponse cheeseResponse = new CheeseResponse();
        String className = cheeseRequest.getClassName();
        String methodName = cheeseRequest.getMethodName();
        try {
            Class<?> aClass = Class.forName(className);
            Method method = aClass.getMethod(methodName, cheeseRequest.getParameterTypes());
            Object invoke = method.invoke(aClass.newInstance(), cheeseRequest.getParameterValue());
            cheeseResponse.setReturnValue(invoke);
        } catch (Exception e) {
            cheeseResponse.setExceptionValue(e);
        }
        return cheeseResponse;
    }
}
package org.wcan.server;

import org.wcan.cheese.CheeseRequest;
import org.wcan.cheese.CheeseResponse;
import org.wcan.serialize.SerializePlus;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
/**
 * @ClassName ServerEndpoint
 * @Description TODO
 * @Author wcan
 * @Date 2025/2/11 上午 10:35
 * @Version 1.0
 */
public class ServerEndpoint {

    private int cheesePort = 8000;  // 监听端口
    public void ServerStart(String strategy) {
        try {
            // 打开服务器端的 ServerSocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new java.net.InetSocketAddress(cheesePort));
            serverSocketChannel.configureBlocking(false);
            // 打开 Selector
            Selector selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器正在运行,监听端口 " + cheesePort);
            //TODO 注册端口
            while (true) {
                // 阻塞,等待 I/O 事件发生
                selector.select();
                // 获取所有发生的事件
                Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
                while (selectedKeys.hasNext()) {
                    SelectionKey key = selectedKeys.next();
                    selectedKeys.remove();
                    if (key.isAcceptable()) {
                        // 接受连接请求
                        handleAccept(serverSocketChannel, selector);
                    } else if (key.isReadable()) {
                        // 处理读取请求
                        handleResponse(key,strategy);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleAccept(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
        // 接受客户端连接
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        // 注册到 Selector,监听读事件
        socketChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("新连接接入:" + socketChannel.getRemoteAddress());
    }


    private void handleResponse(SelectionKey key,String strategy) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int bytesRead = socketChannel.read(buffer);
        if (bytesRead == -1) {
            socketChannel.close();
            System.out.println("连接关闭");
            return;
        }
        buffer.flip();
        byte[] request = null;
        if (buffer.remaining() > 0) {
            request = new byte[buffer.remaining()];
            buffer.get(request);
        } else {
            System.out.println("没有数据可读");
            return;
        }
        //反序列化
        CheeseRequest cheeseRequest = SerializePlus.deserialize(strategy, request, CheeseRequest.class);
        CheeseResponse cheeseResponse = ReflectionExecute.execute(cheeseRequest);
        //序列化
        byte[] serialize = SerializePlus.serializer(strategy, cheeseResponse, CheeseResponse.class);
        buffer.clear();
        buffer.put(serialize);
        buffer.flip();
        // 发送响应数据
        while (buffer.hasRemaining()) {
            socketChannel.write(buffer);
        }
        socketChannel.close();
        System.out.println("响应已发送");
    }
}

 5.4.3、客户端实现

package org.wcan.client;

import org.wcan.cheese.CheeseRequest;
import org.wcan.cheese.CheeseResponse;
import org.wcan.serialize.SerializePlus;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @Description
 * @Author wcan
 * @Date 2025/1/17 下午 15:13
 * @Version 1.0
 */
public class ClientEndpoint {

    private final static int cheesePort = 8000;  // 监听端口

    public CheeseResponse doRequest(CheeseRequest cheeseRequest,String strategy) {
        CheeseResponse cheeseResponse = null;
        try (SocketChannel client = SocketChannel.open(new InetSocketAddress(cheeseRequest.getServerIp(), cheesePort))) {
            client.configureBlocking(false);
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            byte[] serialize = SerializePlus.serializer(strategy, cheeseRequest, CheeseRequest.class);
            buffer.put(serialize);
            buffer.flip();

            while (buffer.hasRemaining()) {
                client.write(buffer);
            }

            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            int bytesRead = 0;
            while (bytesRead == 0) {
                bytesRead = client.read(readBuffer);
            }

            if (bytesRead > 0) {
                readBuffer.flip();
                byte[] dst = new byte[readBuffer.limit()];
                readBuffer.get(dst);
                cheeseResponse = SerializePlus.deserialize(strategy, dst, CheeseResponse.class);
                System.out.println("Response from server: " + cheeseResponse);
            }
        } catch (IOException e) {
            throw new RuntimeException("IO Exception occurred during request processing", e);
        }
        return cheeseResponse;
    }
}

 5.4.4、功能测试

    @Before
    public void Startup() {
        new Thread(() -> {
            new ServerEndpoint().ServerStart("hessian");
        }).start();
    }


    @Test
    public void serializeTest() throws InterruptedException {
        CheeseRequest cheeseRequest = new CheeseRequest("org.wcan.test.TestDemo", "demoTest", String.class,
                new Class[]{String.class, Integer.class}, new Object[]{"乔峰", 35});
        cheeseRequest.setServerIp("127.0.0.1");
        Thread.sleep(1000);
        CheeseResponse cheeseResponse = new ClientEndpoint().doRequest(cheeseRequest, "hessian");
        System.out.println(cheeseResponse.getReturnValue());
    }

这里我们可以根据 doRequest 方法的 strategy  参数  动态的调整序列化的实现方式,需要注意的是  ServerStart 也要保持一致。

5、总结

本篇文章给大家介绍了序列化相关的内容以及常用的序列化方案,并且都提供了对应的代码案例,最后我们编写了一个支持多种序列化方式的通用组件,完成了一个基本的RPC调用的案例,将序列化和Socket串起来了。相信现在的你肯定知道了序列化的作用了,也意识到一个高效的序列化算法 对网络传输是的重要性了。

相关代码仓库:cheese-store


http://www.kler.cn/a/541960.html

相关文章:

  • 编译和链接【三】
  • 2.Excel:滨海市重点中学的物理统考考试情况❗(15)
  • java.io.InvalidClassException
  • [AUTOSAR通信] - PDUR模块解读
  • 微信小程序分包异步化
  • 【报错解决】Sql server 2022连接数据库时显示证书链是由不受信任的颁发机构颁发的
  • 电脑桌面如何设置待办事项,电脑桌面提醒便签推荐
  • django配置跨域
  • 支持selenium的chrome driver更新到133.0.6943.53
  • 今日AI和商界事件(2025-02-11)
  • 基于Jenkins+Maven+Java+HttpClient+TestNG+Git+Allure的持续集成测试框架搭建方案(自己写和Ai对比)
  • 数据库行转列技术详解
  • 分治范式下的快速排序全解:C++实现、时间复杂度优化与工程化实践
  • 深度对比析:DeepSeek服务优胜本地部署、网页版与蓝耘GPU智算云平台的较量以及删除本地部署的过程
  • 【项目总结】易到家家政服务平台 —— 派单调度(7)
  • Mac如何安装JMeter
  • 【数据结构】_树与二叉树
  • Flask魔法:打造你的Web应用路由王国
  • 结构形模式---适配器模式
  • zabbix 监控系统 配置钉钉告警
  • Elasticsearch 安装与使用指南
  • Mybatis源码03 - 配置解析过程(了解)
  • C#上位机--NET Standard
  • 更换a.jar包中的lib里的b.jar包里的class文件
  • 【devops】Macos 轻量化docker解决方案 orbstack | 不用Docker Desktop启动docker服务
  • 【医院成本核算专题】3.拆解医院成本构成:洞察医疗经济的核心密码