Use the Plugin mechanism of Swagger's extension component to customize the generation of API documents

brief history

Let's take a look at the relationship between springfox and swagger.

swagger is a popular API development framework. Based on the "open API specification" (OAS), this framework provides corresponding solutions for the whole API development cycle. It is a very huge project (including design, coding and testing, and supports almost all languages).

OAS itself is an API specification, which is used to describe a complete set of API interfaces, including whether an interface is a GET or POST request, and which parameters and header s are included in this file. It is usually designed in YAML format, which is more convenient to write, and most of it will be in the form of json when transmitted in the network, because json has strong universality.

Due to the popularity of Spring, Marty Pitt wrote a Spring based component, swagger springmvc, to integrate swagger into springmvc. Springfox is developed from this component. At the same time, springfox is also a new project. This paper still uses one of the components, springfox-swagger2.

pringfox-swagger2 still relies on OSA specification documents, that is, a json file describing API, and the function of this component is to help us automatically generate this json file. Another component we will use, springfox swagger UI, parses this json file and presents it in a more friendly way.

SpringFox

Github: https://github.com/springfox/springfox

Automated JSON API documentation for API's built with Spring.

Getting Started

For new projects

For Maven

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

For Gradle

implementation "io.springfox:springfox-boot-starter:<version>"

Scalable components of Swagger

https://github.com/springfox/springfox/tree/master/springfox-spi/src/main/java/springfox/documentation/spi/service

In the source code( https://github.com/springfox/springfox ), you can see some interface files at the end of Plugin as shown in the figure below. We are going to work on these.

To customize the extension function, you only need to implement the apply method in an xxxPlugin interface. In the apply method, we manually scan our custom annotations, and then add the logic of the relevant implementation.

Code example:

/**
 * User defined annotation for parameter passing value
 * @author zhenghui
 * @date 2020 September 13, 2013 13:25:18
 * @desc Read the custom properties and generate the model dynamically
 */
@Configuration
@Order(-19999)   //plugin loading order: the default is the last loading
public class SwaggerModelReader implements ParameterBuilderPlugin {

    @Autowired
    private TypeResolver typeResolver;

    static final Map<String,String> MAPS = new HashMap<>();
    static {
        MAPS.put("byte","java.lang.Byte");
        MAPS.put("short","java.lang.Short");
        MAPS.put("integer","java.lang.Integer");
        MAPS.put("long","java.lang.Long");
        MAPS.put("float","java.lang.Float");
        MAPS.put("double","java.lang.Double");
        MAPS.put("char","java.lang.Character");
        MAPS.put("string","java.lang.String");
        MAPS.put("boolean","java.lang.Boolean");
    }

    //Get the class location of the package where the type is located according to the user-defined type
    static public String getTypePath(String key){
        return key==null || !MAPS.containsKey(key.toLowerCase()) ? null :  MAPS.get(key.toLowerCase());
    }


    @Override
    public void apply(ParameterContext context) {
        ResolvedMethodParameter methodParameter = context.resolvedMethodParameter();

        //Custom annotations
        Optional<ApiIgp> apiIgp = methodParameter.findAnnotation(ApiIgp.class);
        Optional<Apicp> apicp = methodParameter.findAnnotation(Apicp.class);



        if (apiIgp.isPresent() || apicp.isPresent()) {
            Class originClass = null;
            String[] properties = null; //Parameters passed by annotation
            Integer annoType = 0;//Type of annotation
            String name = null + "Model" + 1;  //model name / / parameter name

            String[] noValues = null;
            String[] noValueTypes = null;
            String[] noVlaueExplains = null;
            //Get the parameters passed by the custom annotation
            if (apiIgp.isPresent()){
                properties = apiIgp.get().values(); //Excluded
                originClass = apiIgp.get().classPath();//class of the original object
                name = apiIgp.get().modelName() ;  //model name / / parameter name

                noValues = apiIgp.get().noValues();
                noValueTypes = apiIgp.get().noValueTypes();
                noVlaueExplains = apiIgp.get().noVlaueExplains();

            }else {
                properties = apicp.get().values(); //Needed
                annoType = 1;
                originClass = apicp.get().classPath();//class of the original object
                name = apicp.get().modelName() ;//Name of custom class
                noValues = apicp.get().noValues();
                noValueTypes = apicp.get().noValueTypes();
                noVlaueExplains = apicp.get().noVlaueExplains();
            }

            //Generate a new class
            Class newClass = createRefModelIgp(properties, noValues, noValueTypes, noVlaueExplains, name, originClass, annoType);


            context.getDocumentationContext()
                    .getAdditionalModels()
                    .add(typeResolver.resolve(newClass));  //Add our newly generated Class to the Models of documentContext


            context.parameterBuilder()  //Modify the ModelRef of the model parameter to dynamically generate the class for us
                    .parameterType("body")
                    .modelRef(new ModelRef(name))
                    .name(name);

        }


    }

    /**
     *
     * @param properties annoType=1:Expected annoType=0: excluded
     * @param noValues
     * @param noValueTypes
     * @param noVlaueExplains
     * @param name The name of the mode created
     * @param origin
     * @param annoType Type of annotation
     * @return
     */
    private Class createRefModelIgp(String[] properties, String[] noValues, String[] noValueTypes, String[] noVlaueExplains, String name, Class origin, Integer annoType) {
        try {
            //Gets all variables in the original entity class
            Field[] fields = origin.getDeclaredFields();
            //Convert to a List collection to facilitate stream filtering
            List<Field> fieldList = Arrays.asList(fields);
            //The passed in parameters are also converted to List
            List<String> dealProperties = Arrays.asList(properties);//Remove spaces and separate with commas
            //Filter out existing
            List<Field> dealFileds = fieldList
                    .stream()
                    .filter(s ->
                            annoType==0 ? (!(dealProperties.contains(s.getName()))) //If the annotation type is 0, it indicates that it should be reversed
                                        : dealProperties.contains(s.getName())
                            ).collect(Collectors.toList());

            //Store variables that do not exist
            List<String> noDealFileds = Arrays.asList(noValues);
            List<String> noDealFiledTypes = Arrays.asList(noValueTypes);
            List<String> noDealFiledExplains = Arrays.asList(noVlaueExplains);


            //Create a class
            ClassPool pool = ClassPool.getDefault();
            CtClass ctClass = pool.makeClass(origin.getPackage().getName()+"."+name);

            //Create the object and add the existing variables
            createCtFileds(dealFileds,noDealFileds,noDealFiledTypes,noDealFiledExplains,ctClass,annoType);

            //Returns the final class
            return ctClass.toClass();

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    /**
     * Dynamically generate JavaBean with Swagger annotation according to the value in properties
     *
     * @param dealFileds  The name of an object property that already exists in the original object
     * @param noDealFileds  Object property name that does not exist in the original object
     * @param noDealFiledTypes The types of object attributes that do not exist in the original object, including eight basic types, such as dounle and String
     * @param noDealFiledExplains  Parameter description of custom variable
     * @param ctClass Source class
     * @throws CannotCompileException
     * @throws NotFoundException
     * @throws ClassNotFoundException
     */
    public void createCtFileds(List<Field> dealFileds, List<String> noDealFileds, List<String> noDealFiledTypes,List<String> noDealFiledExplains, CtClass ctClass, Integer annoType) {
       //Add variables existing in the original entity class
//        if(annoType==1)
        for (Field field : dealFileds) {
            CtField ctField = null;
            try {
                ctField = new CtField(ClassPool.getDefault().get(field.getType().getName()), field.getName(), ctClass);
            } catch (CannotCompileException e) {
                System.out.println("1 not found:"+e.getMessage());
            } catch (NotFoundException e) {
                System.out.println("2 not found:"+e.getMessage());
            }
            ctField.setModifiers(Modifier.PUBLIC);
            ApiModelProperty annotation = field.getAnnotation(ApiModelProperty.class);
            String apiModelPropertyValue = java.util.Optional.ofNullable(annotation).map(s -> s.value()).orElse("");



            if (StringUtils.isNotBlank(apiModelPropertyValue)) { //Add model attribute description
                ConstPool constPool = ctClass.getClassFile().getConstPool();

                AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
                ann.addMemberValue("value", new StringMemberValue(apiModelPropertyValue,constPool));
                attr.addAnnotation(ann);

                ctField.getFieldInfo().addAttribute(attr);
            }
            try {
                ctClass.addField(ctField);
            } catch (CannotCompileException e) {
                System.out.println("Cannot add field 1:"+e.getMessage());
            }
        }

        //Add a variable that does not exist in the original entity class
         for (int i = 0; i < noDealFileds.size(); i++) {
            String valueName = noDealFileds.get(i);//Variable name
            String valueType = noDealFiledTypes.get(i);//Type of variable
            valueType=getTypePath(valueType);

            //Depending on the type of variable, the name of the variable, and the class in which the variable will be created
             CtField ctField = null;
             try {
                 ctField = new CtField(ClassPool.getDefault().get(valueType), valueName, ctClass);
             } catch (CannotCompileException e) {
                 System.out.println("3 not found:"+e.getMessage());
             } catch (NotFoundException e) {
                 System.out.println("Could not find 4:"+e.getMessage());
             }
             ctField.setModifiers(Modifier.PUBLIC);//Set whether the permission range is private or public

             if(noDealFiledExplains.size()!=0){
                 //Parameter setting description
                 String apiModelPropertyValue = (apiModelPropertyValue=noDealFiledExplains.get(i))==null?"No description":apiModelPropertyValue;//Parameter description

                 System.out.println(apiModelPropertyValue);

                 if (StringUtils.isNotBlank(apiModelPropertyValue)) { //Add model attribute description
                     ConstPool constPool = ctClass.getClassFile().getConstPool();
                     AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                     Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
                     ann.addMemberValue("value", new StringMemberValue(apiModelPropertyValue,constPool));
                     attr.addAnnotation(ann);

                     ctField.getFieldInfo().addAttribute(attr);
                 }

             }

             //Add this variable to the class
             try {
                 ctClass.addField(ctField);
             } catch (CannotCompileException e) {
                 System.out.println("Cannot add field 2:"+e.getMessage());
             }

         }

    }
}

Swagger common notes

@Api

Used on a class to illustrate the function of the class

@Api(value = "UserController", description = "User related api")

@ApiOperation

Used in the method to explain the function of the method

@ApiOperation(value = "Find users", notes = "Find users", httpMethod = "GET", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)

@ApiImplicitParams

Used to include a set of parameter descriptions on a method

@ApiImplicitParam

Used in the @ ApiImplicitParams annotation to specify various aspects of a request parameter paramType: where is the parameter placed

header–>Acquisition of request parameters:@RequestHeader
query–>Acquisition of request parameters:@RequestParam
path(be used for restful Interface)–>Acquisition of request parameters:@PathVariable
body((not commonly used)
form((not commonly used)

name: Parameter name
dataType: Parameter type
required: Whether the parameter must be passed
value: Meaning of parameters
defaultValue: Default values for parameters
@ApiImplicitParams({
        @ApiImplicitParam(name = "id", value = "only id", required = true, dataType = "Long", paramType = "path"),
})

@ApiResponses

Used to represent a set of responses

@ApiResponse

Used in @ ApiResponses, it is generally used to express an error response message code: number, e.g. 400 message: information, such as "request parameters are not filled in" response: the class that threw the exception

@ApiResponses(value = {
        @ApiResponse(code = 400, message = "No Name Provided")  
})

@ApiModel

Swagger-core builds the model definitions based on the references to them throughout the API introspection.

The @ApiModel allows you to manipulate the meta data of a model from a simple description or name change to a definition of polymorphism.

Describe the information of a Model (this is generally used in the scenario of using @ RequestBody when creating a post, and the request parameters cannot be described with @ ApiImplicitParam annotation)

@ApiModel(value = "User entity class")

@ApiModelProperty

Describes the properties of a model

@ApiModelProperty(value = "log on user")
@ApiIgnore //Use this annotation to ignore this interface

reference material

https://blog.csdn.net/qq_17623363/article/details/109259315 https://blog.csdn.net/wsh900221/article/details/80508548

Added by php_gromnie on Thu, 16 Dec 2021 05:41:40 +0200