自定义spring mvc和feign对应的protostuff消息转换器

本文介绍如何在SpringMVC和Feign中自定义Protostuff消息转换器,包括实现高性能序列化与反序列化的方法,并提供具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、自定义spring mvc protostuff消息转换器

1.protostuff是一个序列化和反序列化的高性能类型,它比json字节数少,且快,适合做rpc调用

2.自定义protostuff消息转换器,代码如下:

public class ProtostuffHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
    /**
     * 避免每次序列化都重新申请Buffer空间
     */
    private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);

    /**
     * 缓存Schema
     */
    private static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<>();

    public ProtostuffHttpMessageConverter() {
        super(new MediaType("application", "xxx-protostuff", Charset.forName("UTF-8")));
    }

    @Override
    protected boolean supports(Class aClass) {
        return true;
    }

    @Override
    protected Object readInternal(Class aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        InputStream in = httpInputMessage.getBody();
        byte[] outByte = IOUtils.toByteArray(in);
        return this.deserialize(outByte, aClass);
    }

    public <T> T deserialize(byte[] data, Class<T> clazz) {
        Schema<T> schema = getSchema(clazz);
        T obj = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(data, obj, schema);
        return obj;
    }

    @Override
    protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        httpOutputMessage.getBody().write(this.serialize(o));
    }

    private <T> byte[] serialize(T obj) {
        Class<T> clazz = (Class<T>) obj.getClass();
        Schema<T> schema = getSchema(clazz);
        byte[] data;
        try {
            data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }
        return data;
    }

    private static <T> Schema<T> getSchema(Class<T> clazz) {
        Schema<T> schema = (Schema<T>) schemaCache.get(clazz);
        if (Objects.isNull(schema)) {
            //这个schema通过RuntimeSchema进行懒创建并缓存
            //所以可以一直调用RuntimeSchema.getSchema(),这个方法是线程安全的
            schema = RuntimeSchema.getSchema(clazz);
            if (Objects.nonNull(schema)) {
                schemaCache.put(clazz, schema);
            }
        }
        return schema;
    }
}

①在构造方法中定义一个支持的媒体类型:application/xxx-protostuff

②readInternal方法用protostuff对输入的参数做反序列化工作

③writeInternal方法使用protostuff对返回的对象做序列化操作

3.注册自定义的消息转换器到spring中,代码如下:

@Configuration
public class MvcConfig extends WebMvcConfigurationSupport {
	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    	converters.add(protostuffHttpMessageConverter());
	}

	@Bean
	public ProtostuffHttpMessageConverter protostuffHttpMessageConverter() {
    	return new ProtostuffHttpMessageConverter();
	}
}

4.暴露一个服务接口,供下面的的feign调用,代码如下:

@Api(tags = "protostuff消息转换器服务")
@RestController
@RequestMapping(ApiConstants.PATH + "/protostuff")
public class ProtostuffController {

    @ApiOperation(value = "反序列化protostuff二进制数据")
    @PostMapping(value = "/deserialize")
    public ResponseVO<?> deserialize(@RequestBody ProtostuffEntity entity) {
        return ResponseVO.ok(entity);
    }

    @ApiOperation(value = "反序列化protostuff二进制数据1")
    @PostMapping(value = "/deserialize1")
    public ResponseVO<?> deserialize1(@RequestBody ProtostuffEntity entity) {
        return ResponseVO.ok(entity);
    }
}

二、自定义feign protostuff消息转换器

1.自定义feign protostuff消息转换器,代码如下:

public class ProtostuffHttpMessageConverter implements GenericHttpMessageConverter<Object> {
    /**
     * 避免每次序列化都重新申请Buffer空间
     */
    private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
    /**
     * 缓存Schema
     */
    private static Map<Class<?>, Schema<?>> schemaCache = new ConcurrentHashMap<>();

    @Override
    public boolean canRead(Type type, Class<?> aClass, MediaType mediaType) {
        if (Objects.isNull(mediaType)) {
            return false;
        }
        MediaType supportType = new MediaType("application", "xxx-protostuff", Charset.forName("UTF-8"));
        return supportType.toString().startsWith(mediaType.toString());
    }

    @Override
    public boolean canRead(Class<?> aClass, MediaType mediaType) {
        if (Objects.isNull(mediaType)) {
            return false;
        }
        MediaType supportType = new MediaType("application", "xxx-protostuff", Charset.forName("UTF-8"));
        return supportType.toString().startsWith(mediaType.toString());
    }

    @Override
    public boolean canWrite(Class<?> aClass, MediaType mediaType) {
        if (Objects.isNull(mediaType)) {
            return false;
        }
        MediaType supportType = new MediaType("application", "xxx-protostuff", Charset.forName("UTF-8"));
        return supportType.toString().startsWith(mediaType.toString());
    }

    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return Stream.of(new MediaType("application", "xxx-protostuff", Charset.forName("UTF-8"))).collect(Collectors.toList());
    }

    @Override
    public Object read(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        return readInternal(aClass, httpInputMessage);
    }

    @Override
    public void write(Object o, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        writeInternal(o, httpOutputMessage);
    }

    @Override
    public Object read(Type type, Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        ParameterizedTypeImpl type1 = (ParameterizedTypeImpl) type;
        return readInternal(type1.getRawType(), httpInputMessage);
    }

    @Override
    public boolean canWrite(Type type, Class<?> aClass, MediaType mediaType) {
        if (Objects.isNull(mediaType)) {
            return false;
        }
        MediaType supportType = new MediaType("application", "xxx-protostuff", Charset.forName("UTF-8"));
        return supportType.toString().startsWith(mediaType.toString());
    }

    @Override
    public void write(Object o, Type type, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        writeInternal(o, httpOutputMessage);
    }

    protected Object readInternal(Class aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
        InputStream in = httpInputMessage.getBody();
        byte[] outByte = IOUtils.toByteArray(in);
        return this.deserialize(outByte, aClass);
    }

    public <T> T deserialize(byte[] data, Class<T> clazz) {
        Schema<T> schema = getSchema(clazz);
        T obj = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(data, obj, schema);
        return obj;
    }

    protected void writeInternal(Object o, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        httpOutputMessage.getBody().write(this.serialize(o));
    }

    private <T> byte[] serialize(T obj) {
        Class<T> clazz = (Class<T>) obj.getClass();
        Schema<T> schema = getSchema(clazz);
        byte[] data;
        try {
            data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }
        return data;
    }

    private static <T> Schema<T> getSchema(Class<T> clazz) {
        Schema<T> schema = (Schema<T>) schemaCache.get(clazz);
        if (Objects.isNull(schema)) {
            //这个schema通过RuntimeSchema进行懒创建并缓存
            //所以可以一直调用RuntimeSchema.getSchema(),这个方法是线程安全的
            schema = RuntimeSchema.getSchema(clazz);
            if (Objects.nonNull(schema)) {
                schemaCache.put(clazz, schema);
            }
        }
        return schema;
    }
}

①fegin中统一解码的入口类是SpringEncoder,SpringEncoder获取的converter需要继承GenericHttpMessageConverter类才可生效,否则获取到的responseClass为null,会报no suitable HttpMessageConverter found for response type错误

②canRead和canWirte方法中对媒体类型做判断,如果为application/xxx-protostuff媒体类型,就返回true

2.注册自定义的feign protostuff消息转换,如上面3.注册相同

3.写一个测试fegin client,调用上面的服务,代码如下:

@FeignClient(value = "tsc", url = "http://localhost:8099/api/tsc/protostuff")
public interface ProtostuffFeignClient {
    @ApiOperation(value = "protostuff二进制数据")
    @PostMapping(value = "/deserialize", consumes = {"application/xxx-protostuff"}, produces = {"application/xxx-protostuff"})
    ResponseVO<ProtostuffEntity> deserialize(@RequestBody ProtostuffEntity entity);

    @ApiOperation(value = "反序列化protostuff二进制数据1")
    @PostMapping(value = "/deserialize1")
    public ResponseVO<ProtostuffEntity> deserialize1(@RequestBody ProtostuffEntity entity);
}

4.写一个测试调用的controller,调用上面的feign服务,代码如下:

@RestController
@RequestMapping(ApiConstants.PATH + "/rpc-client")
public class ProtostuffClientController {

    @Autowired
    private ProtostuffFeignClient feignClient;

    @ApiOperation(value = "调试protostuff")
    @GetMapping(value = "/protostuff")
    public ResponseVO<?> query() {
        ProtostuffEntity entity = new ProtostuffEntity();
        entity.setDesc("protostuff");
        entity.setId("protostuff-xxx");
        ResponseVO<ProtostuffEntity> ret = feignClient.deserialize(entity);
        return ResponseVO.ok(ret);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值