利用javadoc定制自己的接口文档(三)

前言

上一篇我们对freemarker及其使用方式做了简单的介绍,最后展示了自己是如何将模板的生成从第一代doclet中抽出来。在最后展示的doclet2中我们可以看到有以下缺陷:

  1. 注解名称是直接写在代码中的,如果要添加或者修改注解,就要修改源码
  2. 在doclet中填充数据的代码都是硬编码,可维护性及扩展性太差
  3. freemarker操作的初始化及freemarker模板文件的生成直接写在doclet中,耦合性太强,可读性太差,可维护性和扩展性太差

这一篇将针对上面所列的问题,介绍自己是如何将注解名称从代码中抽出,如何设计model层的数据结构,如何将数据及文件的生成从doclet中抽出来,及针对freemarker模板针对这些数据结构所做的修改。于是有了第三代doclet——doclet3。

这个doclet3是可以用的,之后再这个基础上加上异常提示之后会继续更新。

一、将注解名称从代码中抽出

(一)注解的展示形式

这些注解名称需要用户自己去定义,并按照一定的格式要求写在一个外部文件中,在doclet的入口处即start中读进来并解析。那么,这些注解名称该在一个什么样的形式展示出来呢?自己最开始设想有三种:

  • properties文件
  • json
  • xml

最后选择了以xml的形式来展示,理由如下:

  1. 注解之后的内容结构很多,有直接是字符串的,还有需要拆分的,就像之前@param这样,而拆分符号是什么,拆分后的选项有哪些这些信息只靠properties文件这种键值对的形式是无法展现出来的。所以properties文件排除
  2. json的可读性太差,而注解的种类又不是很多,所以文件也不会很大。所以json排除。

(二)注解的展示内容

确定以xml方式展示后,紧接着面临着下一个问题:xml内的结构和内容怎么设计?

要解决这个问题,我们得先确定doclet需要从这个xml中知道注解的什么信息:

  1. 是方法上的注解还是类上的注解
  2. 注解的内容和格式(如uri这样注解后内容直接是个字符串直接展示即可,或是像param这样需要需要以~来拆分)
  3. 是单个注解(如uri这样一个方法有一个),还是有多个同名注解(如param)
  4. 每个方法的模板的地址及对应输出文件地址,类,包同样需要知道( 如果有的话)

确定这些后,再来思考如何展示这些信息:
1. 我们需要告诉doclet无非是三类信息:包的相关信息,类的相关信息,方法的相关信息。除此之外,我们需要注意的是:doclet是以包为单位读入注解的(即使设置了多个包名,也是一个包一个包的读入注解),而在一个包中能写注解的地方只有在类和方法上。于是,设计doclat标签作为根标签,在doclet下,设计三个标签:package,class,method分别展示包、类、方法的相关信息,在class和method标签下,设计tag标签来展示一个注解的相关信息,tag标签可以有多个,这样我们可以确定一个大致的xml框架如下:

<doclet>
    <package>
    </package>
    <class>
        <tag></tag>
        <tag></tag>
    </class>
    <method>
        <tag></tag>
        <tag></tag>
    </method>
</doclet>

2.在每个tag标签下,设计name标签来告诉doclet有哪些注解,设计type标签来标识注解的格式:string表示是字符串,split表示需要拆分。设计symbo标签来展示拆分的符号,设计item标签来展示拆分后每一项对应的名称(item项要按照顺序写出来,如下面xml展示的param,表示有一个方法注解,名称为param,它是以name~select~type~explain顺序来展示的,如在某个方法上写@param userid~true~string~用户id),值得注意的是:name标签,item标签中的内容将作为map的key存放在map中,再讲其传给freemarker模板,所以name标签和item标签的值要与模板中相应值对应。这样设计的xml如下:

<doclet>

    <package>
    </package>
    <class>
        <tag>
            <name>uri</name>
            <type>string</type>
        </tag>
    </class>
    <method>
        <tag>
            <name>param</name>
            <type>split</type>
            <symbol>~</symbol>
            <item>name</item>
            <item>select</item>
            <item>type</item>
            <item>explain</item>
        </tag>
        <tag>
            <name>uri</name>
            <type>string</type>
        </tag>
    </method>
</doclet>

3.参考html表单提交时,所有的标签都是以list形式提交,相对应的,我把所有的注解都当成多个注解来处理,在代码中对应的为一个list,单个注解就是一个长度为1的list。这个在设计freemarker时一定要注意

4.参考web.xml中servlet的展示,相对应的,在package,class,method标签下,设计viewpath标签来表示模板的信息,在每个viewpath下,设计name标签标识模板的名称(在model设计中其作为map的key存放),value标签标识模板的路径。viewpath标签可以有多个,也可以没有,若有多个,则多个viewpath的name不能相同。

模板的路径有了,那么模板的输出又怎么来获取呢?在package标签中,设计basepath标签来标识这个包下所有文件路径(如果有方法或者类的输出路径,也包括这些路径)的基本路径,所有输出路径都是相对于这个路径而已的(这个标签是必须的),设计outpath标签来标识模板的输出信息,设计name标签与viewpath下的name标签相同,设计value标签来标识相对于basepaht路径的文件路径,在method和class下,由于class和method对应的模板输出路径要在注解中指定,所有输出路径是写在tag标签中的,name标签要和对应的viewpath标签的那么相同,type标签是固定值:string。设计的注解xml如下:

这里写代码片<doclet>

    <package>
        <outpath>
            <name>packagehtml</name>
            <value>2.0.html</value>
        </outpath>
        <viewpath>
            <name>packagehtml</name>
            <value>D:/model/index.ftl</value>
        </viewpath>
        <basepath>d:/api2</basepath>
    </package>

    <class>
        <tag>
            <name>uri</name>
            <type>string</type>
        </tag>
    </class>

    <method>
        <tag>
            <name>param</name>
            <type>split</type>
            <symbol>~</symbol>
            <item>name</item>
            <item>select</item>
            <item>type</item>
            <item>explain</item>
        </tag>
        <tag>
            <name>uri</name>
            <type>string</type>
        </tag>
        <tag>
            <name>html</name>
            <type>string</type>
        </tag>
        <tag>
            <name>java</name>
            <type>string</type>
        </tag>
        <viewpath>
            <name>html</name>
            <value>D:/model/Method.ftl</value>
        </viewpath>
        <viewpath>
            <name>java</name>
            <value>D:/model/javaTemplate.ftl</value>
        </viewpath>
    </method>
</doclet>

如上所示,每个方法有两个模板,其位置分别为D:/model/Method.ftl和D:/model/javaTemplate.ftl,对应的输出文件是在方法上@java和@html这两个注解标识的,包对应的有一个模板,其位置为D:/model/index.ftl,模板的输出文件位置为d:/api2/2.0.html

(三)注解xml的读取方式

在标识整个注解信息的xml设计完成之后,doclet该如何知道这个xml的位置呢?

考虑到这个xml需要在程序最开始的时候读入,并且其位置信息还需要用户输入。于是很自然的想到了doclet的自定义标签(关于如何自定义doclet标签,可以参考博客《java doclet概述》)。这里我自定义了-xmlpath标签来标识这个xml的位置。这个xml的名称为xmltest.xml,位置为d:\,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<doclet>

    <package>
        <outpath>
            <name>packagehtml</name>
            <value>2.0.html</value>
        </outpath>
        <viewpath>
            <name>packagehtml</name>
            <value>D:/model/index.ftl</value>
        </viewpath>
        <basepath>d:/api2</basepath>
    </package>

    <class>
        <tag>
            <name>uri</name>
            <type>string</type>
        </tag>
        <tag>
            <name>description</name>
            <type>string</type>
        </tag>
    </class>

    <method>
        <tag>
            <name>param</name>
            <type>split</type>
            <symbol>~</symbol>
            <item>name</item>
            <item>select</item>
            <item>type</item>
            <item>explain</item>
        </tag>
        <tag>
            <name>returnparam</name>
            <type>split</type>
            <symbol>~</symbol>
            <item>name</item>
            <item>type</item>
            <item>explain</item>
        </tag>
        <tag>
            <name>uri</name>
            <type>string</type>
        </tag>
        <tag>
            <name>description</name>
            <type>string</type>
        </tag>
        <tag>
            <name>type</name>
            <type>string</type>
        </tag>
        <tag>
            <name>returnjson</name>
            <type>string</type>
        </tag>
        <tag>
            <name>path</name>
            <type>string</type>
        </tag>
        <tag>
            <name>methodname</name>
            <type>string</type>
        </tag>
        <tag>
            <name>html</name>
            <type>string</type>
        </tag>

        <tag>
            <name>java</name>
            <type>string</type>
        </tag>
        <viewpath>
            <name>html</name>
            <value>D:/model/Method.ftl</value>
        </viewpath>
        <viewpath>
            <name>java</name>
            <value>D:/model/javaTemplate.ftl</value>
        </viewpath>
    </method>
</doclet>

模板的内容我会在第四节来阐述,需要生成文档的目标java文件为text.java,位置为d:\,内容如下:

package com.jchvip.jch2.appInterface;

import com.bluemobi.common.util.AppResult;
import com.bluemobi.common.util.ValidateUtil;
import com.jchvip.jch2.service.MessageManagerServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;

/**
 * @description 消息类
 * @uri 2.0/message
 */
@Controller
@RequestMapping(value = "2.0/message", method= {RequestMethod.POST,RequestMethod.GET} ,produces="text/html;charset=UTF-8")
public class MessageController {



    /**
     * @uri count/userandmax
     * @type read
     * @path 根据用户id和消息最大id获取各种类型消息数目
     * @html MessageController/userandmax.html
     * @java MessageController/userandmax.java
     * @description 根据用户id和消息最大id获取各种类型消息数目
     * @methodname userandmax
     * @param userid~true~string~用户id
     * @param maxid~true~int~最大id
     * @returnparam data中的key~int~消息类型,other 其他;like 点赞;comment 评论
     * @returnparam data中的value~int~多少条未读消息
     * @returnjson
     *{
    {
    "status": 0,
    "serverTime": "160819101001",
    "data": {
    "other": 21,
    "comment": 1,
    "like": 1
    }
    }
     */
    @RequestMapping("/count/userandmax")
    @ResponseBody
    public String messagecountuserandmax(HttpServletRequest request,HttpServletResponse response,String userid,Integer maxid){
        return null;
    }

    /**
     * @uri userandtypeandmax
     * @type read
     * @path 根据用户id和消息最大id和消息类型获取用户消息
     * @html MessageController/userandtypeandmax.html
     * @java MessageController/userandtypeandmax.java
     * @description 根据用户id和消息最大id和消息类型获取用户消息
     * @methodname userandtypeandmax
     * @param userid~trur~string~用户id
     * @param maxid~true~int~消息最大id
     * @param classes~true~int~前端分类,1 点赞,2 评论,3 其他所有分类
     * @returnparam content~string~消息内容
     * @returnparam createtime~string~消息产生时间
     * @returnparam id~int~消息id
     * @returnparam title~string~消息标题
     * @returnparam status~int~消息状态,2 已读,其他值 未读
     * @returnparam type~int~消息类型,1 实名认证通过,2 加好友,3 同意好友,4 点赞, 5 评论,6 实名不通过,7 签约,8 报名,10 活源
     * @returnparam classes~int~前端分类,1 点赞,2 评论,3 其他所有分类
     * @returnjson
     * {
    "status": 0,
    "serverTime": "160819100844",
    "data": [
    {
    "content": "恭喜您,实名认证通过!",
    "createtime": 1471489002000,
    "id": 1,
    "title": "实名认证通过",
    "status": 0,
    "type": 1,
    "classes":3
    }
    ]
    }
     */
    @RequestMapping("/userandtypeandmax")
    @ResponseBody
    public String messageuserandtypeandmax(HttpServletRequest request,HttpServletResponse response,String userid,Integer maxid,Integer classes){
        return null;
    }

    /**
     * @uri updatemessagestatus
     * @type write
     * @path 标识消息为已读
     * @html MessageController/updatemessagestatus.html
     * @java MessageController/updatemessagestatus.java
     * @methodname updatemessagestatus
     * @description 标识消息为已读
     * @param messageid~true~int~要修改的消息id
     * @returnjson
    {
    "status": 0,
    "serverTime": "160819100844",
    }
     */
    @RequestMapping("/updatemessagestatus")
    @ResponseBody
    public String updatemessagestatus(HttpServletRequest request,HttpServletResponse response,Integer messageid){
        return null;
    }

    /**
     * @uri deletemessagebyid
     * @type write
     * @html MessageController/deletemessagebyid.html
     * @java MessageController/deletemessagebyid.java
     * @methodname deletemessagebyid
     * @description 删除消息
     * @path 删除消息
     * @param messageid~true~int~要删除的消息id
     * @returnjson
    {
    "status": 0,
    "serverTime": "160819100844",
    }
     */
    @RequestMapping("/deletemessagebyid")
    @ResponseBody
    public String deletemessagebyid(HttpServletRequest request,HttpServletResponse response,Integer messageid){
        return null;
    }

}

于是,整个doclet命令为:

javadoc -doclet doclet3 -docletpath D:\doclet\out\doclet.jar -encoding utf-8 -charset utf-8 -xmlpath d:\xmltest.xml d:\text.java

在dos命令行直接输入上述命令就会在指定位置生成文件。

二、model层数据结构设计及实现

不难发现,整个model层对应结构大致为一个树型结构:一个包包含多个类,一个类包含多个方法和多个注解,一个方法包含多个注解。我先展示整个model的uml图,让大家有个整体的认识,然后再依次来分析。整个model层uml图如下:
这里写图片描述

(一)Tag类

设计tag来标识注解的种类(注意这里抽象的是注解的种类,而不是个数),其结构如下:
这里写图片描述

  • name标识注解的名称
  • type标识注解的格式,1表示string,2表示split。
  • items标识分割后每项依次对应的名称。
  • symbol标识分割符号
  • itemvalues标识注解后的字符串
  • Tag中transformTagvalue方法的作用处理注解后的字符串,生成模板所需要的数据结构和内容做一些前期处理。若注解格式是string,则直接返回itemvalues字段,若是split,则拆分itemvalues中的每一项,将其值作为map的value,items中的值作为map的key,再讲这个这些map返回。这个方法的代码如下:
public List transformTagvalue(){
        if (type == 1){
            //string
            return itemvalues;
        }else if (type == 2){
            //split
            List<Map> tagvalues = new ArrayList<Map>();
            for (String itemvalue : itemvalues){
                Map<String,String> itemmap = new HashMap<String,String>();
                String[] values = itemvalue.split(symbol);
                for (int i =0; i<values.length ; i++){
                    itemmap.put(items.get(i),values[i]);
                }
                tagvalues.add(itemmap);
            }
            return tagvalues;
        }else {
            return null;
        }
    }

(二)MethodType类

MethodType类需要实现Serializable接口,这是为了后面的深拷贝。
这里写图片描述

  • tagList标识每个方法上不同种类的注解,在上一节中的所说的xml中的method标签下有多少个tag标签,这个list的size就是多少。(如上面最后一个xml的method有4个tag,这里list的大小就是4)
  • template标识每个方法所对应的模板地址(map的key为method标签下viewpath标签的name标签的值,value为viewpath的value标签的值)
  • outpath标识每个模板生成的文件的输出地址(map的key为method标签下viewpath标签的name标签值,value是在doclet读取注解内容时写进去。上面说过在method和class标签下,viewpath标签有多少个,就要有多少个name相同的tag标签)
  • MethodType中transformTagvalue方法的作用处理方法中每种注解,返回method的freemarker模板所需要的数据内容和结构,其中tag的name为map的key,tag的transformTagvalue出参为map的value。这个方法的代码如下:
public Map<String,List> transformTagvalue(){
        Map<String,List> map = new HashMap<>();
        for (Tag tag : tagList){
            map.put(tag.getName(),tag.transformTagvalue());
        }
        return map;
    }

(三)ClassType类

ClassType类需要实现Serializable接口,这是为了后面的深拷贝。
这里写图片描述

  • tagList标识每个类上不同种类的注解,在上一节中的所说的xml中的class标签下有多少个tag标签,这个list的size就是多少。(如上面最后一个xml的class有1个tag,这里list的大小就是1)
  • template标识每个类所对应的模板地址(map的key为class标签下viewpath标签的name标签的值,value为viewpath的value标签的值,在上面最后所列的xml中,class标签下没有viewpath标签,则这个这段和outpath字段均为null)
  • outpath标识每个模板生成的文件的输出地址(map的key为class标签下viewpath标签的name标签值,value是在doclet读取注解内容时写进去。上面说过在method和class标签下,viewpath标签有多少个,就要有多少个name相同的tag标签)
  • methodTypeList标识每个类中的方法,每个类有多少个方法,则这个list的size就为多少
  • ClassType中transformTagvalue方法的作用处理类中每种注解和方法,返回类的freemarker模板所需要的数据内容和结构,它处理注解的方式和MethodType相同,它处理方法的方式是将每个方法的transformTagvalue出参添加到一个list中,再将这个list作为value放入一个map,这个map的key固定为methods,所以在freemarker模板(ftl文件)中,是通过${每个类的名称.methods}来获取类中所有方法的数据。这个方法的代码如下:
public Map<String,List> transformTagvalue(){
        Map<String,List> map = new HashMap<>();
        //转换class注解上的值
        for (Tag tag : tagList){
            map.put(tag.getName(),tag.transformTagvalue());
        }
        //转换class中method中的值
        List methodvalues = new ArrayList();
        for (MethodType methodType : methodTypeList){
            methodvalues.add(methodType.transformTagvalue());
        }
        map.put("methods",methodvalues);
        return map;
    }

(四)PackageType类

这里写图片描述

  • classTypes标识包中的方法,包中有多少个方法,则这个list的size就为多少
  • template标识每个类所对应的模板地址(map的key为package标签下viewpath标签的name标签的值,value为viewpath的value标签的值)
  • outpath标识每个模板生成的文件的输出地址(map的key为package标签下outpath标签下name标签值,value为value标签的值)
  • PackageType中transformTagvalue方法的作用处理包中每个类,返回包的freemarker模板所需要的数据内容和结构,它处理类的方式和ClassType的相同,只是map的key固定为classes,所以在freemarker模板(ftl文件)中,是通过${classes}来获取包中所有类的数据。这个方法的代码如下:
public Map<String,List> transformTagvalue(){
        Map<String,List> map = new HashMap<>();

        List classvalues = new ArrayList();
        for (ClassType classType : classTypes){
            classvalues.add(classType.transformTagvalue());
        }
        map.put("classes",classvalues);
        return map;
    }

三、doclet生成model层数据结构及输出对应文件

在整个model层上,我又添加了三个类:HandleXml,TypeFactory,HandleView。主要是用来处理xml信息,model层下各个类的工厂,输出模板对应的文件。他们与model层的类的关系如下uml图所示:
这里写图片描述

(一)HandleXml类

这里写图片描述

这个类的作用是用来处理读入的xml信息,然后将对应的信息配置在TypeFactory中,使TypeFactory生成需要的类。

这个类中最重要的方法就是handlexml,在start方法(整个程序的入口)一开始便调用这个handlexml方法,入参是注解xml的地址。其代码如下:

public static void handlexml(String xmlAddress) throws DocumentException {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(new File(xmlAddress));
        Element root = document.getRootElement();

        //处理包的标签
        Element packages = root.element("package");
        //设置包模板路径
        List<Element> packageviewpathes = packages.elements("viewpath");
        TypeFactory.getPACKAGETYPE().setTemplate(handlePackagePath(packageviewpathes));

        List<Element> packageOutpathes = packages.elements("outpath");
        TypeFactory.getPACKAGETYPE().setOutpath(handlePackagePath(packageOutpathes));

        Element packageBasepath = packages.element("basepath");
        if (packageBasepath.getText().endsWith("/")){
            TypeFactory.getPACKAGETYPE().setBasepath(packageBasepath.getText());
        }else {
            TypeFactory.getPACKAGETYPE().setBasepath(packageBasepath.getText()+"/");
        }

        //处理类的标签
        Element classtags = root.element("class");
        //设置类模板路径
        List<Element> classPathes = classtags.elements("viewpath");
        List<Map<String,String>> classpathList = handleMethodOrClassPath(classPathes);
        TypeFactory.getCLASSTYPE().setTemplate(classpathList.get(0));
        TypeFactory.getCLASSTYPE().setOutpath(classpathList.get(1));
        //处理类模板标签
        List<Element> classtaglist = classtags.elements("tag");
        TypeFactory.getCLASSTYPE().setTagList(handleTags(classtaglist));

        //处理方法的标签
        Element methodtags = root.element("method");
        //设置方法模板路径
        List<Element> methodPathes = methodtags.elements("viewpath");
        List<Map<String,String>> methodpathList = handleMethodOrClassPath(methodPathes);
        TypeFactory.getMETHODTYPE().setTemplate(methodpathList.get(0));
        TypeFactory.getMETHODTYPE().setOutpath(methodpathList.get(1));

        //处理方法模板标签
        List<Element> methodtaglist = methodtags.elements("tag");
        TypeFactory.getMETHODTYPE().setTagList(handleTags(methodtaglist));

    }

上述代码很简单,就是在读入xml后,分别读取包,类,方法的对应路径,然后将这个xml的信息配置在TypeFactory的三个静态属性上(TypeFactory下面会详细介绍)。

至于handlePackagePath,handleMethodOrClassPath,handleTags这三个私有方法,主要是将xml中的信息转换成TypeFactory中静态属性需要的格式。大家可以在最下面下载源码看看。

(二)TypeFactory类

这里写图片描述

这个类是ClassType,MethodType,PackageType的工厂,doclet3中ClassType,MethodType,PackageType的获取都是通过这个工厂对应的三个get方法。

deepCopy这个方法,是用于对象的深度复制(此处我用的是序列化的方式:将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝)。

那么,为什么要用深度复制这样耗性能的方式给doclet3对象呢?要弄清这个问题,我们需要来看一下经过HandleXml的init方法之后,TypeFactory的三个静态属性会分别携带哪些信息:

  • PackageType类型的PACKAGETYPE变量:静态变量basepath会确定;静态变量template会确定;静态变量outpath会确定;变量classTypes待定。这样,PackageType类型的静态变量都确定,动态静态变量都不定,所以考虑到性能TypeFactory的getPackageType方法就直接new一个PackageType对象作为出参

  • MethodType类型的METHODTYPE变量:静态变量template会确定;变量outpath的大小和key值会确定,value需要从注解中读入;变量tagList的大小和内容会确定,只是它的每一项Tag类中的itemvalues需要从注解中读入。这样静态变量确定,动态变量的结构和内容大多都确定,只剩下在doclet3中读入注解内容将其填入即可,所以需要以METHODTYPE为原型,用深度复制的方式创建对象。(如果不用复制对象的方式,而是每次在doclet3中创建一个对象,然后读一遍xml将方法注解名称信息填入,这样一来更耗性能,二来耦合性太强,极易出错)

  • ClassType类型的CLASSTYPE变量:它的情况和METHODTYPE类似,不过methodTypeList值为null,需要在doclet3中填入。也是采用深度复制的方式创建对象。

(三)HandleView类

这里写图片描述

这个类的init方法主要是依照包,类,方法中的模板路径读入模板,生成Template对象,保存在属性template中,这个map属性的key就是注解xml中viewpath标签下的name值,这也就是为什么所有的viewpath标签不能相同。程序中是在调用HandleXml的handlexml方法之后紧接着调用这个方法

这个类的createfile方法主要是在指定位置生成对应文件。入参data指定模板中填充的数据,入参outpath的key是viewpath标签的name,value是输出路径(是相对于basepath的路径),这样通过这个key就将模板和输出路径对应起来,之后填入数据输出即可。

(四)doclet3

doclet3的start方法代码如下:

public static boolean start(RootDoc root) {
        try {
            HandleXml.handlexml(xmlpath);
            HandleView.init();
        } catch (Exception e) {
            e.printStackTrace();
        }

        PackageType packageType = TypeFactory.getPackageType();
        try {
            doc(root.classes(), packageType);
            //为包生成对应的文档
            try {
                HandleView.createfile(packageType.transformTagvalue(),PackageType.getOutpath());
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

经过上面的介绍,这个代码应该不难理解。我们重点来介绍doc(root.classes(), packageType);这个方法,看它干了什么,其代码如下:

private static void doc(ClassDoc[] classDocs,PackageType packageType) throws Exception {

        List<ClassType> classTypeList = new ArrayList<ClassType>();
        packageType.setClassTypes(classTypeList);
        //处理各个类
        for (int i = 0; i < classDocs.length; i++) {

            ClassType classType = TypeFactory.getClassType();
            classTypeList.add(classType);

            //处理类注解
            ClassDoc classDoc = classDocs[i];
            List<tag.Tag> classtags = classType.getTagList();
            for (tag.Tag classtag : classtags){
                classtag.setItemvalues(docClassList(classDoc,classtag.getName()));
            }
            for (Map.Entry<String,String> outpath : classType.getOutpath().entrySet()){
                outpath.setValue(docClassList(classDoc,outpath.getKey()).get(0));
            }
            //处理类中各个方法的注解
            List<MethodType> methodTypeList = new ArrayList<MethodType>();
            classType.setMethodTypeList(methodTypeList);
            MethodDoc[] methodDocs = classDoc.methods();
            for (int j = 0 ; j < methodDocs.length ; j ++){
                MethodType methodType = TypeFactory.getMethodType();
                methodTypeList.add(methodType);

                //处理方法注解
                MethodDoc methodDoc = methodDocs[j];
                List<tag.Tag> methodtags = methodType.getTagList();
                for (tag.Tag methodtag : methodtags){
                    methodtag.setItemvalues(docMethodList(methodDoc,methodtag.getName()));
                }
                //设置每个方法的输出路径
                for (Map.Entry<String,String> outpath : methodType.getOutpath().entrySet()){
                    outpath.setValue(docMethodList(methodDoc,outpath.getKey()).get(0));
                }
                //为每个方法生成对应的文件
                HandleView.createfile(methodType.transformTagvalue(),methodType.getOutpath());
            }
            //设置每个类的输出路径
            for (Map.Entry<String,String> outpath : classType.getOutpath().entrySet()){
                outpath.setValue(docClassList(classDoc, outpath.getKey()).get(0));
            }
            //为每个类生成对应的文件
            HandleView.createfile(classType.transformTagvalue(),classType.getOutpath());
        }

    }

看了上面的注解,相信大家也有对其有个大概的了解,这个方法就做两件事:

  1. 处理包中各个的类,同时输出类对应的文件;处理类的各个方法,同时输出方法对应文件。
  2. 将这些方法添加到对应类的methodTypeList中,将这些类添加到这个包的classTypes中。

四、freemarker模板的重新设计

由于将所有的注解都当成多个注解来处理,这样传入freemarker模板中的数据便都变为list,所以需要修改原来模板中${uri}这样的取单个注解的取值方式。(像之前param这样本来就是多个注解的不变)。

下面是每个方法对应的html模板。(注意uri,returnjson,description的取值方式变)

<!DOCTYPE html>
<html>
    <meta charset=utf8>
<head>
    <title></title>
</head>
    <style type="text/css">
body{
    font: 12px/1.125 Arial, Helvetica, sans-serif;
}

    .wiki_title{
        line-height: 37px;
border-bottom: 1px solid #e5e5e5;
margin: 16px 0 8px 0;
font-size: 20px;
color: #333;
font-family: "Microsoft Yahei";
font-weight: 300;
    }

    h1.wiki_title{
        font-size: 24px;
    }

    a{
        color: #3c7cb3;
        text-decoration: none;
    }

    table{
        border-collapse: collapse;
border-spacing: 0;
    }

    table.parameters{
        border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
-webkit-border-horizontal-spacing: 0px;
-webkit-border-vertical-spacing: 0px;
width: 100%;
    }

    th,td{
        text-align: center;
font-weight: bolder;
border: 1px solid #cccccc;
height: 20px;
    }

    .code_type{
        text-transform: uppercase;
margin-bottom: 5px;
display: inline-block;*
display: inline;*
zoom: 1;
background: #b4e3b4;
border-radius: 2px;
color: #008200;
padding: 2px 8px;
    }
    a:hover{
          text-decoration: underline;
        }
</style>
<body>
    <h1 class="wiki_title">
        <span class="mw-headline"><#list uri as a>${a}</#list></span>
    </h1>
    <p><#list description as a>${a}</#list></p>
    <h2 class="wiki_title">
        <span class="mw-headline">URL</span>
    </h2>
    <p>
        <span style="font-weight:600">
            <a rel="nofollow" class="external free" href="">http://test.jchvip.net/<#list uri as a>${a}</#list></a>
        </span>
    </p>
    <h2 class="wiki_title">
        <span class="mw-headline" >支持格式</span>
    </h2>
    <p>
        <span style="text-transform:uppercase;font-weight:600">JSON</span>
    </p>

    <h2 class="wiki_title">
        <span class="mw-headline" >HTTP请求方式</span>
    </h2>
    <p>
        <span style="text-transform:uppercase;font-weight:600">POST</span>
    </p>

    <h2 class="wiki_title">
        <span class="mw-headline" >请求参数</span>
    </h2>

    <table border="1" cellspacing="0" cellpadding="0" width="100%" class="parameters" style="border-color: #CCCCCC;">

        <tbody>
            <tr>
                <th width="10%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">名称</th>
                <th width="5%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">必选</th>
                <th width="10%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">类型及范围</th>
                <th width="75%" style="text-align:center;font-weight:bolder;border:1px solid #cccccc">说明</th>
            </tr>
            <#list param as item>
            <tr>
                <td style="text-align:center;font-weight:bolder;border:1px solid #cccccc">${item.name}</td>
                <td style="text-align:center;border:1px solid #cccccc">${item.select}</td>
                <td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.type}</td>
                <td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.explain}</td>
            </tr>
            </#list>

        </tbody>
    </table>

    <h2 class="wiki_title">
        <span class="mw-headline">返回结果</span>
    </h2>

    <div class="code_type" style="text-transform:uppercase;margin-bottom:5px;">JSON示例</div>
    <pre>
    <#list returnjson as a>${a}</#list>
    </pre>

    <h2 class="wiki_title">
        <span class="mw-headline">返回字段说明</span>
    </h2>

    <table border="1" cellspacing="0" cellpadding="0" width="100%" class="parameters" style="border-color: #CCCCCC;">

        <tbody>
            <tr>
                <th width="25%" style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">返回值字段</th>
                <th width="15%" style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">字段类型</th>
                <th width="60%" style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">字段说明</th>
            </tr>

            <#list returnparam as item>
            <tr>
                <td style="text-align:left;padding-left:5px;font-weight:bolder;border:1px solid #cccccc">${item.name}</td>
                <td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.type}</td>
                <td style="text-align:left;padding-left:5px;border:1px solid #cccccc">${item.explain}</td>
            </tr>
            </#list>
        </tbody>
    </table>

    <h2 class="wiki_title">
        <span class="mw-headline" >注意事项</span>
    </h2>

    <p></p>
</body>
</html>

下面是每个方法对应的java模板(注意methodname,uri取值方式的变化):

package com.bluemobi.bluecollar.network.request;
import java.util.HashMap;
import java.util.Map;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.bluemobi.bluecollar.network.LlptHttpJsonRequest;
import com.bluemobi.bluecollar.network.response.getTagListResponse;

public class <#list methodname as a>${a}</#list>Request extends LlptHttpJsonRequest<<#list methodname as a>${a}</#list>Response> {
    private static final String APIPATH = "<#list uri as a>${a}</#list>";

    <#list param as a>

        private String ${a.name};
        public String get${a.name?cap_first}() {return ${a.name};}
        public void set${a.name?cap_first}(String ${a.name}) {this.${a.name} = ${a.name};}

    </#list>

    public <#list methodname as a>${a}</#list>Request(Listener<<#list methodname as a>${a}</#list>Response> listener, ErrorListener errorListener) {
            super(Method.POST, APIPATH, listener, errorListener);
        }
    public <#list methodname as a>${a}</#list>Request(int method, String partUrl, Listener<<#list methodname as a>${a}</#list>Response> listener, ErrorListener errorListener) {
            super(method, partUrl, listener, errorListener);
        }
    public Class<<#list methodname as a>${a}</#list>Response> getResponseClass() {return <#list methodname as a>${a}</#list>Response.class;}

    public String GetApiPath() {return APIPATH;}

    public Map<String, String> GetParameters() {
            Map<String, String> map = new HashMap<String, String>();
        <#list param as a>
            map.put("${a.name}",${a.name});
        </#list>
            return map;
        }
}

下面是包对应的html模板(是可以在里面写js代码的):

<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<html>
<title></title>
<style type="text/css">
    body{font: 12px/1.125 Arial, Helvetica, sans-serif;}
    .custom{
        border-collapse:collapse;
        border:gray;
        width: 100%;
        display: table;
        text-align: left;
        margin-bottom: 20px;
    }
    .custom .tbF1{width: 116px;}
    .custom .tbF2{width: 218px;}
    .custom th{
        padding: 12px 0;
        background: #e9e9e9;
        border: 1px solid #e8eaec;
        padding-left: 10px;
        font-weight: 700;
        font-size: 14px;
        text-align: left;
        color: #000;
    }
    .custom td{
        background: #fff;
        padding-left: 10px;
        padding: 7px 10px;
        line-height: 20px;
        border: 1px solid #e8eaec;
    }
    .custom pre{overflow: hidden;}
    a{
        text-decoration: none;
        color: #3c7cb3;
    }
    a:hover{text-decoration: underline;}
</style>

<#list classes as classitem>
<div>
    <table class = "custom">
        <colgroup><col class = "tbF1"><col class = "tbF2"><col></colgroup>
        <tr>
            <th colspan="3"><#list classitem.description as a>${a}</#list></th>
        </tr>
        <#assign classuri><#list classitem.uri as a>${a}</#list></#assign>
        <#assign readnum = 0/>
        <#assign writenum = 0/>
        <#list classitem.methods as method>
            <#assign type><#list method.type as a>${a}</#list></#assign>
            <#if type == "read">
                <#assign readnum = readnum+1>
                <#if readnum == 1>
                    <tr>
                        <td id="read${classitem_index}">读取接口</td>
                        <td><a href="<#list method.html as a>${a}</#list>"><#list method.uri as a>${classuri+"/"+a}</#list></a></td>
                        <td><#list method.path as a>${a}</#list></td>
                    </tr>
                <#else>
                    <tr>
                        <td><a href="<#list method.html as a>${a}</#list>"><#list method.uri as a>${classuri+"/"+a}</#list></a></td>
                        <td><#list method.path as a>${a}</#list></td>
                    </tr>
                </#if>
            </#if>
        </#list>
        <#list classitem.methods as method>
            <#assign type><#list method.type as a>${a}</#list></#assign>
            <#if type == "write">
                <#assign writenum = writenum+1>
                <#if writenum == 1>
                    <tr>
                        <td id="write${classitem_index}">写入接口</td>
                        <td><a href="<#list method.html as a>${a}</#list>"><#list method.uri as a>${classuri+"/"+a}</#list></a></td>
                        <td><#list method.path as a>${a}</#list></td>
                    </tr>
                <#else>
                    <tr>
                        <td><a href="<#list method.html as a>${a}</#list>"><#list method.uri as a>${classuri+"/"+a}</#list></a></td>
                        <td><#list method.path as a>${a}</#list></td>
                    </tr>
                </#if>
            </#if>
        </#list>
        <input id="readnum${classitem_index}" hidden="hidden" value="${readnum}">
        <input id="writenum${classitem_index}" hidden="hidden" value="${writenum}">
    </table>
</div>
</#list>
<script>
    for(var i=0 ; i < ${classes?size} ; i++ )
    {
        var read = document.getElementById("read"+i);
        if(read){
            var readnum = document.getElementById("readnum"+i);
            read.setAttribute("rowspan",readnum.value);
        }
        var write = document.getElementById("write"+i);
        if(write){
            var writenum = document.getElementById("writenum"+i);
            write.setAttribute("rowspan",writenum.value);
        }

    }
    var alist = document.getElementsByTagName("a");
    var date = new Date();
    for(var j=0 ; j < alist.length ; j++ ){
        alist[j].setAttribute("href",alist[j].getAttribute("href")+ "?" + date.getTime());
    }
</script>
</html>

代码下载:https://github.com/zhaoshiqiang/doclet

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值