快速上手
1:引入依赖
注意事项:版本不能高于2.0.9,因为目前官网knife4j官网上对于高版本得还在迭代。官网地址
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.9</version>
</dependency>
2:自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonProperty {
String key(); //key
String example() default "";
Class type() default String.class; //支持string 和 int
String description() default "";
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonObject {
ApiJsonProperty[] value(); //对象属性值
String name(); //对象名称
}
3:编写参数构建插件
import com.fasterxml.classmate.TypeResolver;
import com.phm.annotation.ApiJsonProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.ParameterContext;
import java.util.Map;
import java.util.Optional;
//plugin加载顺序,默认是最后加载
@Component
@Order
public class MapApiReader extends ClassLoader implements ParameterBuilderPlugin {
@Autowired
private TypeResolver typeResolver;
public void apply(ParameterContext parameterContext) {
Map<String, Object> maps = SwaggerMapContext.getMap();
ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();
OperationContext operationContext = parameterContext.getOperationContext();
String requestMappingPatternName = operationContext.requestMappingPattern();
Optional<String> parameterNameOptional = methodParameter.defaultName();
String parameterName = parameterNameOptional.get();
if (methodParameter.getParameterType().canCreateSubtype(Map.class)) {
System.out.println(requestMappingPatternName);
String name = "H" + parameterName;
name = SwaggerASMUtil.returnClassName(requestMappingPatternName,name);
ApiJsonProperty[] properties = (ApiJsonProperty[]) maps.get(requestMappingPatternName);
byte[] cs = SwaggerASMUtil.createRefModel(properties,name);
Class hw = this.defineClass(name, cs, 0, cs.length);
parameterContext.getDocumentationContext().getAdditionalModels().add(typeResolver.resolve(hw));
parameterContext.parameterBuilder().parameterType("body").modelRef(new ModelRef(name)).name(name);
}
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
}
注意事项:一定要配置获取项目得名称进行拼接,否则后续map得key值无法精准匹配
import com.phm.annotation.ApiJsonObject;
import com.phm.annotation.ApiJsonProperty;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Autowired
private Environment env;
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class clazz = bean.getClass();
Package packageStr = clazz.getPackage();
String packAgeName = packageStr.getName();
if(!packAgeName.contains(SwaggerMapContext.filterPackage)){
return bean;
}
if(clazz.getAnnotation(RestController.class) == null && clazz.getAnnotation(Controller.class) == null){
return bean;
}
RequestMapping controllerRequestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class);
String classRequestUrl = Arrays.toString(controllerRequestMapping.value());
List methods = Arrays.asList(clazz.getDeclaredMethods());
Iterator<Method> iterator = methods.iterator();
//获取项目名称
String property = env.getProperty("server.servlet.context-path");
while(iterator.hasNext()){
Method method = iterator.next();
String methodRequest = getRequestUrl(method);
String key = classRequestUrl + methodRequest ;
key = key.replaceAll("\\[","").replaceAll("\\]","");
ApiJsonObject annotation = method.getAnnotation(ApiJsonObject.class);
if(annotation != null){
ApiJsonProperty[] values = annotation.value();
SwaggerMapContext.getMap().put(property+key,values);
}
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 这边只做简单打印 原样返回bean
//if(AutoConfigurationPackages.class.getName().equals(beanName)){
// System.out.println("postProcessBeforeInitialization===="+beanName);
//}
return bean;
}
private String getRequestUrl(Method method){
String methodRequest = "";
if(method.getAnnotation(RequestMapping.class) != null) {
methodRequest = Arrays.toString(method.getAnnotation(RequestMapping.class).value());
}
if(method.getAnnotation(PutMapping.class) != null) {
methodRequest = Arrays.toString(method.getAnnotation(PutMapping.class).value());
}
if(method.getAnnotation(DeleteMapping.class) != null) {
methodRequest = Arrays.toString(method.getAnnotation(DeleteMapping.class).value());
}
if(method.getAnnotation(GetMapping.class) != null) {
methodRequest = Arrays.toString(method.getAnnotation(GetMapping.class).value());
}
if(method.getAnnotation(PatchMapping.class) != null) {
methodRequest = Arrays.toString(method.getAnnotation(PatchMapping.class).value());
}
if(method.getAnnotation(PostMapping.class) != null) {
methodRequest = Arrays.toString(method.getAnnotation(PostMapping.class).value());
}
return methodRequest;
}
}
注意事项:filterPackage 需要配置自定义注解得目录或者controller目录都可以
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class SwaggerMapContext {
public static final String filterPackage = "com.phm.controller";
private static Map<String, Object> map = new ConcurrentHashMap<>();
public static Map<String, Object> getMap(){
return map;
}
}
示例代码:
@PostMapping("/mark")
@ApiJsonObject(name = "params", value = {
@ApiJsonProperty(type = Integer.class,key = "mobile", example = "18614242538", description = "user mobile"),
@ApiJsonProperty(type = Integer.class,key = "rows", example = "15", description = "行数")
})
@ApiOperation(value = "健康评估")
public Result mark(@RequestBody Map<String, Object> params) {
System.out.println(params.get("rows"));
return Result.success();
}
项目搭建集成步骤省略,参考别的博客。到这里map类型参数就可以正常接收了
总结思路:
1:在spring初始化的时候 通过 MyBeanPostProcessor 收集 注解信息 放到 SwaggerMapContext里
2:MapApiReader 实现了 ParameterBuilderPlugin ,在 apply 里 ,循环遍历 收集到的注解信息 使用 ASM ,动态生成类。也就是 将Map类型参数里的 key 转换成一个类中的 字段。
3:生成类之后,加载这个类,替换 modelRef