java生成api接口文档

原创
2018/08/23 14:00
阅读数 1.3K

API接口文档,对于Java开发团队来说,是必不可少的一项工作,不管是JSP展示,前后端分离,还是前后端的代码在一个项目中,都是写展示页面必须参考的文档之一。生成接口文档一般有两种方法:

1、手写,费时费力;

2、用第三方插件如Swagger2或自定义注解,但对代码本身入侵严重,所有的接口声明部分都要重新按照它的规范重写。

所以,我打算只通过一个Java函数,在不破坏原有接口代码的基础上,自动生成API文档。直接上代码:

 

package com.tiger.testApp.controller;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author wh
 * @date 2018年8月22日 下午2:17:28 
*  @Description: 生成API文档
 */
@RestController
@EnableAutoConfiguration
@RequestMapping(value="/api",name="测试生成接口文档")
public class APIDocController {
	
	 	List<String> classPaths = new ArrayList<String>();
	    /**
	     * 获取表名和注释以及表的字段信息
	     */
	    @RequestMapping(value="/list",method=RequestMethod.GET,name="获取表名和注释以及表的字段信息")
	    public Map<String,Object> list(@RequestParam Map<String,Object> param, Integer pageSize) {
	        try {
	        	//接口文件包的根路径
	            String basePack = "com.tiger.testApp.controller";
	            String classpath = APIDocController.class.getResource("/").getPath();
	            basePack = basePack.replace(".", File.separator);
	            String searchPath = classpath + basePack;
	            List<String> allClasses = new ArrayList<String>();
	            doPath(new File(searchPath));
	            for (String s : classPaths) {
	                s = s.replace(classpath.replace("/","\\").replaceFirst("\\\\",""),"")
                     .replace("\\",".").replace(".class","");
	                allClasses.add(s);
	            }
	            Map<String,Object> data = new HashMap<String,Object>();
	            data.put("apiList", getApiInfo(allClasses));
	            return data;
	        } catch (Exception e) {
	            e.printStackTrace();
	            return null;
	        }  
	    }
	    
	    /**
	     * 获取指定路径下的所有类名
	     * @param file
	     */
	    private void doPath(File file) {
	        if (file.isDirectory()) {
	            File[] files = file.listFiles();
	            for (File f1 : files) {
	                doPath(f1);
	            }
	        } else {
	            if (file.getName().endsWith(".class")) {
	                classPaths.add(file.getPath());
	            }
	        }
	    }
	    
	    /**获取类的注释、路径和所有方法的注释、路径、输入、输出信息
	     * @param clsList
	     * @return
	     * @throws ClassNotFoundException
	     */
		@SuppressWarnings("unchecked")
	    public  List<Map<String,Object>> getApiInfo(List<String> clsList) throws 
             ClassNotFoundException{
	    	List<Map<String,Object>> clsInfo = new ArrayList<Map<String,Object>>();
	    	DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
			if (clsList != null && clsList.size() > 0) {
				for (String clsName : clsList) {
					Class cls =  Class.forName(clsName);
					Map<String,Object> clsMap = new HashMap<String,Object>();
					RequestMapping clsMapp = (RequestMapping) 
                        cls.getAnnotation(RequestMapping.class);
					if (clsMapp == null) {
						continue;
					}else {
						clsMap.put("clsUrl", clsMapp.value());
						clsMap.put("clsDesc", clsMapp.name());
					}
					clsMap.put("clsName", clsName);
					List<Map<String,Object>> methodList = new ArrayList<Map<String,Object>>();
					Method[] methods = cls.getDeclaredMethods();
					if (methods != null && methods.length > 0) {
						for (Method method : methods) {
							Map<String,Object> methodMap = new HashMap<String,Object>();
							RequestMapping requestMapp = (RequestMapping) 
                                     method.getAnnotation(RequestMapping.class);
							if (requestMapp == null) {
								continue;
							}else {
								methodMap.put("requestUrl", requestMapp.value());
								methodMap.put("requestType", requestMapp.method());
								String[] parameterNames = discover.getParameterNames(method);
								List<Map<String,Object>> paramList = new 
                                    ArrayList<Map<String,Object>>();
								Parameter[] params = method.getParameters();
								int i=0;
								for(Parameter param : params) {
									Map<String,Object> paramMap = new HashMap<String,Object>();
									/******
									 * 版本JDK1.8
									 * store information about method parameters已勾选
									 * maven编译插件已经添加 -parameters参数
									 * param.getName()返回的名称依然是arg0,arg1...
										paramMap.put("paramName", param.getName());
									 */
									paramMap.put("paramName", parameterNames[i]);
									paramMap.put("paramType",getType(param.getType().getName()));
									paramList.add(paramMap);
									i++;
								}
								methodMap.put("methodParams", paramList);
								methodMap.put("returnType", 
                                   getType(method.getReturnType().getName()));
								methodMap.put("methodDesc", requestMapp.name());
							}
							methodList.add(methodMap);
						}
					}
					clsMap.put("methodList", methodList);
					clsInfo.add(clsMap);
				}
			}
			return clsInfo;
		}
		
		/**处理函数返回类型
		 * @param className
		 * @return
		 * @throws ClassNotFoundException
		 */
		private Object getType(String className) throws ClassNotFoundException {
			if(isBasicClass(className)) {
				return getShortClassName(className);
			}
			try {
				return getClassField(Class.forName(className));
			}catch(Exception e) {
				return null;
			}
		}
		
		
		/**获取类名的最后一部分
		 * @param className
		 * @return
		 */
		private String getShortClassName(String className) {
			if(className == null 
                || "".equals(className) 
                ||className.indexOf(".")<0) {
				return className;
			}
			return className.substring(className.lastIndexOf(".")+1);
		}
		
		/**判断是否是基本类
		 * @param c
		 * @return
		 */
		private boolean isBasicClass(String className) {
			String[] arr =  {"Byte","Short","Integer","Long","Float","Double",
                             "Character","Boolean",
					         "byte","short","int","long",
                             "float","double","char",
                             "boolean","String","Map"};
			List<String> packList = Arrays.asList(arr);
			try {
				if(packList.contains(className)){
					return true;
				}else {
					Class c = Class.forName(className);
					if(c == null) {
						return true;
					}
					if(c.isPrimitive()) {
						return true;
					}
					if(className.indexOf(".")>-1) {
						className = className.substring(className.lastIndexOf(".")+1);
					}
					if(packList.contains(className)){
						return true;
					}else {
						return false;
					}
				}
			}catch(Exception e) {
				e.printStackTrace();
				return true;
			}
		}

		/**获取类的属性信息
		 * @return
		 */
		private Map<String,Object> getClassField(Class c){
			Map<String,Object> result = new HashMap<String,Object>();
			try {
				if(c == null) {
					return null;
				}
				Field[] fs = c.getDeclaredFields();
				for(Field f : fs) {
					/***
					 * 此处仅简单返回pojo类的属性名称和类型
					 * 可以通过自定义注解,添加诸如中文名,长度,是否必须,备注信息,
                     * 哪些字段不需要等
					 * 对于类型又是pojo类型的没有做递归处理
					 */
					String fType = f.getType().getName();
					if(fType.indexOf(".")>-1) {
						fType = fType.substring(fType.lastIndexOf(".")+1);
					}
					result.put(f.getName(), fType);
	                /***
					 * 如果要获取字段的自定义注解信息,调用下边这个方法
					 * 这样做,需要修改原有POJO的属性定义部分代码
						getColumnAnnoInfo(result,f);
					***/
				}
			}catch(Exception e) {
				e.printStackTrace();
				return null;
			}
			return result;
		}


       /**获取字段的注解信息
		 * @param fieldMap 字段信息map
		 * @param f        目标字段
		 * @return
		 */
		private Map<String,Object> getColumnAnnoInfo(Map<String,Object> fieldMap, Field f) {
			
			try {
				MyColumn myc = (MyColumn) f.getAnnotation(MyColumn.class);
				if(myc != null) {
					fieldMap.put("cname",myc.name());
					fieldMap.put("length",myc.length());
					fieldMap.put("isRequire",myc.isRequire());
					fieldMap.put("memo",myc.memo());
				}
			}catch(Exception e) {
				e.printStackTrace();
				return fieldMap;
			}
			return fieldMap;
		}
}

同时支持RequestMapping,GetMapping和PostMapping的方法注解的写法:

RequestMapping requestMapp = (RequestMapping) method.getAnnotation(RequestMapping.class);
					if(requestMapp != null) {
						methodMap.put("requestUrl", requestMapp.value());
						methodMap.put("requestType", requestMapp.method());
						methodMap.put("methodDesc",  requestMapp.name());
					}else {
						GetMapping   requestMapp2 = (GetMapping) method.getAnnotation(GetMapping.class);
						if(requestMapp2 != null) {
							methodMap.put("requestUrl", requestMapp2.value());
							methodMap.put("requestType", "GET");
							methodMap.put("methodDesc",  requestMapp2.name());
						}else {
							PostMapping  requestMapp3 = (PostMapping) method.getAnnotation(PostMapping.class);
							if(requestMapp3 != null) {
								methodMap.put("requestUrl", requestMapp3.value());
								methodMap.put("requestType", "POST");
								methodMap.put("methodDesc",  requestMapp3.name());
							}else {
								continue;
							}
						}
					}

启动springboot工程,不需要添加额外的任何jar,访问地址:

http://localhost:8080/api/list

得到api文档所需的json串:

{
	"apiList": [{
		"clsUrl": ["/api"],
		"clsDesc": "测试生成接口文档",
		"clsName": "com.tiger.testApp.controller.APIDocController",
		"methodList": [{
			"methodDesc": "获取表名和注释以及表的字段信息",
			"requestType": ["GET"],
			"methodParams": [{
				"paramType": "Map",
				"paramName": "param"
			}, {
				"paramType": "Integer",
				"paramName": "pageSize"
			}],
			"requestUrl": ["/list"],
			"returnType": "Map"
		}]
	}, {
		"clsUrl": ["/db"],
		"clsDesc": "生成word文档",
		"clsName": "com.tiger.testApp.controller.TemplateController",
		"methodList": [{
			"methodDesc": "新增公司信息信息",
			"requestType": ["POST"],
			"methodParams": [{
				"paramType": {
					"companyCode": "String",
					"imgs": "List",
					"serialVersionUID": "long",
					"companyType": "Integer",
					"companyName": "String",
					"registorDate": "Date"
				},
				"paramName": "c"
			}],
			"requestUrl": ["/add"],
			"returnType": "int"
		}, {
			"methodDesc": "获取公司信息信息",
			"requestType": ["GET"],
			"methodParams": [{
				"paramType": "int",
				"paramName": "id"
			}],
			"requestUrl": ["/info"],
			"returnType": {
				"companyCode": "String",
				"imgs": "List",
				"serialVersionUID": "long",
				"companyType": "Integer",
				"companyName": "String",
				"registorDate": "Date"
			}
		}]
	}]
}

列注解:MyColumn.java:

package com.tiger.testApp.anno;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyColumn {

	//字段中文名
	String name() default "";
	//字段备注
	String memo() default "";
	//字段长度
	int length() default 1;
	//是否必须
	boolean isRequire() default true;
}

其中:

clsUrl:Controller类的映射地址;

clsDesc:类的功能描述;

clsName:类的全称;

methodList:类的所有映射函数;

methodDesc: 接口功能描述;

requestType:接口请求方式;

methodParams:接口参数列表,每个参数包括参数名称(paramName)和参数类型(paramType);

requestUrl:接口映射地址;

returnType:接口返回数据类型。

这样,就可以根据得到的数据,在浏览器上展示api接口信息,也可以根据我的上一篇博客:《java生成word版数据字典》来生成API的word文档。

待优化的地方:

1、正如代码注释中所写,对于POJO类型的参数或返回类型,没有处理其属性又是POJO类型的情况;

2、对于POJO类的属性,需要通过自定义的注解,修改原本的POJO类属性,以详细描述一下属性的中文名,长度,是否必填,备注等信息;

3、对参数类型或返回类型是Map<String,Object>的情况,无法描述其中的key,最好转换成一个新的POJO类型。

 

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部