Java编程:按照指定的字段顺序,将 Bean 转换为 Json

一、背景

最近在做项目的时候,我碰到了一个问题,使用json传递报文的时候怎么保存报文的顺序?比如下面一段报文:

{
	"ec": {
		"request": {
			"header": {
				"actiontype": "00",
				"currenttime": "1586003427105"
			},
			"body": {
				"uuid": "dsasdadada",
				"subject": "CN=AO",
				"pubkey": "dsadsadsad",
				"validaty": "",
				"duration": "",
				"certhash": ""
			}
		},
		"signature": {
			"alg": "sm3withSM2",
			"pubkey": "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw==",
			"sign": "MEUCIQD1NNi3enH4IuivzTMdGt3NYuuIbui/2/zBbk/rZ/BPIQIgbDqaiS34rkkaSc/QWLaH2kfjTIT3lzWJN4M1IzzJ+TA="
		},
		"eccert": {
			"type": "00",
			"value": "fsafasdsadasdas"
		}
	},
	"signature": {
		"alg": "sm3withSM2",
		"pubkey": "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw==",
		"sign": "MEYCIQDdwZByaHxSBYGZv2mD9VjKjatzkx8FkbWRjwd+aVJuyAIhALgERohg8KTaVqkun2kjcHHe+7HBgFEIZCbyZo8m0pA2"
	}
}

这一段报文怎么创建呢?怎么确保每次创建的报文格式都是一致的呢?

二、解决方法

  1. 我的第一种解决方案(傻瓜式)
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.15</version>
</dependency>

使用fastjson的JSONObject反复创建添加,代码如下:

    
    @Test
    public void test(){
        JSONObject jsonObject = new JSONObject(true);
        JSONObject ec = new JSONObject(true);
        JSONObject request = new JSONObject(true);
        JSONObject header = new JSONObject(true);
        header.put("actiontype","00");
        header.put("currenttime","1586003427105");

        JSONObject body = new JSONObject(true);
        body.put("uuid","dsasdadada");
        body.put("subject","CN=AO");
        body.put("pubkey","dsadsadsad");
        body.put("validaty","");
        body.put("duration","");
        body.put("certhash","");
        request.put("header",header);
        request.put("body",body);

        JSONObject signature = new JSONObject();
        signature.put("alg","sm3withSM2");
        signature.put("pubkey", "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw==");
        signature.put("sign","MEUCIQD1NNi3enH4IuivzTMdGt3NYuuIbui/2/zBbk/rZ/BPIQIgbDqaiS34rkkaSc/QWLaH2kfjTIT3lzWJN4M1IzzJ+TA=");

        JSONObject eccert = new JSONObject();
        eccert.put("type","00");
        eccert.put("value","fsafasdsadasdas");
        ec.put("request",request);
        ec.put("signature",signature);
        ec.put("eccert",eccert);

        JSONObject signNature = new JSONObject();
        signNature.put("alg","sm3withSM2");
        signNature.put("pubkey", "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw==");
        signNature.put("sign", "MEYCIQDdwZByaHxSBYGZv2mD9VjKjatzkx8FkbWRjwd+aVJuyAIhALgERohg8KTaVqkun2kjcHHe+7HBgFEIZCbyZo8m0pA2");

        jsonObject.put("ec",ec);
        jsonObject.put("signature",signNature);
        System.out.println(JSONObject.toJSONString(jsonObject,true));
    }
    

打印结果:

{
	"ec":{
		"request":{
			"header":{
				"actiontype":"00",
				"currenttime":"1586003427105"
			},
			"body":{
				"uuid":"dsasdadada",
				"subject":"CN=AO",
				"pubkey":"dsadsadsad",
				"validaty":"",
				"duration":"",
				"certhash":""
			}
		},
		"signature":{
			"sign":"MEUCIQD1NNi3enH4IuivzTMdGt3NYuuIbui/2/zBbk/rZ/BPIQIgbDqaiS34rkkaSc/QWLaH2kfjTIT3lzWJN4M1IzzJ+TA=",
			"alg":"sm3withSM2",
			"pubkey":"MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw=="
		},
		"eccert":{
			"type":"00",
			"value":"fsafasdsadasdas"
		}
	},
	"signature":{
		"sign":"MEYCIQDdwZByaHxSBYGZv2mD9VjKjatzkx8FkbWRjwd+aVJuyAIhALgERohg8KTaVqkun2kjcHHe+7HBgFEIZCbyZo8m0pA2",
		"alg":"sm3withSM2",
		"pubkey":"MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw=="
	}
}

这种方法特别的麻烦,每次向服务器发起请求时必须手动编写一次,这些代码存在高度的重复性,只是body不同,其它都是类似的,这样写代码复用性不高,存在代码臃肿,不利于阅读和项目拓展。

我的第二种解决方案(静态工厂模式和建造者模式)

  1. 将报文拆分成多个可重用组件(AtRequestBody、Ec、EcCert、Head、request和Signature)
  2. 使用静态工厂方法创建对应的对象
  3. 使用建造者模式创建AtRequestBody,实例化每个对象,组装对象

这种方法代码比较多,实例化起来也不是很简单,调用也不是很方便,而且组装的时候碰到一个问题,怎么保证AtRequestBody转化成json字符串按照,怎么保证转化是按照bean的字段顺序转化呢?也是本文的标题的关键字。

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>2.8.1</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.8.1</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>2.8.1</version>
</dependency>

使用jackson的注解@JsonPropertyOrder({“ec”,“signature”}),这个会确保Java bean转化成jsonStr时按照指定顺序序列化。

package com.infosec.ra.common.build.outer.request;

import cn.com.infosec.hsm.crypto.HSM;
import cn.com.infosec.hsm.crypto.IHSM;
import cn.com.infosec.util.encoders.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.infosec.ra.common.RAConstant;
import com.infosec.ra.common.RAEnum;
import com.infosec.ra.common.build.Builder;
import com.infosec.ra.common.build.outer.Signature;
import com.infosec.ra.util.IHSMUtil;
import lombok.*;

import java.security.PublicKey;

/**
 * @author Javacfox
 */
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"ec","signature"})
public class AtRequestBody {
    private Ec ec;
    private Signature signature;

    public void setSignature(String alg){
        try {
            IHSM sjj1507 = HSM.getInst("SOFT");
            sjj1507.init();
            IHSMUtil.setSjj1507(sjj1507);
            byte[] sm3withSM2s = IHSMUtil.sign("1", 256, "12345678".toCharArray(), JSON.toJSONString(this.ec).getBytes(), "SM3withSM2", RAConstant.SM2ID);
            PublicKey p1= IHSMUtil.exportPublicKey("SM2", IHSM.SIGN, "1", 256,"12345678".toCharArray());
            byte[] encoded = p1.getEncoded();
            this.signature = Builder.of(Signature::new)
                    .with(Signature::setAlg,alg)
                    .with(Signature::setPubkey, Base64.toBase64String(encoded))
                    .with(Signature::setSign,Base64.toBase64String(sm3withSM2s))
                    .build();
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    public static String getMsg(String actionType, String certType,String cert,JSONObject jsonObject) throws JsonProcessingException {
        Ec ec = Builder.of(Ec::new)
                .with(Ec::setRequest, actionType, System.currentTimeMillis() + "", jsonObject)
                .with(Ec::setSignature, RAConstant.SIGN_ALG)
                .with(Ec::setEcCert, certType, cert)
                .build();
        AtRequestBody build = Builder.of(AtRequestBody::new)
                .with(AtRequestBody::setEc, ec)
                .with(AtRequestBody::setSignature, RAConstant.SIGN_ALG)
                .build();
        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(build);
        //JSONObject jsonObject1 = JSONObject.parseObject(JSONObject.toJSON(build).toString(), Feature.OrderedField);
        return s;
    }
}

代码比较多,就不一一贴了,不然篇幅太长。

我的第三种解决方案(java8 通用builder)

  1. 将报文拆分成多个可重用组件(AtRequestBody、Ec、EcCert、Head、request和Signature)
  2. 使用Supplier接口实例化对象
  3. 使用Consumer接口初始化对象
  4. 使用jackson确保java bean系列化时按照字段顺序

具体代码如下:

一、将报文拆分成可复用组件(java bean)

  1. Signature
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.*;

/**
 * @author Javacfox
 */
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"alg","pubkey","sign"})
public class Signature {
    private String alg;
    private String pubkey;
    private String sign;
}

2.Request

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.infosec.ra.common.build.Builder;
import lombok.*;


/**
 * @author Javacfox
 */
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"header","body"})
public class Request {
    private Head header;
    private JSONObject body;

    public void setHeader(String actiontype,String currenttime){
        this.header = Builder.of(Head::new)
                .with(Head::setActiontype,actiontype)
                .with(Head::setCurrenttime,currenttime)
                .build();
    }
}

3.head

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.*;

/**
 * @author Javacfox
 */
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"actiontype","currenttime"})
public class Head {
    private String actiontype;
    private String currenttime;
}

4.EcCert

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.*;

/**
 * @author Javacfox
 */
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"type","value"})
public class EcCert {
    private String type;
    private String value;
}

5.ec

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.infosec.ra.common.build.Builder;
import lombok.*;

/**
 * @author Javacfox
 */
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"request","signature","eccert"})
public class Ec extends RequestBody {
    private EcCert eccert;

    public void setEcCert(String type,String value){
        this.eccert = Builder.of(EcCert::new)
                .with(EcCert::setType, type)
                .with(EcCert::setValue, value)
                .build();
    }
}

6.AtRequestBody

import cn.com.infosec.hsm.crypto.HSM;
import cn.com.infosec.hsm.crypto.IHSM;
import cn.com.infosec.util.encoders.Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.infosec.ra.common.RAConstant;
import com.infosec.ra.common.RAEnum;
import com.infosec.ra.common.build.Builder;
import com.infosec.ra.common.build.outer.Signature;
import com.infosec.ra.util.IHSMUtil;
import lombok.*;

import java.security.PublicKey;

/**
 * @author Javacfox
 */
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = false)
@JsonPropertyOrder({"ec","signature"})
public class AtRequestBody {
    private Ec ec;
    private Signature signature;

    public void setSignature(String alg){
        try {
            IHSM sjj1507 = HSM.getInst("SOFT");
            sjj1507.init();
            IHSMUtil.setSjj1507(sjj1507);
            byte[] sm3withSM2s = IHSMUtil.sign("1", 256, "12345678".toCharArray(), JSON.toJSONString(this.ec).getBytes(), "SM3withSM2", RAConstant.SM2ID);
            PublicKey p1= IHSMUtil.exportPublicKey("SM2", IHSM.SIGN, "1", 256,"12345678".toCharArray());
            byte[] encoded = p1.getEncoded();
            this.signature = Builder.of(Signature::new)
                    .with(Signature::setAlg,alg)
                    .with(Signature::setPubkey, Base64.toBase64String(encoded))
                    .with(Signature::setSign,Base64.toBase64String(sm3withSM2s))
                    .build();
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    public static String getMsg(String actionType, String certType,String cert,JSONObject jsonObject) throws JsonProcessingException {
        Ec ec = Builder.of(Ec::new)
                .with(Ec::setRequest, actionType, System.currentTimeMillis() + "", jsonObject)
                .with(Ec::setSignature, RAConstant.SIGN_ALG)
                .with(Ec::setEcCert, certType, cert)
                .build();
        AtRequestBody build = Builder.of(AtRequestBody::new)
                .with(AtRequestBody::setEc, ec)
                .with(AtRequestBody::setSignature, RAConstant.SIGN_ALG)
                .build();
        ObjectMapper objectMapper = new ObjectMapper();
        String s = objectMapper.writeValueAsString(build);
        //JSONObject jsonObject1 = JSONObject.parseObject(JSONObject.toJSON(build).toString(), Feature.OrderedField);
        return s;
    }
}

二、创建建造器(Builder)

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * @author Javacfox
 */
public class Builder<T> {

    private final Supplier<T> instantiator;

    private List<Consumer<T>> modifiers = new ArrayList<>();

    public Builder(Supplier<T> instant) {
        this.instantiator = instant;
    }

    public static <T> Builder<T> of(Supplier<T> instant) {
        return new Builder<>(instant);
    }

    public <P1> Builder<T> with(Consumer1<T, P1> consumer, P1 p1) {
        Consumer<T> c = instance -> consumer.accept(instance, p1);
        modifiers.add(c);
        return this;
    }

    public <P1, P2> Builder<T> with(Consumer2<T, P1, P2> consumer, P1 p1, P2 p2) {
        Consumer<T> c = instance -> consumer.accept(instance, p1, p2);
        modifiers.add(c);
        return this;
    }

    public <P1, P2, P3> Builder<T> with(Consumer3<T, P1, P2, P3> consumer, P1 p1, P2 p2, P3 p3) {
        Consumer<T> c = instance -> consumer.accept(instance, p1, p2, p3);
        modifiers.add(c);
        return this;
    }

    public <P1, P2, P3,P4> Builder<T> with(Consumer4<T, P1, P2, P3,P4> consumer, P1 p1, P2 p2, P3 p3,P4 p4) {
        Consumer<T> c = instance -> consumer.accept(instance, p1, p2, p3,p4);
        modifiers.add(c);
        return this;
    }

    public <P1, P2, P3,P4,P5> Builder<T> with(Consumer5<T, P1, P2, P3,P4,P5> consumer, P1 p1, P2 p2, P3 p3,P4 p4,P5 p5) {
        Consumer<T> c = instance -> consumer.accept(instance, p1, p2, p3,p4,p5);
        modifiers.add(c);
        return this;
    }

    public T build() {
        T value = instantiator.get();
        modifiers.forEach(modifier -> modifier.accept(value));
        modifiers.clear();
        return value;
    }
    

    /**
     * 1 参数 Consumer
     */
    @FunctionalInterface
    public interface Consumer1<T, P1> {
        /**
         * 接收参数方法
         * @param t 对象
         * @param p1 参数二
         */
        void accept(T t, P1 p1);
    }

    /**
     * 2 参数 Consumer
     */
    @FunctionalInterface
    public interface Consumer2<T, P1, P2> {
        /**
         * 接收参数方法
         * @param t 对象
         * @param p1 参数一
         * @param p2 参数二
         */
        void accept(T t, P1 p1, P2 p2);
    }

    /**
     * 3 参数 Consumer
     */
    @FunctionalInterface
    public interface Consumer3<T, P1, P2, P3> {
        /**
         * 接收参数方法
         * @param t 对象
         * @param p1 参数一
         * @param p2 参数二
         * @param p3 参数三
         */
        void accept(T t, P1 p1, P2 p2, P3 p3);
    }

    @FunctionalInterface
    public interface Consumer4<T, P1, P2, P3, P4>{
        /**
         * 接收参数方法
         * @param t 对象
         * @param p1 参数一
         * @param p2 参数二
         * @param p3 参数三
         * @param p4 参数四
         */
        void accept(T t,P1 p1,P2 p2,P3 p3,P4 p4);
    }

    @FunctionalInterface
    public interface Consumer5<T, P1, P2, P3, P4, P5>{
        /**
         * 接收参数方法
         * @param t 对象
         * @param p1 参数一
         * @param p2 参数二
         * @param p3 参数三
         * @param p4 参数四
         * @param p5 参数五
         */
        void accept(T t,P1 p1,P2 p2,P3 p3,P4 p4,P5 p5);
    }
}
  1. 调用方法
JSONObject jsonObject = new JSONObject(true);
jsonObject.put("uuid","dsasdadada");
jsonObject.put("subject","CN=AO");
jsonObject.put("pubkey","dsadsadsad");
jsonObject.put("validaty","");
jsonObject.put("duration","");
jsonObject.put("certhash","");
System.out.println(AtRequestBody.getMsg("00",RAEnum.CertType.EC.getValue(),"fsafasdsadasdas",jsonObject));
  1. 运行结果
{
	"ec":{
		"request":{
			"header":{
				"actiontype":"00",
				"currenttime":"1586003427105"
			},
			"body":{
				"uuid":"dsasdadada",
				"subject":"CN=AO",
				"pubkey":"dsadsadsad",
				"validaty":"",
				"duration":"",
				"certhash":""
			}
		},
		"signature":{
			"sign":"MEUCIQD1NNi3enH4IuivzTMdGt3NYuuIbui/2/zBbk/rZ/BPIQIgbDqaiS34rkkaSc/QWLaH2kfjTIT3lzWJN4M1IzzJ+TA=",
			"alg":"sm3withSM2",
			"pubkey":"MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw=="
		},
		"eccert":{
			"type":"00",
			"value":"fsafasdsadasdas"
		}
	},
	"signature":{
		"sign":"MEYCIQDdwZByaHxSBYGZv2mD9VjKjatzkx8FkbWRjwd+aVJuyAIhALgERohg8KTaVqkun2kjcHHe+7HBgFEIZCbyZo8m0pA2",
		"alg":"sm3withSM2",
		"pubkey":"MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEuFn8z5HGpbTm790ZVfs0w5kHAN2qASpVINFPqDdqvd39olapX8Op+Re2+yXU5MwmxdDtVj5PmOEnyGqLuLpqZw=="
	}
}

很明显,和第一次结果是一致的,但是比起第一次少了特别多代码,很简洁干净。

总结:
代码不可能一次就能既简洁又好用,复用性和拓展性高,需要反复的重构和优化,不断的提高自己的代码能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑不语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值