记一次WebService数据接口开发

概述

也许是人员紧张,一个跨部门调用的新接口开发任务就交给我了。然后在只了解简单业务和接口数据属性名的情况下,开始了第一次的数据接口开发。我甚至没有一个工程模版。

准备

项目组前段时间开始使用maven进行项目构建和依赖管理了,我顺势就自己create一个,把公司私服的封装工具引用一下。要保存数据,添个数据库相关的依赖;用webservice接口,添上axis2的依赖;数据是xml的,dom4j已经添加进去了,日志系统slf4j也有了。创建一个入口类,copy一个META-INF/services.xml文件放上去再改改,大约能跑起来了。

构建

  1. xml格式解析
    输入数据是xml格式的,一种简单而“古老”的格式。看着那一半像技术一半像运维写的接口文档,就觉得没这么简单。为了以后好改,决定把解析功能剥离出来。毕竟改个三四次的觉悟还是有的,事实也是如此。
    设想格式为

    <root>
    <id>1</id>
    <name>xiaoming</name>
    <age>18</age>
    <address>18</address>
    </root>

    在简单百度之后发现java标准库里有个注解@XmlElement不错,拿来用用。

    //Person类
    public class Person {
    
      @XmlElement(name = "id")
      private String id;
      @XmlElement(name = "age")
      private String age;
      @XmlElement(name = "address")
      private String address;
        ...
    }
    //XMLHelper类,用于解析接收到的xml格式数据并给javabean赋值
    public final class XMLHelper {
    private final static Logger logger = LoggerFactory.getLogger(XMLHelper.class);
    private final static String DEFAULT_NAME = "##default";// @XmlElement注解属性的默认值
    public static <T> T xmlInitBean(String xmlStr, T obj) {
        Document dom = getDocument(xmlStr);
        Element root = dom.getRootElement();
    
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(XmlElement.class)) {
                XmlElement xe = field.getAnnotation(XmlElement.class);
                // 未指定name属性值的,使用字面量
                String xmlNodeName = DEFAULT_NAME.equals(xe.name()) ? field.getName() : xe.name();
                Element element = root.element(xmlNodeName);
    
                // 指定赋值字段未赋值打印警告并跳过
                if(element == null) {
                    logger.warn("{}未成功赋值", field.getName());
                    continue;
                }
    
                String text = element.getTextTrim();
                // 忽略访问权限
                field.setAccessible(true);
                try {
                    field.set(obj, text);
                } catch (Exception e) {
                    logger.error("反射赋值{}={}发生异常!", field.getName(), text, e);
                }
            }
        }
        return obj;
      }
        /**
         * 将输入xml数据输出成Document
         */
        private static Document getDocument(String xmlStr) {
            Document dom = null;
            try {
                dom = DocumentHelper.parseText(xmlStr);
            } catch (DocumentException e) {
                logger.error("处理输入数据发生异常:",  e);
                throw new RuntimeException(e);
            }
            return dom;
        }
    }

    通过注解信息辅助标记,剥离字段名约束,反射动态赋值,剥离赋值对象类型,可以满足相同xml数据结构情况下,只使用一个方法满足不同类型数据字段。
    创建一个类,用于 确认信息的返回,不同接口返回时有些微差异,主要部分基本相同,但仍可通过一个类进行返回xml格式数据的构建

public class ReturnMessage {
  private String id;
  private Integer rtnCode;
  private Optional<String> rtnMsg;
  private String assignOpr;

  public ReturnMessage(String id, Integer rtnCode, String rtnMsg) {
    this.id = id;
    this.rtnCode = rtnCode;
    this.rtnMsg = Optional.ofNullable(rtnMsg);
  }

  /**
   * 返回一个表示成功的返回信息对象
   * 
   * @param id
   * @return
   */
  public static ReturnMessage buildSuccessMsg(String id) {
    return new ReturnMessage(id, 1, "");
  }

  /**
   * 返回构建一个表示失败的信息对象
   * 
   * @param id
   * @param msg
   * @return
   */
  public static ReturnMessage buildErrorMsg(String id, String msg) {
    return new ReturnMessage(id, 0, msg);
  }

  public ReturnMessage withAssignOpr(String assignOpr) {
    this.assignOpr = assignOpr;
    return this;
  }
}

测试

如预计的一样的,实际数据格式是超出预期的,大约如下

<root code='1'>
<CL P = 'id'>1</CL>
<CL P = 'name'>xiaoming</CL>
<CL P = 'age'>18</CL>
<CL P = 'address'>shanghai</CL>
</root>
这只是举例数据,实际数据复杂度更高,但不宜公开。还存在个别字段实际使用属性名与文档不一致(由于功能实现问题做出的变更,未及时反映到文档并详细通知相关人员),时间格式不明确。
于是修改XmlHelper类,将属性名和值的映射构建成Key-Value形式,在获取属性值赋值时改为直接从构建好的map中获取:
    /**
     * 将各节点属性名到值信息进行压缩,直接得到k-v键值对
     * @param root
     * @return
     */
    private static Map<String, String> getXmlMap(Element root){
        Map<String, String> propMap = new HashMap<>();
        @SuppressWarnings("unchecked")
        List<Element> elements = root.elements("CL");
        for (Element element : elements) {
            Attribute attribute = element.attribute("P");
            propMap.put(StringUtils.trim(attribute.getValue().trim()), element.getText());
        }
        return propMap;
    }


    // 取值部分修改
    Map<String, String> propMap = getXmlMap(root);
    String text = propMap.get(xmlNodeName);
    // 同时可以增加空值校验,利用@XmlElement(name = "id", nillable = false)中nillable属性进行控制字段空值校验
    // 指定赋值字段未赋值打印警告并跳过
                if(StringUtils.isEmpty(text)) {
                    logger.warn("{}未成功赋值:"+xe.nillable(), field.getName());
                    if(!xe.nillable()){
                        throw new RuntimeException("【item/"+xmlNodeName+"】不可为空!");
                    }
                    continue;
                }

总结

  1. 接口类设计文档,事前没有明确数据格式文档真的是比较坑的事情,时间格式,xml结构,数据类型不确定性太高,提前想到的情况下,也不能保证万无一失,而且联调测试人员还是其他公司的,他们开发还在外地远程改完部署再测。还好格式是统一的吧,只用改工具类就完事了。
  2. 通过反射和注解将数据绑定剥离出来,在控制方面灵活度提高了,增加空值校验的开发代价减少。当然运行时的反射操作其实是不推荐的,所以我赋值时入参用了实例对象,而不是输入class再生成实例,能少用就少用吧。

PS.
数据是假的,看看就好;代码是处理过的,毕竟工作时间写的代码归公司的。仅纪念一次不算太顺利的接口开发工作。
@XmlElement的标记信息还是有可以挖掘的地方的,比如可以标记类型用于格式转换,设置默认值。
希望代码和文档能优雅些。

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值