Convert 组件介绍
Convert 是一个比较独立的组件,仅依赖于util包。提供Java对象的序列化与反序列化功能。支持JSON、PROTOBUF两种格式化。 两种格式使用方式完全一样,其性能都大幅度超过其他JSON框架。同时JSON内置于HTTP服务中,PROTOBUF也是SNCP协议数据序列化的基础。
Convert 快速上手
本介绍仅以JSON为例(PROTOBUF与JSON使用方式雷同)。其操作类主要是JsonConvert,配置类主要是JsonFactory、ConvertColumn。JsonFactory采用同ClassLoader类似的双亲委托方式设计。
JsonConvert 序列化encode方法:
public String convertTo(final Object value);
public String convertTo(final Type type, final Object value);
public void convertTo(final OutputStream out, final Object value);
public void convertTo(final OutputStream out, final Type type, final Object value);
public ByteBuffer[] convertTo(final java.util.function.Supplier<ByteBuffer> supplier, final Object value);
public ByteBuffer[] convertTo(final java.util.function.Supplier<ByteBuffer> supplier, final Type type, final Object value);
public void convertTo(final JsonWriter writer, final Object value);
public void convertTo(final JsonWriter writer, final Type type, final Object value);
JsonConvert 反序列化decode方法:
public <T> T convertFrom(final Type type, final String text);
public <T> T convertFrom(final Type type, final char[] text);
public <T> T convertFrom(final Type type, final char[] text, final int start, final int len);
public <T> T convertFrom(final Type type, final InputStream in);
public <T> T convertFrom(final Type type, final ByteBuffer... buffers);
public <T> T convertFrom(final Type type, final JsonReader reader);
Convert 与 ByteBuffer 的结合
从以上的方法可以看出,与其他JSON框架相比Convert多了与ByteBuffer结合的方法。特别是convertTo方法加了Supplier<ByteBuffer>方法,这么做是为了提高数据传输的性能。在大部分情况下JSON序列化得到的数据流是为了传输出去,常见的场景就是HTTP+JSON接口。Convert提供ByteBuffer接口会大量减少中间临时数据的产生。大部分输出JSON数据的方法如下:
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String json = new Gson().toJson(record);
resp.setContentType("text/json; charset=UTF-8");
resp.getOutputStream().write(json.getBytes("UTF-8"));
}
几乎所有的JSON框架提供的接口以String作为返回结果为主,其内在都是以char[]作为JsonWriter的载体。以Gson为例,Gson拼接JSON默认使用的是StringWriter,StringWriter的扩容策略是翻倍。为了方便计算,假设一个对象转换成JSON字符串大小为了10K。Gson在转换过程中产生的临时的char[]的大小: 16 + 32 + 64 + 128 + 256 + 512 + 1K + 2K + 4K + 8K + 16K = 32K, char[]转换成最终的String结果又会产生10K的char[], 最后在response输出时又会产生10K的byte[](方便计算不考虑双字节),也就是说整个对象输出过程中会产生52K的临时数据。而且常见的HTTP服务器(如实现java-servlet规范的服务器)不会把底层的ByteBuffer对象池暴露给上层。所以以String为输出结果的JSON方法都会产生5倍于数据体积大小(其他低于1倍扩容策略的框架会产生更多)的垃圾数据。
Redkale框架的HTTP服务内置了Convert的JSON接口,避免了大量的垃圾数据产生。Redkale的HTTP是基于AIO(NIO.2)实现且存在ByteBuffer对象池,response的finishJson系列方法将HTTP服务的ByteBuffer对象池传给Convert, 使Convert在序列化过程中直接以UTF-8编码方式输出到ByteBuffer里,输出结束后将ByteBuffer交给对象池回收,从而减少大量构建bye[]、char[]所产生的临时数据。
protected void execute(HttpRequest req, HttpResponse resp) throws IOException {
resp.finishJson(record);
}
Convert 基本用法:
public class UserRecord {
private int userid;
private String username = "";
private String password = "";
public UserRecord() {
}
/** 以下省略getter setter方法 */
}
public static void main(String[] args) throws Exception {
UserRecord user = new UserRecord();
user.setUserid(100);
user.setUsername("redkalename");
user.setPassword("123456");
final JsonConvert convert = JsonConvert.root();
String json = convert.convertTo(user);
System.out.println(json); //应该是 {"password":"123456","userid":100,"username":"redkalename"}
UserRecord user2 = convert.convertFrom(UserRecord.class, json);
System.out.println(convert.convertTo(user2)); //应该也是 {"password":"123456","userid":100,"username":"redkalename"}
/**
* 以下功能是为了屏蔽password字段。
* 等价于 public String getPassword() 加上 @ConvertColumn :
*
* @ConvertColumn(ignore = true, type = ConvertType.JSON)
* public String getPassword() {
* return password;
* }
**/
final JsonFactory childFactory = JsonFactory.root().createChild();
childFactory.register(UserRecord.class, true, "password"); //屏蔽掉password字段使其不输出
childFactory.reloadCoder(UserRecord.class); //重新加载Coder使之覆盖父Factory的配置
final JsonConvert childConvert = childFactory.getConvert();
json = childConvert.convertTo(user);
System.out.println(json); //应该是 {"userid":100,"username":"redkalename"}
user2 = childConvert.convertFrom(UserRecord.class, json);
System.out.println(childConvert.convertTo(user2)); //应该也是 {"userid":100,"username":"redkalename"}
}
在Redkale里存在默认的JsonConvert、ProtobufConvert对象。 只需在所有Service、Servlet中增加依赖注入资源。
public class XXXService implements Service {
@Resource
private JsonConvert jsonConvert;
@Resource
private ProtobufConvert protobufConvert;
}
public class XXXServlet extends HttpServlet {
@Resource
private JsonConvert jsonConvert;
@Resource
private ProtobufConvert protobufConvert;
}
同一类型数据通过设置新的JsonFactory可以有不同的输出。
public class UserSimpleInfo {
private int userid;
private String username = "";
@ConvertColumn(ignore = true, type = ConvertType.JSON)
private long regtime; //注册时间
@ConvertColumn(ignore = true, type = ConvertType.JSON)
private String regaddr = ""; //注册IP
/** 以下省略getter setter方法 */
}
public class UserInfoServlet extends HttpBaseServlet {
@Resource
private UserSerice service;
@Resource
private JsonFactory jsonRootFactory;
@Resource
private JsonConvert detailConvert;
@Override
public void init(HttpContext context, AnyValue config) {
final JsonFactory childFactory = jsonRootFactory.createChild();
childFactory.register(UserSimpleInfo.class, false, "regtime", "regaddr"); //允许输出注册时间与注册地址
childFactory.reloadCoder(UserSimpleInfo.class); //重新加载Coder使之覆盖父Factory的配置
this.detailConvert = childFactory.getConvert();
}
// 获取他人基本信息
@HttpMapping(url = "/user/info/")
public void info(HttpRequest req, HttpResponse resp) throws IOException {
int userid = Integer.parseInt(req.getRequstURILastPath());
UserSimpleInfo user = service.findUserInfo(userid);
resp.finishJson(user); // 不包含用户的注册时间和注册地址字段信息
}
//获取用户自己的信息
@HttpMapping(url = "/user/myinfo")
public void mydetail(HttpRequest req, HttpResponse resp) throws IOException {
int userid = req.currentUser().getUserid(); //获取当前用户ID
UserSimpleInfo user = service.findUserInfo(userid);
resp.finishJson(detailConvert, user); // 包含用户的注册时间和注册地址字段信息
}
}
Convert 支持带参数构造函数。
1. public 带参数的构造函数加上 @ConstructorParameters 注解:
public class UserRecord {
private int userid;
private String username = "";
private String password = "";
@ConstructorParameters({"userid", "username", "password"})
public UserRecord(int userid, String username, String password) {
this.userid = userid;
this.username = username;
this.password = password;
}
public int getUserid() {
return userid;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
2. 自定义Creator:
public class UserRecord {
private int userid;
private String username = "";
private String password = "";
UserRecord(int userid, String username, String password) {
this.userid = userid;
this.username = username;
this.password = password;
}
public int getUserid() {
return userid;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
/**
* 自定义Creator方法。
* 1) 方法名可以随意。
* 2) 方法必须是static。声明为private的方法只能被当前类使用,若想方法被子类使用,需要声明protected
* 3)方法的参数必须为空。
* 4)方法的返回类型必须是Creator。
*
* @return
*/
private static Creator<UserRecord> creator() {
return new Creator<UserRecord>() {
@Override
@ConstructorParameters({"userid", "username", "password"}) //带参数的构造函数必须有ConstructorParameters注解
public UserRecord create(Object... params) {
return new UserRecord((params[0] == null ? 0 : (Integer) params[0]), (String) params[1], (String) params[2]);
}
};
}
}
通常JavaBean类默认有个public空参数的构造函数,因此大部分情况下不要自定义Creator,其实只要不是private的空参数构造函数Convert都能自动支持(其他的框架都仅支持public的构造函数),只有仅含private的构造函数才需要自定义Creator。带参数的构造函数就需要标记@ConstructorParameters,常见于Immutable Object。
Convert 支持自定义Decode、Encode。
1. 通过ConvertFactory显式的注册:
public class FileSimpleCoder<R extends Reader, W extends Writer> extends SimpledCoder<R, W, File> {
public static final FileSimpleCoder instance = new FileSimpleCoder();
@Override
public void convertTo(W out, File value) {
out.writeString(value == null ? null : value.getPath());
}
@Override
public File convertFrom(R in) {
String path = in.readString();
return path == null ? null : new File(path);
}
}
JsonFactory.root().register(java.io.File.class, FileSimpleCoder.instance);
ProtobufFactory.root().register(java.io.File.class, FileSimpleCoder.instance);
2. 通过JavaBean类自定义静态方法自动加载:
public class InnerCoderEntity {
private final String val;
private final int id;
private InnerCoderEntity(int id, String value) {
this.id = id;
this.val = value;
}
public static InnerCoderEntity create(int id, String value) {
return new InnerCoderEntity(id, value);
}
/**
* 该方法提供给Convert组件自动加载。
* 1) 方法名可以随意。
* 2) 方法必须是static
* 3)方法的参数有且只能有一个, 且必须是org.redkale.convert.ConvertFactory或子类。
* —3.1) 参数类型为org.redkale.convert.ConvertFactory 表示适合JSON和PROTOBUF。
* —3.2) 参数类型为org.redkale.convert.json.JsonFactory 表示仅适合JSON。
* —3.3) 参数类型为org.redkale.convert.pb.ProtobufFactory 表示仅适合PROTOBUF。
* 4)方法的返回类型必须是org.redkale.convert.Decodeable/org.redkale.convert.Encodeable/org.redkale.convert.SimpledCoder
* 若返回类型不是org.redkale.convert.SimpledCoder, 就必须提供两个方法: 一个返回Decodeable 一个返回 Encodeable。
*
* @param factory
* @return
*/
private static SimpledCoder<Reader, Writer, InnerCoderEntity> createConvertCoder(final org.redkale.convert.ConvertFactory factory) {
return new SimpledCoder<Reader, Writer, InnerCoderEntity>() {
//必须与EnMember[] 顺序一致
private final DeMember[] deMembers = new DeMember[]{
DeMember.create(factory, InnerCoderEntity.class, "id"),
DeMember.create(factory, InnerCoderEntity.class, "val")};
//必须与DeMember[] 顺序一致
private final EnMember[] enMembers = new EnMember[]{
EnMember.create(factory, InnerCoderEntity.class, "id"),
EnMember.create(factory, InnerCoderEntity.class, "val")};
@Override
public void convertTo(Writer out, InnerCoderEntity value) {
if (value == null) {
out.writeObjectNull(InnerCoderEntity.class);
return;
}
out.writeObjectB(value);
for (EnMember member : enMembers) {
out.writeObjectField(member, value);
}
out.writeObjectE(value);
}
@Override
public InnerCoderEntity convertFrom(Reader in) {
if (in.readObjectB(InnerCoderEntity.class) == null) return null;
int index = 0;
final Object[] params = new Object[deMembers.length];
while (in.hasNext()) {
DeMember member = in.readFieldName(deMembers); //读取字段名
in.readBlank(); //读取字段名与字段值之间的间隔符,JSON则是跳过冒号:
if (member == null) {
in.skipValue(); //跳过不存在的字段的值, 一般不会发生
} else {
params[index++] = member.read(in);
}
}
in.readObjectE(InnerCoderEntity.class);
return InnerCoderEntity.create(params[0] == null ? 0 : (Integer) params[0], (String) params[1]);
}
};
}
public int getId() {
return id;
}
public String getVal() {
return val;
}
@Override
public String toString() {
return JsonConvert.root().convertTo(this);
}
}
由上可以看出,Convert的自定义配置完全符合面向对象思想,提倡在JavaBean内部去自定义非常规的构造函数或Decode、Encode方法,通过ConvertFactory显式配置的方式通常用于非自己定义的数据类(如 java.io.File)。