【統一接口調用的設計與實現】-對象到報文的互換


    在我們的日常業務系統開發過程中,隨着業務的發展,我們經常需要與外圍系統進行接口對接,用以獲得對方的業務能力或者將自己的業務能力提供給對方,本文主要介紹外圍系統的接口調用的介紹和統一調用的設計與實現。

 
接口調用生命周期
    業務調用時,我們通常將接口接口數據按照一定的規范封裝成報文或者參數,然后通過網絡協議將對應的報文發送給對應的外圍接口地址,外圍接受到相關業務請求后,將內部處理結果,再通過約定的報文形式回傳給接口調用方,整個過程如下圖所示:
 
                        
 
        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>
 
 通過!~
 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM