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

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

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

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

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

    /**
     * 缓存Schema
     */
    private static Map, 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 deserialize(byte[] data, Class clazz) {
        Schema 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  byte[] serialize(T obj) {
        Class clazz = (Class) obj.getClass();
        Schema schema = getSchema(clazz);
        byte[] data;
        try {
            data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }
        return data;
    }

    private static  Schema getSchema(Class clazz) {
        Schema schema = (Schema) 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> 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 {
    /**
     * 避免每次序列化都重新申请Buffer空间
     */
    private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
    /**
     * 缓存Schema
     */
    private static Map, 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 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 deserialize(byte[] data, Class clazz) {
        Schema 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  byte[] serialize(T obj) {
        Class clazz = (Class) obj.getClass();
        Schema schema = getSchema(clazz);
        byte[] data;
        try {
            data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }
        return data;
    }

    private static  Schema getSchema(Class clazz) {
        Schema schema = (Schema) 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 deserialize(@RequestBody ProtostuffEntity entity);

    @ApiOperation(value = "反序列化protostuff二进制数据1")
    @PostMapping(value = "/deserialize1")
    public ResponseVO 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 ret = feignClient.deserialize(entity);
        return ResponseVO.ok(ret);
    }
}

你可能感兴趣的:(Spring,spring,mvc,java)