“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
- through
RequestMappingHandlerMapping
- through
Swagger
- through
Spring 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
PatternsRequestCondition
Request Path InformationRequestMethodsRequestCondition
HTTP Method InformationParamsRequestCondition
Query or form informationHeadersRequestCondition
Request header informationHeadersRequestCondition
Content-type information of the requestProducesRequestCondition
The requiredAccept
information- 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
- 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
- It may not exist in some projects
RequestMappingHandlerMapping
, 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
ContextRefreshedEvent
Events 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
- Get All Endpoints in Spring Boot | Baeldung
- Types of Spring HandlerAdapters | Baeldung
- Spring Application Context Events | Baeldung
- Implement a problem where the ApplicationListener event is fired twice