最近在做一个手机后台项目,使用的是SpringMVC,开发的接口是HTTP接口。在接口写完后需要在网页中吧接口的名称测试地址等信息添加到网页中,感觉这样很麻烦还容易漏。于是就写了一个自定义注解通过注解的方式将接口的描述信息加入到接口中,通过注解描述接口信息并且生产接口测试地址
先看使用方法及最终效果
@ResponseBody
@RequestMapping("/getBusWaiting")
@AppInterface(value="获取候车信息",group="test",order=1,params={
@InterfaceParam(name="lineName",desc="线路名称",testValue="B2"),
@InterfaceParam(name="isUpDown",desc="上下行标识",testValue="1"),
@InterfaceParam(name="stationNum",desc="站序",testValue="0"),
@InterfaceParam(name="len",desc="长度",testValue="700")
})
public AppResponse getBusWaitingInfo(String lineName,Integer isUpDown,Integer stationNum,Integer len){
AppResponse result = new AppResponse();
return result;
}
下面是具体实现
接口描述类
/**
* 创建人:王成委
* 创建时间:2014年12月8日 下午5:28:11
* 类描述: 标记手机App接口,接口描述信息,用于生成接口信息及测试路径
* 版权说明: © 2014 Tiamaes
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AppInterface {
/**
* 方法描述:接口名称
* @return String
*/
String value();
/**
* 方法描述:分组
* @return String
*/
String group() default "";
/**
* 方法描述:排序
* @return int
*/
int order () default 0;
/**
* 方法描述:参数列表
* @return InterfaceParam[]
*/
InterfaceParam[] params() default {};
}
接口参数类
/**
* 创建人:王成委
* 创建时间:2014年12月8日 下午5:29:34
* 类描述: 手机App接口参数说明
* 版权说明: © 2014 Tiamaes
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InterfaceParam {
/**
* 方法描述:参数名称
* @return String
*/
String name();
/**
* 方法描述:接口说明
* @return String
*/
String desc();
/**
* 方法描述:测试参数值
* @return String
*/
String testValue() default "";
}
testValue支持自定义变量,在主类中有具体实现
package com.tiamaes.gjds.app.aop;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import com.tiamaes.gjds.app.annotation.AppInterface;
import com.tiamaes.gjds.app.annotation.InterfaceParam;
import com.tiamaes.gjds.app.base.RequestMethodMapping;
import com.tiamaes.gjds.app.base.RequestMethodParameter;
import com.tiamaes.gjds.app.base.Variable;
import com.tiamaes.gjds.util.SetUtils;
/**
* 类描述: 生成接口描述信息并放入Application中
* 创建人:王成委
* 创建时间:2015年1月19日 下午4:42:24
* 版权说明: © 2015 Tiamaes
* @see com.tiamaes.gjds.app.annotation.AppInterface
*/
public class InterfaceAnnotationConfigProcesser implements ApplicationContextAware,InitializingBean{
private Log logger = LogFactory.getLog(getClass());
private Map> mappers =
new HashMap>();
private WebApplicationContext applicationContext;
/**
* 方法描述:加载带有{@link com.tiamaes.gjds.app.annotation.AppInterface}注解的接口
* 首先需要获取所有接口,然后过滤方法中带有@AppInterface
* 创建人: 王成委
* 创建时间: 2015年1月10日 上午10:50:06
*/
public void loadHandlerMapping(){
this.logger.info("初始化配置");
Map handlers = BeanFactoryUtils.beansOfTypeIncludingAncestors(
applicationContext, HandlerMapping.class, true, false);
for(Entry entry : handlers.entrySet()){
HandlerMapping mapping = entry.getValue();
if(mapping instanceof RequestMappingHandlerMapping){
RequestMappingHandlerMapping requestHandler = (RequestMappingHandlerMapping)mapping;
Map handlerMethods = requestHandler.getHandlerMethods();
for(Entry handlerMethod : handlerMethods.entrySet()){
AppInterface annotation = handlerMethod.getValue().getMethodAnnotation(AppInterface.class);
if(annotation== null)continue;
PatternsRequestCondition patternsCondition = handlerMethod.getKey().getPatternsCondition();
String requestUrl = SetUtils.first(patternsCondition.getPatterns());
this.register(requestUrl, annotation,handlerMethod.getValue().getBeanType());
}
}
}
}
/**
* 方法描述:注册方法
* 创建人: 王成委
* 创建时间: 2015年1月10日 上午10:50:06
* @param requestUrl
* @param annotation
* @param beanType
*/
private void register(String requestUrl, AppInterface annotation,
Class> beanType) {
String group = annotation.group();
List groupMappers = this.mappers.get(group);
if(groupMappers == null)groupMappers = new ArrayList();
RequestMethodMapping mapper = new RequestMethodMapping();
mapper.setGroup(group);
mapper.setController(beanType.getName());
mapper.setOrder(annotation.order());
mapper.setName(annotation.value());
mapper.setUrl(requestUrl);
mapper.setParams(this.toParameters(annotation.params()));
groupMappers.add(mapper);
this.mappers.put(group, groupMappers);
}
/**
* 方法描述:读取参数
* 创建人: 王成委
* 创建时间: 2015年1月10日 上午10:50:06
* @param params
* @return
*/
private List toParameters(InterfaceParam[] params){
List parameters = new ArrayList();
for(InterfaceParam param : params){
RequestMethodParameter bean = new RequestMethodParameter();
bean.setName(param.name());
bean.setDesc(param.desc());
if(StringUtils.startsWithIgnoreCase(param.testValue(), "#")){
String var = param.testValue();
String value = getByVariable(var.substring(var.indexOf("#")+1));
bean.setTestValue(value);
}else{
bean.setTestValue(param.testValue());
}
parameters.add(bean);
}
return parameters;
}
/**
* 方法描述:获取变量的值
* 创建人: 王成委
* 创建时间: 2015年1月10日 上午10:50:06
* @param var
* @return
*/
private String getByVariable(String var){
Variable variable = Variable.valueOf(var);
return variable.getValue();
}
/**
* 方法描述:对接口方法根据分组排序
* 创建人: 王成委
* 创建时间: 2015年1月20日 下午16:00:06
*/
private void orderMappers(){
for(Entry> entry : this.mappers.entrySet() ){
Collections.sort(entry.getValue(), new Comparator() {
@Override
public int compare(RequestMethodMapping o1, RequestMethodMapping o2) {
Integer one = o1.getOrder();
Integer two = o2.getOrder();
if(one != null && two != null) return one.intValue()-two.intValue();
return 0;
}
});
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = (WebApplicationContext)applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
this.loadHandlerMapping();
this.orderMappers();
this.applicationContext.getServletContext().setAttribute("api", this.mappers);
}
/**
* 方法描述:刷新接口信息
* 创建人: 王成委
* 创建时间: 2015年1月10日 上午10:50:06
* @throws Exception
*/
public void refresh() throws Exception{
this.mappers = new HashMap>();
this.afterPropertiesSet();
}
}
利用C标签生产接口描述信息
接口地址
接口名称
参数说明
${list.url}
${list.name}
${params.name}:${params.desc}