Tianqiong-Api interface automation management series 2: MiApi-multi-protocol interface scanner detailed explanation

Open source address

https://github.com/XiaoMi/mone/tree/master/miapi-all/mi-api Welcome friends who are interested in cloud native technology/R&D efficiency to join (Fork, Star) us.

Overview

In the previous article's overall introduction to the platform, we introduced the core capabilities and processes provided by the platform, and roughly described the main logic for obtaining and generating interface data information in the loading and generation of service interface information . This article will go deep into the source code level to introduce how we implement the scanning, parsing and generation of interface document data.

Business dependency package

In the previous article, we mentioned the key components/modules for obtaining business project data, namely annotations, scanners, and caches .

In order to obtain the basic data of the business project interface we need above, we need the business side to introduce a set of dependency packages provided by us. We provide targeted dependency packages for services of different protocol interfaces, such as providing Http interface and Dubbo interface. For the projects, we have designed and provided dependency packages adapted to Http and Dubbo respectively. These packages will be used for parsing data of different protocol interfaces, data push, service registration, etc.

Annotation definition

effect

Annotations are used to make some specific marks in the project, such as module layer marks, interface layer marks, field layer marks, etc., for subsequent scanner scanning and parsing process.

accomplish

At the beginning of the design, we first made it clear what data we needed based on the interface delivery process we defined. We hope that after the business project is running, R&D personnel can search for information about their own project modules in the platform, and load and generate corresponding interface data based on the selected module information. We hope that the modules here are classes that developers are used to and clearly defined in the code.

For example, for an Http interface, based on the traditional mvc architecture, usually the interface entry will be implemented under the xxxController class, for example:

@RestController
@RequestMapping()
public class HelloController {

@RequestMapping(path = "/hello",method = RequestMethod.POST)
public Response<String> getToken() {
    Response<String> r = new Response<>();
    r.setData("hello");
    return r;
}
}

Then we hope that users can directly search for the keyword HelloController in the search box to see all module and interface information under the project, and select to load and generate documents accordingly.

For a Dubbo interface, it is as follows:

public interface DubboHealthService {
    Result<Health> health2(AaReq aaReq);
}


@DubboService(timeout = 1000, group = "staging",version = "1.0")
@Slf4j
public class DubboHealthServiceImpl implements DubboHealthService {

    @Override
    public Result<Health> health(AaReq aaReq) {
        Health health = new Health();
        health.setVersion("1.0");
        return Result.success(health);
    }

We hope that the searchable module is an interface definition, namely DubboHealthService. Users only need to search for the interface to obtain a list of all interfaces and their methods under the project, and filter and load to generate documents based on these two.

In summary, for the interface data we need to obtain, we provide several basic annotations in the business dependency package: @EnableApiDocs, @ApiModule, @ApiDoc, @ParamDefine. (For different protocol interfaces, the annotation naming is slightly different)

  • @EnableApiDocs

  • @EnableApiDocs is used to start the Bootstrap class and is used as a switch. The user can decide whether to enable the data scanning and push function according to whether to add the switch or not.

@EnableAutoConfiguration
@ComponentScan(basePackages = {"com.xxx.xxx.hello", "com.xxx.xxx"})
@DubboComponentScan(basePackages = "com.xxx.xxx.hello")
@ServletComponentScan
@EnableDubboApiDocspublic class HelloBootstrap {
    private static final Logger logger = LoggerFactory.getLogger(HelloBootstrap.class);

    public static void main(String... args) {
        try {
            SpringApplication.run(HelloBootstrap.class, args);
        } catch (Throwable throwable) {
            logger.error(throwable.getMessage(), throwable);
            System.exit(-1);
        }
    }
}

For example, the above project that provides the dubbo interface only needs to add the @EnableDubboApiDocs annotation to the startup class to enable this function.

The implementation of this startup class annotation is also very simple:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Inherited
@Import({DubboApiDocsAnnotationScanner.class})
public @interface EnableDubboApiDocs {
}

In fact, we just @Import the scanner class in the dependency package in this annotation. Then as long as we add this annotation, spring will help us initialize the scanner class into the container, and subsequent actions will be executed by the scanner.

  • @ApiModule

  • This annotation is used to annotate module classes, and its implementation is as follows:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface ApiModule {

    /**
     * 用于自定义模块名
     */    String value();

    /**
    * 用于定位模块类的类型
     * dubbo api interface class
    * 若为http接口,则该选项为 apiController
     */    Class<?> apiInterface();

}

This annotation will be used to filter class information that needs to be parsed from the spring container during the scanner's scanning process, and also provide basic information about these classes.

The usual usage of the Http interface is as follows:

@RestController
@RequestMapping()
@HttpApiModule(value = "这是一个controller类HelloController", apiController = HelloController.class)
public class HelloController {
}

The usual usage of the Dubbo interface is as follows:

@DubboService(timeout = 1000, group = "staging",version = "1.0")
@Slf4j
@ApiModule(value = "健康检测服务", apiInterface = DubboHealthService.class)public class DubboHealthServiceImpl implements DubboHealthService {
}
  • @ApiDoc

  • This annotation is used to mark the specific interface that needs to generate documents, and is implemented as follows:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface ApiDoc {

    /**
     * api name.用于自定义接口名
     */    String value();

    /**
     * api description.自定义接口描述文档
     */    String description() default "";

}

Common usage is as follows:

@Override
@ApiDoc(value = "健康监测方法",description = "这是一个用于健康监测的方法")
public Result<Health> health(AaReq aaReq) {
    Health health = new Health();
    health.setVersion("1.0");
    return Result.success(health);
}
  • @ParamDefine

  • This annotation is used to define specific parameter fields and is implemented as follows:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Documented
@Inherited
public @interface ParamDefine {

    /**
     * 参数名
     */String value();

    /**
     * 该参数是否必填
     */boolean required() default false;

    /**
     * 是否忽略该字段,若是,在生成文档时该字段将被忽略
     */boolean ignore() default false;
    
    /**
     * 字段说明描述
     */
    String description() default "";


    /**
     * 默认值,若有默认值,将以该值生成mock数据
     */String defaultValue() default "";

}

Common usage is as follows:

@Data
public class AaReq implements Serializable {
    @ApiDocClassDefine(value = "用户名a",required = true,description = "这里是用户名参数",defaultValue = "dongzhenixng")
    private String username;

    @ApiDocClassDefine(value ="年龄",required = false,description = "用户年龄",defaultValue = "23")
    private Integer age;
    
   /**
* 也可以不使用该字段,平台将默认提取该字段基本信息,如参数名、类型等
     */    private double age21;

}

scanner

effect

The scanner is the core of this project. Based on the reflection capability of jdk, we obtain the project's service and interface information data at runtime.

accomplish

The scanner is imported into the spring container by the @EnableApiDocs switch. Since we need to use the bean data after initialization of the spring container as the parsing target, the scanning and parsing action must occur after spring completes the basic initialization operation. Therefore, spring's open ApplicationListener interface is implemented here . , this interface can receive a series of events triggered by spring projects. Here we receive the ApplicationReadyEvent, which is triggered after the spring framework initializes and completes the basic information of the project.

public class ApiDocsScanner implements ApplicationListener<ApplicationReadyEvent> {

@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
    //扫描解析逻辑......
}
}

After spring initialization is completed, the bean information of the project is maintained in the spring context ApplicationContext. Then the module collection marked with @ApiModule can be obtained from the scanner, and then a certain piece of class information marked with the annotation can be obtained one by one through reflection. , construct the module data structure, then obtain the method list under each module through reflection, filter the methods with @ApiDoc annotation, construct the data structure of the interface method layer, and then further process the methods marked with the annotation.

In method-level processing, reflection is also used to obtain specific parameter field information. Here, the field-level data structure is recursively constructed according to the field type. Of course, the parsing operation here is more complicated and cumbersome. We need to differentiate and perform targeted parsing for different parameter types. For example, how to deal with basic type parameters? How to deal with objects, Lists, Maps, Sets, Queues and even nested generic parameters? Which types cannot be looped and recursed? Which ones need to be ignored... We have done a lot of consideration and compatibility with these details during the development and testing process. We will not introduce them in detail here. Interested friends can look at our open source code.

After completing the data scanning and analysis of the above steps, aggregate all data according to certain rules, then obtain the local IP and port used when the project is running, call the open interface provided by the platform, and push the above data to the platform in a unified manner, and the platform will use ip:port is a unique index to store data in the platform database. The reason why ip:port is used as the only index here is that general microservice business projects, whether they are in the development process or released to the test environment or production environment, will most likely have multiple instances, that is, the same project will be used by multiple parties, Running in multiple places, the code versions and development progress of different instances may not be completely consistent. Therefore, we hope that users can select a specified instance on the platform and load and generate the interface data of that instance in a targeted manner.

Scanner execution flow chart

Guess you like

Origin blog.csdn.net/shanwenbang/article/details/128871851