“This is the first day of my participation in the First Challenge 2022. For details: First Challenge 2022”

preface

When we do back-end business development, one of the little features we often need is to get all the interfaces we write and their request information. You can use it for interface testing, or you can develop some custom tools.

In the development process, we usually configure Swagger to test our controller, and we often use postman, curl and other tools to simulate sending requests. However, in many cases, these tools cannot meet all our requirements and need to be customized for development. For example, the team of the author maintains a tool based on all the interface information to facilitate some customized requirements development, daily business maintenance and online problem handling in addition to the daily use of Swagger.

So how to get the required interface information?

Combined with the query information, as well as the author’s own practice, the following three methods are roughly summarized

  1. throughRequestMappingHandlerMapping
  2. throughSwagger
  3. throughSpring Boot Actuator

As a series of articles, this article first introduces the basic methods – through RequestMappingHandlerMapping access

What is theRequestMappingHandlerMapping

Look directly at the source code comments

/**
 * Creates {@link RequestMappingInfo} instances from type and method-level
 * {@link RequestMapping @RequestMapping} annotations in
 * {@link Controller @Controller} classes.
 *
 * @author Arjen Poutsma
 * @author Rossen Stoyanchev
 * @author Sam Brannen
 * @since3.1 * /
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
      implements MatchableHandlerMapping.EmbeddedValueResolverAware {... }Copy the code

As noted in the annotation, this class is used to process @requestmapping at the type and method level of the class annotated by @controller and to create instances of RequestMappingInfo, which is used to create and store all RequestMapping information. It is described below in Reference 2

The RequestMappingHandlerMapping is used to maintain the mapping of the request URI to the handler. Once the handler is obtained, the DispatcherServlet dispatches the request to the appropriate handler adapter, which then invokes the handlerMethod().

Which preserved the path from the request to the corresponding handler mappings, the DispatcherServlet will request to RequestMappingHandlerAdapter to handle, call the corresponding saved handle method

RequestMappingInfo

What is stored in RequestMappingInfo? Look at the source code comments

/**
 * Request mapping information. Encapsulates the following request mapping conditions:
 * <ol>
 * <li>{@link PatternsRequestCondition}
 * <li>{@link RequestMethodsRequestCondition}
 * <li>{@link ParamsRequestCondition}
 * <li>{@link HeadersRequestCondition}
 * <li>{@link ConsumesRequestCondition}
 * <li>{@link ProducesRequestCondition}
 * <li>{@code RequestCondition} (optional, custom request condition)
 * </ol>
 *
 * @author Arjen Poutsma
 * @author Rossen Stoyanchev
 * @since3.1 * /
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {... }Copy the code

It can be seen that it encapsulates the following information

  1. PatternsRequestConditionRequest Path Information
  2. RequestMethodsRequestConditionHTTP Method Information
  3. ParamsRequestConditionQuery or form information
  4. HeadersRequestConditionRequest header information
  5. HeadersRequestConditionContent-type information of the request
  6. ProducesRequestConditionThe requiredAcceptinformation
  7. Other custom request conditions

So a very simple idea can be from the mind, if after for SpringMVC starts, we went to get RequestMappingHandlerMapping object, you can get information to all of the interface

How do you do that? Listen for container start events!

ApplicationListener@EventListener

The ApplicationListener interface is used to implement event listening for the container. It is designed in observer mode and requires the implementation of onApplicationEvent method, where generic E is the container event of interest and other events are filtered

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

   /**
    * Handle an application event.
    * @param event the event to respond to
    */
   void onApplicationEvent(E event);
}
Copy the code

For convenience, you can also implement this using the @EventListener annotation

The specific implementation

Go directly to the code, which hides the content of custom processing

@Slf4j
@Component
public class FrontToolListener implements ApplicationListener<ContextRefreshedEvent> {

    private static final String REQUEST_BEAN_NAME = "requestMappingHandlerMapping";

    // Implement event listening methods
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // Exposure information is required, so the online environment is not scanned
        if{log.info() {log.info("Online environment, do not scan controller");
            return;
        }

        / / determine whether RequestMappingHandlerMapping exists, if there is no do not scan
        ApplicationContext applicationContext = event.getApplicationContext();
        if(! applicationContext.containsBean(REQUEST_BEAN_NAME)) { log.info("{} not found, scan skipped", REQUEST_BEAN_NAME);
            return;
        }

        log.info("{} exists, start scanning controller methods", REQUEST_BEAN_NAME);
        // Get all interface method information
        RequestMappingHandlerMapping requestMapping =
            applicationContext.getBean(REQUEST_BEAN_NAME, RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> infoMap = requestMapping.getHandlerMethods();
        
        // Omit custom processing information. }}Copy the code

Some things to watch out for

  1. The author’s customized tool needs to expose interface information, so scanning is not allowed in the online environment. Otherwise, there are security problems. In principle, scanning is not allowed in the pre-delivery environment, unless you can guarantee that the pre-delivery environment will not be accessed by the outside
  2. It may not exist in some projectsRequestMappingHandlerMapping, the need for manual injection, in what case does not exist the author is still studying, the injection code is as follows
@Slf4j
@EnableWebMvc
@Configuration
public class FrontToolConfig implements WebMvcConfigurer {

    @Bean
    @ConditionalOnMissingBean(name = "requestMappingHandlerMapping")
    public RequestMappingHandlerMapping requestMappingHandlerMapping(a) {
        log.info("Start injection RequestMappingHandlerMapping does not exist,");
        return new RequestMappingHandlerMapping();
    }
Copy the code
  1. ContextRefreshedEventEvents can be fired multiple times, causing our listener function to be executed multiple times, essentially because there are multiple IOC containers that fire container events multiple times. See this article for a solutionImplement a problem where the ApplicationListener event is fired twiceOr add a static bool to indicate that the processing is complete

Reference documentation

  1. Get All Endpoints in Spring Boot | Baeldung
  2. Types of Spring HandlerAdapters | Baeldung
  3. Spring Application Context Events | Baeldung
  4. Implement a problem where the ApplicationListener event is fired twice