在我们的日常业务系统开发过程中,随着业务的发展,我们经常需要与外围系统进行接口对接,用以获得对方的业务能力或者将自己的业务能力提供给对方,本文主要介绍外围系统的接口调用的介绍和统一调用的设计与实现。
接口调用生命周期
业务调用时,我们通常将接口接口数据按照一定的规范封装成报文或者参数,然后通过网络协议将对应的报文发送给对应的外围接口地址,外围接受到相关业务请求后,将内部处理结果,再通过约定的报文形式回传给接口调用方,整个过程如下图所示:
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>
通过!~