最近在做一个项目,需要获取spring boot项目中的controller的接口列表,将其展示到界面,内容信息包括请求类型、请求地址、请求参数、接口描述、响应参数等。
实际上就是获取上图中红框部分内容,将这些内容生成一个对象放到一个集合中,展示到如下界面中,接口列表即是集合数据,选择某个接口则展示接口信息到界面。
分析之下,这个需求可以通过java的反射实现。整体思路如下:
1、首先创建接口对象,包含如下字段:接口编码、接口名称、接口描述、请求类型、请求地址、请求参数、响应参数。
2、由于支持get和post请求,入参可能是多个基础类型参数,也可能是实体对象,同理响应参数可能也是实体,所以将全部参数解析成参数对象,包含如下字段:字段值、字段名称、字段类型。再将其形成List设置到接口对象中。
3、通过反射获取Controller对象的方法列表,从方法列表中解析接口编码;从注解中解析接口名称、接口描述;从参数列表中解析请求参数对象;从返回类型中解析响应参数对象。
4、组装数据添加到list中。
涉及知识点:
1、Class.forName() 根据类名获取Class对象 2、class.isAnnotationPresent(RequestMapping.class) 根据class对象判断是否存在某个注解 3、RequestMapping requestAnnotation= (RequestMapping) class.getAnnotation(RequestMapping.class) 根据class对象获取注解对象(需要强制类型转换) 4、Method[] methods=class.getDeclaredMethods() 根据class对象获取方法列表 5、method.isAnnotationPresent(ApiOperation.class) 根据method对象判断是否存某个注解 6、ApiOperation annotation=method.getAnnotation(ApiOperation.class) 根据method对象获取注解对象 7、Parameter[] params = method.getParameters() 根据method对象获取参数列表数组 8、String paramType=param.getType().getName() 根据参数对象获取参数类型名称 9、param.getName() 根据参数对象获取参数名称 10、Type type = method.getGenericReturnType() 根据method对象获取返回类型 11、type instanceof ParameterizedType 判断类型对象是否是 参数化类型(有泛型的参数) 12、Type[] actualTypeArguments = parameterizedType.getActualTypeArguments() 根据参数化类型对象获取实际对象数组(泛型的类型) 13、String tempTypeName=parameterizedType.getRawType().getTypeName() 获取参数化类型的泛型类型名称 14、Class<?> classTemp=(Class<?>) type 将返回类型转换为Class对象 15、Field[] fields=class.getDeclaredFields() 根据class对象获取字段数组 16、field.getModifiers() 根据field字段获取修饰类型值 PUBLIC: 1 PRIVATE: 2 PROTECTED: 4 STATIC: 8 FINAL: 16 SYNCHRONIZED: 32 VOLATILE: 64 TRANSIENT: 128 NATIVE: 256 INTERFACE: 512 ABSTRACT: 1024 STRICT: 2048 17、Modifier.toString(field.getModifiers()) 根据修饰值获取具体修饰编码 如public static final 18、String fieldCode = field.getName() 根据field字段获取字段编码 19、field.isAnnotationPresent(ApiModelProperty.class) 根据field对象判断是否存在某个注解 20、ApiModelProperty annotation=field.getAnnotation(ApiModelProperty.class) 根据field对象获取注解对象
一、接口对象和参数对象
package dto.dataset; import entity.dataset.SysDataSetInterface; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import lombok.experimental.Accessors; import java.io.Serializable; import java.util.List; @Data @NoArgsConstructor @Accessors(chain = true) @ToString(callSuper = true) @EqualsAndHashCode(callSuper = false) @ApiModel(value = "LocalInterfaceDTO", description = "本地接口实体类") public class LocalInterfaceDTO implements Serializable { /** * 接口名称 */ @ApiModelProperty(value = "接口名称") private String interfaceName; /** * 接口编码 */ @ApiModelProperty(value = "接口编码") private String interfaceCode; /** * 请求地址 */ @ApiModelProperty(value = "请求地址") private String requestUri; /** * 请求类型 */ @ApiModelProperty(value = "请求类型") private String requestType; /** * 请求参数 */ @ApiModelProperty(value = "请求参数") private List<InterfaceParamDTO> requestParam; /** * 请求参数是否是实体 */ @ApiModelProperty(value = "请求参数是否是实体") private Boolean requestIsEntity; /** * 接口描述 */ @ApiModelProperty(value = "接口描述") private String interfaceDesc; /** * 响应参数 */ @ApiModelProperty(value = "响应参数") private List<InterfaceParamDTO> responseParam; /** * 响应参数是否是集合 */ @ApiModelProperty(value = "响应参数是否是集合") private Boolean responseIsList; /** * 当前页数 */ @ApiModelProperty(value = "当前页数") private Integer current; /** * 每页条数 */ @ApiModelProperty(value = "每页条数") private Integer size; /** * 在DTO中新增并自定义字段,需要覆盖验证的字段,请新建DTO。Entity中的验证规则可以自行修改,但下次生成代码时,记得同步代码!! */ private static final long serialVersionUID = 1L; public static LocalInterfaceDTO build() { return new LocalInterfaceDTO(); } }
package dto.dataset; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import lombok.experimental.Accessors; import java.io.Serializable; @Data @NoArgsConstructor @Accessors(chain = true) @ToString(callSuper = true) @EqualsAndHashCode(callSuper = false) @ApiModel(value = "InterfaceParamDTO", description = "接口参数DTO") public class InterfaceParamDTO implements Serializable { /** * 参数名称 */ @ApiModelProperty(value = "参数名称") private String paramName; /** * 参数编码 */ @ApiModelProperty(value = "参数编码") private String paramCode; /** * 参数编码 */ @ApiModelProperty(value = "参数类型") private String paramType; /** * 参数值 */ @ApiModelProperty(value = "参数值") private String paramValue; /** * 在DTO中新增并自定义字段,需要覆盖验证的字段,请新建DTO。Entity中的验证规则可以自行修改,但下次生成代码时,记得同步代码!! */ private static final long serialVersionUID = 1L; public static InterfaceParamDTO build() { return new InterfaceParamDTO(); } }
二、解析目标Controller
package controller.dataset; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import BaseController; import MyPage; import R; import api.dataset.DataSetDataApi; import dto.dataset.DataSetDTO; import dto.dataset.DataSetMappingRefTreeDTO; import manage.entity.qualityCheck.QualityCheckImplement; import manage.entity.qualityCheck.QualityCheckPlan; import enums.SystemAlias; import utils.UriUtil; import annotation.FeignAutowired; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @Slf4j @Validated @RestController @RequestMapping("/dataSetLocalInterface") @Api(value = "DataSetLocalInterfaceController", tags = "数据集本地接口控制层") public class DataSetLocalInterfaceController extends BaseController { @Autowired private UriUtil uriUtil; /* TODO 本地接口待完善 */ /** * 示例接口 * @param planId * @return * @throws URISyntaxException */ @ApiOperation(value = "根据计划id查询质控实施列表", notes = "根据计划id查询质控实施列表") @GetMapping("/queryQualityImplementByPlanId") public R<List<QualityCheckImplement>> queryQualityImplementByPlanId(@RequestParam("planId") Integer planId) throws URISyntaxException { // 调用查询方法 List<QualityCheckImplement> qualityCheckImplementList=new ArrayList<QualityCheckImplement>(); return success(qualityCheckImplementList); } /** * 示例接口2 * @param plan * @return * @throws URISyntaxException */ @ApiOperation(value = "根据计划id查询质控实施列表2", notes = "根据计划id查询质控实施列表2") @PostMapping("/queryQualityImplementByPlanId2") public R<QualityCheckImplement> queryQualityImplementByPlanId2(@RequestBody QualityCheckPlan plan) throws URISyntaxException { // 调用查询方法 QualityCheckImplement qualityCheckImplement=new QualityCheckImplement(); return success(qualityCheckImplement); } }
三、解析Controller和Service
package controller.dataset; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import BaseController; import MyPage; import R; import api.dataset.DataSetDataApi; import dataset.SysDataSetService; import dto.dataset.DataSetDTO; import dto.dataset.DataSetMappingRefTreeDTO; import dto.dataset.InterfaceParamDTO; import dto.dataset.LocalInterfaceDTO; import enums.SystemAlias; import common.utils.UriUtil; import openfeign.annotation.FeignAutowired; import io.swagger.annotations.*; import io.swagger.models.auth.In; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.util.CollectionUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import sun.reflect.misc.ReflectUtil; import java.lang.annotation.Annotation; import java.lang.reflect.*; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @Validated @RestController @RequestMapping("/sysDataSetInterface") @Api(value = "SysDataSetInterfaceController", tags = "数据集接口控制层") public class SysDataSetInterfaceController extends BaseController { @Autowired private UriUtil uriUtil; @Autowired private SysDataSetService sysDataSetService; @ApiOperation(value = "查询本地接口列表", notes = "查询本地接口列表") @GetMapping("/queryLocalInterfaceList") public R<List<LocalInterfaceDTO>> queryLocalInterfaceList() throws URISyntaxException { try { // 解析本地接口Controller类 Class c=Class.forName(DataSetLocalInterfaceController.class.getName()); List<LocalInterfaceDTO> localInterfaceDTOList=sysDataSetService.queryLocalInterfaceList(c); System.out.println(JSONObject.toJSON(localInterfaceDTOList)); return success(localInterfaceDTOList); }catch (Exception e){ System.out.println(e); return null; } } }
package service.dataset.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.TypeReference; import dto.basis.SpeechInfoDTO; import service.basis.SpeechService; import service.dataset.SysDataSetService; import repository.CacheRepository; import dto.dataset.InterfaceParamDTO; import dto.dataset.LocalInterfaceDTO; import utils.StrPool; import com.iflytek.cloud.speech.*; import com.iflytek.msp.lfasr.LfasrClient; import com.iflytek.msp.lfasr.model.Message; import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.*; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import static CacheKey.SPEECH_CACHE; @Service @Slf4j public class SysDataSetServiceImpl implements SysDataSetService { private static String[] baseTypes = {"java.lang.Integer", "java.lang.Double", "java.lang.Float", "java.lang.Long", "java.lang.Short", "java.lang.Byte", "java.lang.Boolean", "java.lang.Character", "java.lang.String", "int","double","long","short","byte","boolean","char","float"}; @Override public List<LocalInterfaceDTO> queryLocalInterfaceList(Class c) throws ClassNotFoundException { // 创建本地接口列表集合 List<LocalInterfaceDTO> localInterfaceDTOList=new ArrayList<LocalInterfaceDTO>(); // 请求地址头 String requestMapping=""; // 判断存在RequestMapping注解 if(c.isAnnotationPresent(RequestMapping.class)){ // 获取注解 RequestMapping requestMappingAnnotation= (RequestMapping) c.getAnnotation(RequestMapping.class); // 判断注解对象存在 获取值 if(requestMappingAnnotation!=null && requestMappingAnnotation.value().length>0){ requestMapping = requestMappingAnnotation.value()[0]; } } // 获取方法列表 Method[] methods=c.getDeclaredMethods(); // 判断不为空 if(!ArrayUtils.isEmpty(methods)){ // 遍历方法列表 for(Method method:methods){ // 创建本地接口列表对象 LocalInterfaceDTO localInterfaceDTO=new LocalInterfaceDTO(); // 设置接口编码 localInterfaceDTO.setInterfaceCode(method.getName()); // 判断当前方法存在ApiOperation注解 if(method.isAnnotationPresent(ApiOperation.class)){ // 获取注解对象 ApiOperation annotation=method.getAnnotation(ApiOperation.class); // 设置接口名称 localInterfaceDTO.setInterfaceName(annotation.value()); // 设置接口描述 localInterfaceDTO.setInterfaceDesc(annotation.notes()); } // 请求地址 String requestURI=""; // 请求类型 String requestType=""; // 判断当前方法存在GetMapping注解 if(method.isAnnotationPresent(GetMapping.class)){ // 获取注解对象 GetMapping annotation=method.getAnnotation(GetMapping.class); // 判断注解对象存在 获取值 if(annotation!=null && annotation.value().length>0){ requestURI = annotation.value()[0]; } requestType = "get"; // 判断当前方法存在PostMapping注解 }else if(method.isAnnotationPresent(PostMapping.class)){ // 获取注解对象 PostMapping annotation=method.getAnnotation(PostMapping.class); // 判断注解对象存在 获取值 if(annotation!=null && annotation.value().length>0){ requestURI = annotation.value()[0]; } requestType = "post"; } // 设置请求地址 localInterfaceDTO.setRequestURI(requestMapping+requestURI); // 设置请求类型 localInterfaceDTO.setRequestType(requestType); // 创建请求对象集合 List<InterfaceParamDTO> requestParamList=new ArrayList<InterfaceParamDTO>(); // 获取参数列表 Parameter[] params = method.getParameters(); // 判断不为空 if(params!=null && params.length>0){ // 遍历参数列表 for(Parameter param:params){ // 获取参数类型 String paramType=param.getType().getName(); // 如果当前属于基本类型则直接添加到请求参数中 if(ArrayUtils.contains(baseTypes,paramType)){ // 创建参数对象 InterfaceParamDTO requestParam=new InterfaceParamDTO(); // 设参数编码 requestParam.setParamCode(param.getName()); // 设置参数名称 requestParam.setParamName(param.getName()); // 设置参数类型 requestParam.setParamType(paramType); // 添加参数到列表 requestParamList.add(requestParam); // 调用方法获取该对象的参数列表集合 }else{ // 根据类型Class获取参数列表集合 List<InterfaceParamDTO> tempRequestParamList=parseParamDTOListForClass(param.getType()); // 合并集合 requestParamList.addAll(tempRequestParamList); } } } // 设置请求参数 localInterfaceDTO.setRequestParam(requestParamList); // 创建响应对象集合 List<InterfaceParamDTO> responseParamList=new ArrayList<InterfaceParamDTO>(); // 响应参数是否是集合 Boolean responseIsList = false; // 获取返回类型 Type t = method.getGenericReturnType(); // 解析第一层R对象 判断是否是参数化类型 if(t instanceof ParameterizedType){ // 转换参数化类型 ParameterizedType parameterizedType = (ParameterizedType) t; // 获取实际对象值 Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); // 遍历实际对象 for(Type type:actualTypeArguments) { // 解析第二层对象 如果是Map List等则还是参数化对象 // 判断是否是参数化类型 if(type instanceof ParameterizedType){ ParameterizedType parameterizedType1 = (ParameterizedType) type; // 获取类型名称 String tempTypeName=parameterizedType1.getRawType().getTypeName(); // 当前是集合 则继续解析 if("java.util.List".equals(tempTypeName)){ // 获取实际对象值 Type[] actualTypeArguments1= parameterizedType1.getActualTypeArguments(); // 遍历实际对象 for(Type type1:actualTypeArguments1) { // 获取实际对象类 Class<?> classTemp=(Class<?>) type1; // 调用方法获取参数列表集合 List<InterfaceParamDTO> tempResponseParamList =parseParamDTOListForClass(classTemp); // 合并集合 responseParamList.addAll(tempResponseParamList); } // 设置为true responseIsList = true; } // 否则是具体对象类 }else{ Class<?> classTemp=(Class<?>) type; // 调用方法获取参数列表集合 List<InterfaceParamDTO> tempResponseParamList = parseParamDTOListForClass(classTemp); // 合并集合 responseParamList.addAll(tempResponseParamList); } } } // 设置响应参数 localInterfaceDTO.setResponseParam(responseParamList); // 设置响应参数是否是集合标志 localInterfaceDTO.setResponseIsList(responseIsList); // 添加到集合中 localInterfaceDTOList.add(localInterfaceDTO); } } return localInterfaceDTOList; } /** * 根据类转换参数列表集合 * @param cls * @return */ public static List<InterfaceParamDTO> parseParamDTOListForClass(Class cls) throws ClassNotFoundException { // 创建参数列表集合 List<InterfaceParamDTO> paramDTOList = new ArrayList<InterfaceParamDTO>(); // 获取class中的全部字段 Field[] fields=cls.getDeclaredFields(); // 遍历字段 for(Field field:fields){ // 当前为private修饰 if(2==field.getModifiers()){ // 字段名称 String fieldName = ""; // 字段编码 String fieldCode = field.getName(); // 判断当前字段是否有ApiModelProperty注解 if(field.isAnnotationPresent(ApiModelProperty.class)){ // 获取注解 ApiModelProperty annotation=field.getAnnotation(ApiModelProperty.class); // 设置字段名称 fieldName=annotation.value(); } // 创建参数对象 InterfaceParamDTO param=new InterfaceParamDTO(); // 设置参数名称 param.setParamName(fieldName); // 设置参数编码 param.setParamCode(fieldCode); // 设置参数类型 param.setParamType(field.getType().getName()); // 添加到集合中 paramDTOList.add(param); } } return paramDTOList; } }
四、接口请求返回值
{ "code": 0, "data": [ { "interfaceName": "根据计划id查询质控实施列表", "interfaceCode": "queryQualityImplementByPlanId", "requestURI": "/dataSetLocalInterface/queryQualityImplementByPlanId", "requestType": "get", "requestParam": [ { "paramName": "planId", "paramCode": "planId", "paramType": "java.lang.Integer" } ], "interfaceDesc": "根据计划id查询质控实施列表", "responseParam": [ { "paramName": "主键id", "paramCode": "id", "paramType": "java.lang.Integer" }, { "paramName": "计划id", "paramCode": "planId", "paramType": "java.lang.Integer" }, { "paramName": "计划流水号", "paramCode": "planNo", "paramType": "java.lang.String" }, { "paramName": "计划检查日期范围(格式:YYYY-MM-DD至YYYY-MM-DD)", "paramCode": "planCheckDate", "paramType": "java.lang.String" }, { "paramName": "计划检查时间范围(格式:MM:SS至MM:SS)", "paramCode": "planCheckTime", "paramType": "java.lang.String" }, { "paramName": "检查状态 0 未检查 1 已检查", "paramCode": "checkStatus", "paramType": "java.lang.Integer" }, { "paramName": "科室id", "paramCode": "deptId", "paramType": "java.lang.String" }, { "paramName": "科室名称", "paramCode": "deptName", "paramType": "java.lang.String" }, { "paramName": "检查人ids", "paramCode": "checkUserId", "paramType": "java.lang.String" }, { "paramName": "检查人姓名", "paramCode": "checkUserName", "paramType": "java.lang.String" }, { "paramName": "检查份数", "paramCode": "checkQuantity", "paramType": "java.lang.Integer" }, { "paramName": "存在回归问题 0不存在 1存在", "paramCode": "exitsRegression", "paramType": "java.lang.Integer" } ], "responseIsList": true }, { "interfaceName": "根据计划id查询质控实施列表2", "interfaceCode": "queryQualityImplementByPlanId2", "requestURI": "/dataSetLocalInterface/queryQualityImplementByPlanId2", "requestType": "post", "requestParam": [ { "paramName": "主键id", "paramCode": "id", "paramType": "java.lang.Integer" }, { "paramName": "计划名称", "paramCode": "planName", "paramType": "java.lang.String" }, { "paramName": "计划流水号", "paramCode": "planNo", "paramType": "java.lang.String" }, { "paramName": "质控分级id(quality_check_level)", "paramCode": "levelId", "paramType": "java.lang.Integer" }, { "paramName": "质控类型id(quality_check_type)", "paramCode": "typeId", "paramType": "java.lang.Integer" }, { "paramName": "计划开始日期", "paramCode": "planStartDate", "paramType": "java.time.LocalDate" }, { "paramName": "计划结束日期", "paramCode": "planEndDate", "paramType": "java.time.LocalDate" }, { "paramName": "质控小组id(quality_check_group)", "paramCode": "groupId", "paramType": "java.lang.Integer" }, { "paramName": "检查组长ids", "paramCode": "leaderIds", "paramType": "java.lang.String" }, { "paramName": "检查组长名称", "paramCode": "leaderNames", "paramType": "java.lang.String" }, { "paramName": "检查成员ids", "paramCode": "memberIds", "paramType": "java.lang.String" }, { "paramName": "检查成员名称", "paramCode": "memberNames", "paramType": "java.lang.String" }, { "paramName": "计划状态 0待发起 1未开始 2检查中 3已完结", "paramCode": "planStatus", "paramType": "java.lang.Integer" }, { "paramName": "检查重点", "paramCode": "checkPoint", "paramType": "java.lang.String" }, { "paramName": "检查时段 1单日 2多日", "paramCode": "checkTimePeriod", "paramType": "java.lang.Integer" }, { "paramName": "问题回归模式 1自动选择 2手动选择", "paramCode": "regressionModel", "paramType": "java.lang.Integer" }, { "paramName": "检查分析总结", "paramCode": "analysisContent", "paramType": "java.lang.String" } ], "interfaceDesc": "根据计划id查询质控实施列表2", "responseParam": [ { "paramName": "主键id", "paramCode": "id", "paramType": "java.lang.Integer" }, { "paramName": "计划id", "paramCode": "planId", "paramType": "java.lang.Integer" }, { "paramName": "计划流水号", "paramCode": "planNo", "paramType": "java.lang.String" }, { "paramName": "计划检查日期范围(格式:YYYY-MM-DD至YYYY-MM-DD)", "paramCode": "planCheckDate", "paramType": "java.lang.String" }, { "paramName": "计划检查时间范围(格式:MM:SS至MM:SS)", "paramCode": "planCheckTime", "paramType": "java.lang.String" }, { "paramName": "检查状态 0 未检查 1 已检查", "paramCode": "checkStatus", "paramType": "java.lang.Integer" }, { "paramName": "科室id", "paramCode": "deptId", "paramType": "java.lang.String" }, { "paramName": "科室名称", "paramCode": "deptName", "paramType": "java.lang.String" }, { "paramName": "检查人ids", "paramCode": "checkUserId", "paramType": "java.lang.String" }, { "paramName": "检查人姓名", "paramCode": "checkUserName", "paramType": "java.lang.String" }, { "paramName": "检查份数", "paramCode": "checkQuantity", "paramType": "java.lang.Integer" }, { "paramName": "存在回归问题 0不存在 1存在", "paramCode": "exitsRegression", "paramType": "java.lang.Integer" } ], "responseIsList": false } ], "msg": "ok", "path": null, "extra": null, "timestamp": "1608723946343", "isSuccess": true, "isError": false }
发表评论