前言
最近使用了最新版本的springfox-swagger2做api接口文档;但是发现之前的position已过时,故想着修改springfox的java代码来实现按position排序~
依赖信息
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
代码分析
我们启动工程后,访问swagger-ui.html能看到获取api信息的url地址:http://localhost:8080/cms/v2/api-docs
我们使用idea搜索(ctrl+shitf+F)一下api-docs,范围选择scope会发现一个Swagger2Controller的类。打开,可见到如下代码:
@RequestMapping(
value = DEFAULT_URL,
method = RequestMethod.GET,
produces = { APPLICATION_JSON_VALUE, HAL_MEDIA_TYPE })
@PropertySourcedMapping(
value = "${springfox.documentation.swagger.v2.path}",
propertyKey = "springfox.documentation.swagger.v2.path")
@ResponseBody
public ResponseEntity getDocumentation(
@RequestParam(value = "group", required = false) String swaggerGroup,
HttpServletRequest servletRequest) {
String groupName = Optional.fromNullable(swaggerGroup).or(Docket.DEFAULT_GROUP_NAME);
Documentation documentation = documentationCache.documentationByGroup(groupName);
if (documentation == null) {
LOGGER.warn("Unable to find specification for group {}", groupName);
return new ResponseEntity(HttpStatus.NOT_FOUND);
}
Swagger swagger = mapper.mapDocumentation(documentation);
UriComponents uriComponents = componentsFrom(servletRequest, swagger.getBasePath());
swagger.basePath(Strings.isNullOrEmpty(uriComponents.getPath()) ? "/" : uriComponents.getPath());
if (isNullOrEmpty(swagger.getHost())) {
swagger.host(hostName(uriComponents));
}
return new ResponseEntity(jsonSerializer.toJson(swagger), HttpStatus.OK);
}
从代码上看,我们可了解到,前端swagger-ui所需的api接口信息都是由Swagger对象提供。我们进入此方法
Swagger swagger = mapper.mapDocumentation(documentation);
可看到此方法的实现代码如下:
public Swagger mapDocumentation(Documentation from) {
if ( from == null ) {
return null;
}
Swagger swagger = new Swagger();
swagger.setVendorExtensions( vendorExtensionsMapper.mapExtensions( from.getVendorExtensions() ) );
swagger.setSchemes( mapSchemes( from.getSchemes() ) );
swagger.setPaths( mapApiListings( from.getApiListings() ) );
swagger.setHost( from.getHost() );
swagger.setDefinitions( modelMapper.modelsFromApiListings( from.getApiListings() ) );
swagger.setSecurityDefinitions( securityMapper.toSecuritySchemeDefinitions( from.getResourceListing() ) );
ApiInfo info = fromResourceListingInfo( from );
if ( info != null ) {
swagger.setInfo( mapApiInfo( info ) );
}
swagger.setBasePath( from.getBasePath() );
swagger.setTags( tagSetToTagList( from.getTags() ) );
List list2 = from.getConsumes();
if ( list2 != null ) {
swagger.setConsumes( new ArrayList( list2 ) );
}
else {
swagger.setConsumes( null );
}
List list3 = from.getProduces();
if ( list3 != null ) {
swagger.setProduces( new ArrayList( list3 ) );
}
else {
swagger.setProduces( null );
}
return swagger;
}
从代码上的字面看,可以估计到api的请求的url等信息主要由下面的方法得到。
swagger.setPaths( mapApiListings( from.getApiListings() ) );
为了查看具体的信息,我们先在此处打个断点。运行工程看看swagger paths里面的数据信息。某次执行后,debug的信息如下
paths = {TreeMap@7288} size = 4
0 = {TreeMap$Entry@7292} "/generator.cmd/a/generator" ->
key = "/generator.cmd/a/generator"
value = {Path@7297}
1 = {TreeMap$Entry@7293} "/generator.cmd/b/generator" ->
key = "/generator.cmd/b/generator"
value = {Path@7299}
2 = {TreeMap$Entry@7294} "/generator.cmd/c/generator" ->
key = "/generator.cmd/c/generator"
value = {Path@7301}
3 = {TreeMap$Entry@7295} "/generator.cmd/generator" ->
key = "/generator.cmd/generator"
value = {Path@7303}
由此可见,在生成sagger信息的时候,就已经默认使用了TreeMap方法来排序;故返回给前端的信息都是由url的path排序的。所以为实现按position排序,估计可以由此着手修改。
为此我们重写该方法:
package xxx.swagger;
import com.google.common.base.Optional;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.Swagger;
import io.swagger.models.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.service.ApiDescription;
import springfox.documentation.service.ApiListing;
import springfox.documentation.service.Documentation;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static springfox.documentation.builders.BuilderDefaults.nullToEmptyList;
@Component
@Primary
public class ServiceModelToSwagger2MapperImplExt extends ServiceModelToSwagger2MapperImpl {
private static final Logger logger = LoggerFactory.getLogger(ServiceModelToSwagger2MapperImplExt.class);
@Override
public Swagger mapDocumentation(Documentation from) {
Swagger swagger = super.mapDocumentation(from);
swagger.setPaths( mapApiListings( from.getApiListings() ) );
// 可自定义tag的排序
// List tags = swagger.getTags();
// Collections.sort(tags, new Comparator() {
// @Override
// public int compare(Tag left, Tag right) {
// String leftTag = left.getName().split("、")[0];
// String rightTag = right.getName().split("、")[0];
// int leftNum = 0;
// int rightNum = 0;
// try{
// leftNum = Integer.parseInt(leftTag);
// rightNum = Integer.parseInt(rightTag);
// }catch (Exception e){}
// int position = Integer.compare(leftNum, rightNum);
// return position;
// }
// });
// swagger.setTags(tags);
return swagger;
}
protected Map mapApiListings(Multimap apiListings) {
Map paths = new LinkedHashMap<>();
Multimap apiListingMap = LinkedListMultimap.create();
Iterator iter = apiListings.entries().iterator();
while(iter.hasNext())
{
Map.Entry entry = (Map.Entry)iter.next();
ApiListing apis = entry.getValue();
List apiDesc = apis.getApis();
List newApi = new ArrayList<>();
for(ApiDescription a:apiDesc){
newApi.add(a);
}
Collections.sort(newApi, new Comparator() {
@Override
public int compare(ApiDescription left, ApiDescription right) {
int leftPos = left.getOperations().size() == 1 ? left.getOperations().get(0).getPosition() : 0;
int rightPos = right.getOperations().size() == 1 ? right.getOperations().get(0).getPosition() : 0;
int position = Integer.compare(leftPos, rightPos);
if(position == 0) {
position = left.getPath().compareTo(right.getPath());
}
return position;
}
});
try {
//因ApiListing的属性都是final故需要通过反射来修改值
ModifyFinalUtils.modify(apis, "apis", newApi);
} catch (Exception e) {
e.printStackTrace();
}
apiListingMap.put(entry.getKey(),apis);
}
for (ApiListing each : apiListingMap.values()) {
for (ApiDescription api : each.getApis()) {
paths.put(api.getPath(), mapOperations(api, Optional.fromNullable(paths.get(api.getPath()))));
}
}
return paths;
}
private Path mapOperations(ApiDescription api, Optional existingPath) {
Path path = existingPath.or(new Path());
for (springfox.documentation.service.Operation each : nullToEmptyList(api.getOperations())) {
Operation operation = mapOperation(each);
path.set(each.getMethod().toString().toLowerCase(), operation);
}
return path;
}
}
public class ModifyFinalUtils {
public static void modify(Object object, String fieldName, Object newFieldValue) throws Exception {
Field field = object.getClass().getDeclaredField(fieldName);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true); //Field 的 modifiers 是私有的
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
if(!field.isAccessible()) {
field.setAccessible(true);
}
field.set(object, newFieldValue);
}
}
执行后,我们debug可以得到以下信息
paths = {LinkedHashMap@7364} size = 4
0 = {LinkedHashMap$Entry@7370} "/generator.cmd/b/generator" ->
key = "/generator.cmd/b/generator"
value = {Path@7374}
1 = {LinkedHashMap$Entry@7371} "/generator.cmd/generator" ->
key = "/generator.cmd/generator"
value = {Path@7375}
2 = {LinkedHashMap$Entry@7372} "/generator.cmd/c/generator" ->
key = "/generator.cmd/c/generator"
value = {Path@7376}
3 = {LinkedHashMap$Entry@7373} "/generator.cmd/a/generator" ->
key = "/generator.cmd/a/generator"
value = {Path@7377}
看来,按position排序已经生效。