在我們的日常業務系統開發過程中,隨着業務的發展,我們經常需要與外圍系統進行接口對接,用以獲得對方的業務能力或者將自己的業務能力提供給對方,本文主要介紹外圍系統的接口調用的介紹和統一調用的設計與實現。
接口調用生命周期
業務調用時,我們通常將接口接口數據按照一定的規范封裝成報文或者參數,然后通過網絡協議將對應的報文發送給對應的外圍接口地址,外圍接受到相關業務請求后,將內部處理結果,再通過約定的報文形式回傳給接口調用方,整個過程如下圖所示:
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
-
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獲得類對象
-
/** * 通過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
-
{ "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)驗證類文件
-
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)調用代碼生成器
-
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>
通過!~
