【统一接口调用的设计与实现】对象到报文的互换

    在我们的日常业务系统开发过程中,随着业务的发展,我们经常需要与外围系统进行接口对接,用以获得对方的业务能力或者将自己的业务能力提供给对方,本文主要介绍外围系统的接口调用的介绍和统一调用的设计与实现。

 
接口调用生命周期
    业务调用时,我们通常将接口接口数据按照一定的规范封装成报文或者参数,然后通过网络协议将对应的报文发送给对应的外围接口地址,外围接受到相关业务请求后,将内部处理结果,再通过约定的报文形式回传给接口调用方,整个过程如下图所示:
 
                    
 
       1)接口地址:对方提供的一个可以访问的URL地址,访问地址可以直接带一些系统级或者业务级参数
       2)请求数据:数据在消息传输过程中,首先选择通过表单参数POST/GET提交,当请求数据过大或者多变情况,我们可以将请求参数按照一定的数据格式进行封装为字符串,然后通过传输协议报文头直接传输,这里的数据格式有XML格式、Json格式等。
       3)响应数据:相应数据一般直接放入 请求响应的数据流中,获得相应字符流按照数据格式解析为对应的响应数据
       4)传输协议:一般分为:webservice,socket,http等多种形式,主流为http(s),本文主要也是基于http(s)进行实现
 
统一接口调用设计
        上面简单介绍了业务调用生命周期以及一些要点与经验,接下来看一下业务系统具体接口调用的过程。在业务系统设计中,我们通常从基础架构,业务架构,数据架构多个层面去建设,以大拆小,求同存异,进行模块设计,让模块 职责分明,高内聚,易扩展,同时模块间耦合度尽量低,调用方式尽量统一,简单。而这里我将会从业务层与接口层进行描述。
        1)业务层只需要关注业务自身逻辑,只需要在需要调用接口时调用一下接口层的API接口,调用API的数据需要是自己好获得、好理解,API调用简单明了,比如这里传入业务实体对象,而业务实体对象有接口层提供
        2)接口层不关心业务规则与流程,只需关注接口调用的规则以及其他细节,同时高扩展性,如:加解密、签名,报文封装,报文转换,并负责系统交互
        3)接口调用API需要简单、通用、易扩展特性
 
            
 
        业务层的接口调用,将相当于调用自己系统其他模块一样,底层调用是透明的。
        接下来我们开始讨论本文的重点,对象到报文的无缝转换
 
对象到报文的互换工具类
        对象到报文的互换这里指的意思为接口层将API接收到的业务对象转化为接口规范要求的接口报文(JSON/XML格式),这里我们我们选择借助第三方现成的jar包,我们只需要在其上做一个简单封装即可。
        json我们选择:jackson-core-2.2.3.jar,jackson-annotations-2.2.3.jar
        xml我们选择:xstream-1.4.7.jar
       封装我们的底层转换类:
     1)JsonUtils.java
public class JsonUtils {
    private static ObjectMapper objectMapper = new ObjectMapper();
    /**
     * Json内容转化为对象
     * @param content
     * @param valueType
     * @param <T>
     * @return
     * @throws IOException
     */
    public static  <T> T  readValue(String content, Class<T> valueType) throws IOException {
        return objectMapper.readValue(content,valueType);
    }
    /**
     * 对象转化为Json内容
     * @param t
     * @param <T>
     * @return
     * @throws IOException
     */
    public static  <T> String  writeValueAsString(T t) throws IOException {
        return objectMapper.writeValueAsString(t);
    }
}
      2) XmlUtils.java
  1. public class XmlUtils {
        
        /**
         * XML内容转化为对象
         * @param content
         * @param valueType
         * @param <T>
         * @return
         */
        public static <T> T readValue(String content, Class<T> valueType) {
            XStream xstream = new XStream(new DomDriver());
            xstream.processAnnotations(valueType);
            return (T) xstream.fromXML(content);
        }
        /**
         * 对象转化为XML内容
         * @param t
         * @param <T>
         * @return
         * @throws IOException
         */
        public static  <T> String writeValueAsString(T t) throws IOException {
            XStream xstream = new XStream(new DomDriver("utf8"));
            xstream.processAnnotations(t.getClass());// 识别obj类中的注解
            // 以格式化的方式输出XML
            return xstream.toXML(t);
        }
    }

        这样我们借助第三方jar包,我们轻松实现了这一步操作,而不用自己重复制造轮子!
 
 
通过报文模板实体类自动生成
        接口层需要向业务层提供java实体对象,我们常规的做法是根据接口规范进行逐一编写,当接口比较多且接口内容复杂时,难免感觉比较繁琐,大多数程序猿来并不希望自己来写这些无脑代码,于是这里提供另外一个工具类(BeanGeneratorUtil.java),根据数据报文示例生成实体类。
        通过Json获得类对象
  1.  /**
         * 通过Json数据生成Bean对象
         * @param packagePath
         * @param rootClassName
         * @param jsonString
         */
        public static void generateByJson(String packagePath, String rootClassName, String jsonString) throws IOException {
            JsonNode jsonNode = JacksonObjectMapper.getInstance().readTree(jsonString);
            Map<String, Object> mergeMap = new HashMap<String, Object>();
            mergeMap.put(rootClassName, parseJsonNode(jsonNode));
            generateClassByJson(packagePath, rootClassName, mergeMap.get(rootClassName), true);
        }
    /**
         * 解析Json节点信息
         * @param jsonNode
         * @return
         */
        public static Object parseJsonNode(JsonNode jsonNode) {
            if(jsonNode.isArray()) {
                List result = new ArrayList();
                for (JsonNode subNode : jsonNode) {
                    Map<String, Object> fieldMap = (Map<String, Object>)parseJsonNode(subNode);
                    mergeField(result, fieldMap);
                }
                return result;
            }
            Map<String, Object> fieldMap = new LinkedHashMap<String, Object>();
            Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> field = fields.next();
                fieldMap.put(field.getKey(), parseJsonNode(field.getValue()));
            }
            if(fieldMap.size() == 0) {
                return jsonNode.asText();
            }
            return fieldMap;
        }
    /**
         * 生成类文件
         * @param packagePath
         * @param rootClassName
         * @param data
         * @param isRoot
         */
        private static void generateClassByJson(String packagePath, String rootClassName, Object data, boolean isRoot) {
            com.hframe.generator.bean.Class beanClass = new com.hframe.generator.bean.Class();
            beanClass.setSrcFilePath("E:\\xfb_workspace\\boomshare\\bs-xfb-wx\\src\\main\\java\\");
            beanClass.setClassPackage(packagePath);
            beanClass.setClassName(rootClassName);
            beanClass.addConstructor();
            Map<String, Object> dataMap = new LinkedHashMap<String, Object>();
            if(data instanceof Map) {
                dataMap = (Map<String, Object>) data;
            }else if(data instanceof List){
                dataMap = (Map<String, Object>) ((List) data).get(0);
            }else {
                return ;
            }
            beanClass.addImportClass("com.fasterxml.jackson.annotation.JsonProperty");
            for (String fieldName : dataMap.keySet()) {
                Field field = getField(fieldName, dataMap.get(fieldName));
                field.addFieldAnno("@JsonProperty(\"" + fieldName + "\")");
                beanClass.addField(field);
                if(!"String".equals(field.getType())) {
                    if(field.getType().startsWith("List<") && !beanClass.getImportClassList().contains("java.util.List")) {
                           beanClass.addImportClass("java.util.List");
                    }
                    if(isRoot) {
                        beanClass.addImportClass(packagePath + "." + CreatorUtil.getJavaClassName(rootClassName).toLowerCase() + ".*");
                    }
                    generateClassByJson(packagePath + (isRoot ? ("." + CreatorUtil.getJavaClassName(rootClassName).toLowerCase()) : ""),
                            CreatorUtil.getJavaClassName(fieldName), dataMap.get(fieldName), false);
                }
            }
            Map map = new HashMap();
            map.put("CLASS", beanClass);
            String content = VelocityUtil.produceTemplateContent("com/hframe/generator/vm/poByTemplate.vm", map);
            System.out.println(content);
            FileUtils.writeFile(beanClass.getFilePath(), content);
        }
 
 
   通过XML获得类对象
 
/**
     * 通过Xml数据生成Bean对象
     * @param packagePath
     * @param rootClassName
     * @param xmlString
     */
    public static void generateByXml(String packagePath, String rootClassName,String rootXmlName, String xmlString) throws IOException {
        Document document = Dom4jUtils.getDocumentByContent(xmlString);
        Element root = document.getRootElement();
        Map<String, Object> mergeMap = new HashMap<String, Object>();
        mergeMap.put(rootClassName, parseXmlNode(root));
        generateClassByXml(packagePath, rootClassName, rootXmlName, mergeMap.get(rootClassName), true);
    }
 /**
     * 解析XML节点信息
     * @param element
     * @return
     */
    private static Object parseXmlNode(Element element) {
        if(checkElementIsArray(element)) {
            List result = new ArrayList();
            String xmlElementName = null;
            for (Object o : element.elements()) {
                Element subElement = (Element) o;
                xmlElementName = subElement.getName();//子元素名称
                Map<String, Object> fieldMap = (Map<String, Object>)parseXmlNode(subElement);
                mergeField(result, fieldMap);
            }
            result.add(xmlElementName);
            return result;
        }
        Map<String, Object> fieldMap = new LinkedHashMap<String, Object>();
        for (Object o : element.elements()) {
            Element subElement = (Element) o;
            fieldMap.put(subElement.getName(), parseXmlNode(subElement));
        }
        if(fieldMap.size() == 0) {
            return element.getTextTrim();
        }
        return fieldMap;
    }
/**
     * 生成类文件
     * @param packagePath
     * @param rootClassName
     * @param rootXmlName
     * @param data
     * @param isRoot
     */
    private static void generateClassByXml(String packagePath, String rootClassName,String rootXmlName, Object data, boolean isRoot) {
        com.hframe.generator.bean.Class beanClass = new com.hframe.generator.bean.Class();
        beanClass.setSrcFilePath("E:\\xfb_workspace\\boomshare\\bs-xfb-wx\\src\\main\\java\\");
        beanClass.setClassPackage(packagePath);
        beanClass.setClassName(rootClassName);
        beanClass.addConstructor();
        beanClass.addAnnotation("@XStreamAlias(\"" + rootXmlName + "\")");
        Map<String, Object> dataMap = new LinkedHashMap<String, Object>();
        if(data instanceof Map) {
            dataMap = (Map<String, Object>) data;
        }else if(data instanceof List){
            dataMap = (Map<String, Object>) ((List) data).get(0);
        }else {
            return ;
        }
        beanClass.addImportClass("com.thoughtworks.xstream.annotations.XStreamAlias");
        beanClass.addImportClass("com.thoughtworks.xstream.annotations.XStreamAsAttribute");
        for (String fieldName : dataMap.keySet()) {
            String subElementName = getSubElementName(dataMap.get(fieldName));
            Field field = getField(fieldName, dataMap.get(fieldName), subElementName);
            field.addFieldAnno("@XStreamAlias(\"" + fieldName + "\")");
            beanClass.addField(field);
            if(!"String".equals(field.getType())) {
                if(field.getType().startsWith("List<") && !beanClass.getImportClassList().contains("java.util.List")) {
                    beanClass.addImportClass("java.util.List");
                }
                if(isRoot) {
                    beanClass.addImportClass(packagePath + "." + CreatorUtil.getJavaClassName(rootClassName).toLowerCase() + ".*");
                }
                if(subElementName != null) {
                    generateClassByXml(packagePath + (isRoot ? ("." + CreatorUtil.getJavaClassName(rootClassName).toLowerCase()) : ""),
                            CreatorUtil.getJavaClassName(subElementName), subElementName, dataMap.get(fieldName), false);
                }else {
                    generateClassByXml(packagePath + (isRoot ? ("." + CreatorUtil.getJavaClassName(rootClassName).toLowerCase()) : ""),
                            CreatorUtil.getJavaClassName(fieldName), fieldName, dataMap.get(fieldName), false);
                }
            }
        }
        Map map = new HashMap();
        map.put("CLASS", beanClass);
        String content = VelocityUtil.produceTemplateContent("com/hframe/generator/vm/poByTemplate.vm", map);
        System.out.println(content);
        FileUtils.writeFile(beanClass.getFilePath(), content);
    } 
其他代码略,有兴趣可以找我要全套代码
 
 
测试-Json
    1)准备测试报文-test.json
  1. {
      "button":[
        {
          "type":"click",
          "name":"今日歌曲",
          "key":"V1001_TODAY_MUSIC"
        },
        {
          "name":"菜单",
          "sub_button":[
            {
              "type":"view",
              "name":"搜索",
              "url":"http://www.soso.com/"
            },
            {
              "type":"view",
              "name":"视频",
              "url":"http://v.qq.com/"
            },
            {
              "type":"click",
              "name":"赞一下我们",
              "key":"V1001_GOOD"
            }]
        }]
    }
    2)调用代码生成器
   public static void main(String[] args) throws IOException {
        String rootClassPath = Thread.currentThread().getContextClassLoader ().getResource("").getPath();
        String jsonString = FileUtils.readFile(rootClassPath + "test.json");
        generateByJson("com.wechat.bean.request","Menu",jsonString);
    }
 
   3)生成类文件
   Menu.java
   [menu]
      |-Button.java
      |-SubButton.java
        
代码如下:
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class Menu   {
    @JsonProperty("button")
    private List<Button> buttonList;
    public Menu() {
        }
    public List<Button> getButtonList() {
        return buttonList;
    }
    public void setButtonList(List<Button> buttonList) {
        this.buttonList = buttonList;
    }
}
public class Button   {
    @JsonProperty("type")
    private String type;
    @JsonProperty("name")
    private String name;
    @JsonProperty("key")
    private String key;
    @JsonProperty("sub_button")
    private List<SubButton> subButtonList;
    public Button() {
        }
    
    public String getType(){
        return type;
    }
    public void setType(String type){
        this.type = type;
    }
     
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
     
    public String getKey(){
        return key;
    }
    public void setKey(String key){
        this.key = key;
    }
     
    public List<SubButton> getSubButtonList(){
        return subButtonList;
    }
    public void setSubButtonList(List<SubButton> subButtonList){
        this.subButtonList = subButtonList;
    }
}
public class SubButton   {
    @JsonProperty("type")
    private String type;
    @JsonProperty("name")
    private String name;
    @JsonProperty("url")
    private String url;
    @JsonProperty("key")
    private String key;
    public SubButton() {
    }
   
 
     
    public String getType(){
        return type;
    }
    public void setType(String type){
        this.type = type;
    }
     
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
     
    public String getUrl(){
        return url;
    }
    public void setUrl(String url){
        this.url = url;
    }
     
    public String getKey(){
        return key;
    }
    public void setKey(String key){
        this.key = key;
    }
}
 
   4)验证类文件
  
  1. public static void main(String[] args) throws IOException {
            String rootClassPath = Thread.currentThread().getContextClassLoader ().getResource("").getPath();
            String jsonString = readFile(rootClassPath + "test.json");
            Menu menu = readValue(jsonString, Menu.class);
            System.out.println(writeValueAsString(menu));
        }

   5)输出结果
{"button":[{"type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC","sub_button":null},{"type":null,"name":"菜单","key":null,"sub_button":[{"type":"view","name":"搜索","url":"http://www.soso.com/","key":null},{"type":"view","name":"视频","url":"http://v.qq.com/","key":null},{"type":"click","name":"赞一下我们","url":null,"key":"V1001_GOOD"}]}]}
 
        通过!
 
测试-XML
  1)准备测试报文-test.xml
    
<persons>
    <type>001</type>
    <listPerson>
        <person>
            <name>6666554</name>
            <sex>lavasoft</sex>
            <tel>man</tel>
            <addes>
                <address>
                    <addType>type1</addType>
                    <place>郑州市经三路财富广场1</place>
                </address>
                <address>
                    <addType>type2</addType>
                    <place>郑州市经三路财富广场2</place>
                </address>
            </addes>
        </person>
        <person>
            <name>7777754</name>
            <sex>yutian</sex>
            <tel>man</tel>
            <addes>
                <address>
                    <addType>type3</addType>
                    <place>郑州市经三路财富广场3</place>
                </address>
                <address>
                    <addType>type4</addType>
                    <place>郑州市经三路财富广场4</place>
                </address>
            </addes>
        </person>
    </listPerson>
</persons>
 
 2)调用代码生成器
  1.  String xmlString = FileUtils.readFile(rootClassPath + "test.xml");
     generateByXml("com.wechat.bean.request","Persons","persons",xmlString);
 
3)生成类文件
@XStreamAlias("persons")
public class Persons   {
    @XStreamAlias("type")
    private String type;
    @XStreamAlias("listPerson")
    private List<Person> personList;
    public Persons() {
        }
   
 
     
    public String getType(){
        return type;
    }
    public void setType(String type){
        this.type = type;
    }
     
    public List<Person> getPersonList(){
        return personList;
    }
    public void setPersonList(List<Person> personList){
        this.personList = personList;
    }
}
@XStreamAlias("person")
public class Person   {
    @XStreamAlias("name")
    private String name;
    @XStreamAlias("sex")
    private String sex;
    @XStreamAlias("tel")
    private String tel;
    @XStreamAlias("addes")
    private List<Address> addressList;
    public Person() {
        }
   
 
     
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
     
    public String getSex(){
        return sex;
    }
    public void setSex(String sex){
        this.sex = sex;
    }
     
    public String getTel(){
        return tel;
    }
    public void setTel(String tel){
        this.tel = tel;
    }
     
    public List<Address> getAddressList(){
        return addressList;
    }
    public void setAddressList(List<Address> addressList){
        this.addressList = addressList;
    }
}
@XStreamAlias("address")
public class Address   {
    @XStreamAlias("addType")
    private String addtype;
    @XStreamAlias("place")
    private String place;
    public Address() {
        }
   
 
     
    public String getAddtype(){
        return addtype;
    }
    public void setAddtype(String addtype){
        this.addtype = addtype;
    }
     
    public String getPlace(){
        return place;
    }
    public void setPlace(String place){
        this.place = place;
    }
}
 
  4)验证类文件
  
public static void main(String[] args) throws IOException {
        String rootClassPath = Thread.currentThread().getContextClassLoader ().getResource("").getPath();
        String xmlString = readFile(rootClassPath + "test.xml");
        System.out.println(xmlString);
        Persons person = readValue(xmlString, Persons.class);
        System.out.println(writeValueAsString(person));
    }
 5)输出结果
<persons>
    <type>001</type>
    <listPerson>
        <person>
            <name>6666554</name>
            <sex>lavasoft</sex>
            <tel>man</tel>
            <addes>
                <address>
                    <addType>type1</addType>
                    <place>郑州市经三路财富广场1</place>
                </address>
                <address>
                    <addType>type2</addType>
                    <place>郑州市经三路财富广场2</place>
                </address>
            </addes>
        </person>
        <person>
            <name>7777754</name>
            <sex>yutian</sex>
            <tel>man</tel>
            <addes>
                <address>
                    <addType>type3</addType>
                    <place>郑州市经三路财富广场3</place>
                </address>
                <address>
                    <addType>type4</addType>
                    <place>郑州市经三路财富广场4</place>
                </address>
            </addes>
        </person>
    </listPerson>
</persons>
 
 通过!~
 
 
原文地址:https://www.cnblogs.com/hframe/p/5152088.html