Redkale

##

Redkale 入门 Convert 组件 Service 组件 Source 组件 Net 组件 Watch 组件 Redkale 插件 技术文章

### Convert 组件介绍

Convert 是一个比较独立的组件，仅依赖于util包。提供Java对象的序列化与反序列化功能。支持JSON(JavaScript Object Notation)、BSON(Binary Stream Object Notation)两种格式化。 两种格式使用方式完全一样，其性能都大幅度超过其他JSON框架。同时JSON内置于HTTP服务中，BSON也是SNCP协议数据序列化的基础。

### Convert 快速上手

本介绍仅以JSON为例(BSON与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、BsonConvert对象。 只需在所有Service、Servlet中增加依赖注入资源。

    public class XXXService implements Service {

        @Resource
        private JsonConvert jsonConvert;

        @Resource
        private BsonConvert bsonConvert;
    }

    public class XXXServlet extends HttpServlet {

        @Resource
        private JsonConvert jsonConvert;

        @Resource
        private BsonConvert bsonConvert;

    }

同一类型数据通过设置新的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();
        }

        // 获取他人基本信息
        @AuthIgnore
        @WebAction(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);  // 不包含用户的注册时间和注册地址字段信息
        }

        //获取用户自己的信息
        @WebAction(url = "/user/myinfo")
        public void mydetail(HttpRequest req, HttpResponse resp) throws IOException {
            int userid = currentUser(req).getUserid(); //获取当前用户ID
            UserSimpleInfo user = service.findUserInfo(userid);
            resp.finishJson(detailConvert, user);  // 包含用户的注册时间和注册地址字段信息
        }
    }

Convert 支持带参数构造函数。

1. public 带参数的构造函数加上 @java.beans.ConstructorProperties 注解：

    public class UserRecord {

        private int userid;

        private String username = "";

        private String password = "";

        @java.beans.ConstructorProperties({"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。
         * 3）方法的参数必须为空。
         * 4）方法的返回类型必须是Creator。
         *
         * @return
         */
        private static Creator<UserRecord> creator() {
            return new Creator<UserRecord>() {
                @Override
                @Creator.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。带参数的构造函数就需要标记@java.beans.ConstructorProperties，常见于Immutable Object。
[小技巧]: 若使用Java 8编译项的新特性，带上 -parameters 编译项进行编译的类，带参数的构造函数可以省去@ConstructorProperties注解，Convert会自动匹配参数名动态生成Creator。

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);

    BsonFactory.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和BSON。
         * —3.2) 参数类型为org.redkale.convert.json.JsonFactory 表示仅适合JSON。
         * —3.3) 参数类型为org.redkale.convert.bson.BsonFactory 表示仅适合BSON。
         * 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)。

### BSON的协议格式

BSON类似Java自带的Serializable， 其格式如下:
1). 基本数据类型: 直接转换成byte[]
2). SmallString(无特殊字符且长度小于256的字符串): length(1 byte) + byte[](utf8); 通常用于类名、字段名、枚举。
3). String: length(4 bytes) + byte[](utf8);
4). 数组: length(4 bytes) + byte[]...
5). Object:
1. realclass (SmallString) (如果指定格式化的class与实体对象的class不一致才会有该值, 该值可以使用@ConvertEntity给其取个别名)
2. 空字符串(SmallString)
3. SIGN_OBJECTB 标记位，值固定为0xBB (short)
4. 循环字段值:
4.1 SIGN_HASNEXT 标记位，值固定为1 (byte)
4.2 字段类型; 1-9为基本类型&字符串; 101-109为基本类型&字符串的数组; 127为Object
4.3 字段名 (SmallString)
4.4 字段的值Object
5. SIGN_NONEXT 标记位，值固定为0 (byte)
6. SIGN_OBJECTE 标记位，值固定为0xEE (short)