Featured image of post 获取Spring中所有web请求接口的path和method

获取Spring中所有web请求接口的path和method

获取Spring中所有web请求接口的path和method

1.概述

最近在开发一个项目的过程中,遇到一个需求,需要去汇总拿到系统中所有的请求接口,然后集中进行一些处理。 我这里大概有两种思路:

  • 众所周知,接口都是定义在Controller层的,我们通过Spring容器拿到那些Controller层的Bean的class,然后通过反射拿到所有的该类方法,然后依次获取每个方法的注解(主要是获取那几个和RequestMapping相关注解),这种方案我写到一半,觉得太过于繁琐了,这里需要考虑的情况有很多,

    1. 可以使用@RestController()@Controller标注控制层。
    2. Controller类上面也可以有@RequestMapping注解,
    3. @RequestMapping注解的变形又还有5种,GetMapping, PostMapping, PutMapping, DeleteMapping, PatchMapping
    4. Mapping注解又有两个属性可用于定义path,默认值value和path,而且均是数组的形式。
    5. 路径参数上/的兼容也很难处理,有些开发者开头不写/,有些又要写这玩意儿。

    我写着写着,代码越来越复杂,有点像屎山了。就想到了另外一种方案。

  • 使用spring开发web应用的时候,要能够处理请求映射关系是肯定会提前收集好对应的路径和handler之间的映射关系的,然后我就从DispatcherServlet的doDispatch方法开始调试代码,很明显会在里面的 mappedHandler = getHandler(processedRequest);方法调用中获取到handler。调试进入,

image-20240718000302554

可以看到就是在遍历handlerMappings

image-20240718000915407

去查了一下这几个handlerMapping的资料。

  1. RequestMappingHandlerMapping
    • 这是Spring MVC中用于处理基于注解的请求映射的核心组件。
    • 它主要处理带有 @RequestMapping@GetMapping@PostMapping 等注解的方法。
    • 通过这些注解,你可以将HTTP请求映射到特定的方法上,从而处理不同的请求。
  2. BeanNameUrlHandlerMapping
    • 这是一个简单的URL到处理器映射策略,它将URL路径映射到Bean的名称。
    • 通常不推荐使用,因为它依赖于Bean的名称,而不是更灵活的路径表达式或注解。
  3. RouterFunctionMapping
    • 这是Spring WebFlux的一部分,用于处理基于函数式编程的路由。
    • 它允许你使用函数式编程风格来定义路由,而不是传统的Controller方法。
    • 这种方式更适用于响应式编程模型,例如使用 RouterFunctions 定义路由。
  4. SimpleUrlHandlerMapping
    • 这是一个简单的URL到处理器映射器,它允许你通过代码配置URL到处理器的映射。
    • 它不依赖于注解,而是通过在Spring配置中显式定义URL和处理器的映射关系。
  5. WelcomePageHandlerMapping
    • 这个映射器用于处理欢迎页面的请求。
    • 当一个目录请求被发送到服务器时(例如访问根目录 /),它会被映射到欢迎页面。
    • 通常用于定义默认页面或目录索引页面。

不难看出来,满足我们需求的就是RequestMappingHandlerMapping,调试的时候仔细看了一下,确实找到了路径到handler的映射

image-20240718001915161

之后就是从spring容器中获取这个bean,然后进行各种路径收集等操作。

2.具体代码实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.ruayou.client.manager;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @Author:ruayou
 * @Date:2024/7/18 0:22
 * @Filename:RequestPathFinder
 */
public class RequestPathFinder implements ApplicationContextAware {

    ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public Set<String> getAllRequestPath() {
        HashSet<String> set = new HashSet<>();
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        for (Map.Entry<RequestMappingInfo, HandlerMethod> requestMappingInfoHandlerMethodEntry : mapping.getHandlerMethods().entrySet()) {
            RequestMappingInfo key = requestMappingInfoHandlerMethodEntry.getKey();
            Set<String> patternValues = key.getPatternValues();
            RequestMethodsRequestCondition methods = key.getMethodsCondition();
            patternValues.forEach((pa)->{
                if (!methods.isEmpty()) {
                    methods.getMethods().forEach((m)->{
                        set.add(m.name()+" "+pa);
                    });
                }
            });
        }
        set.forEach(s -> System.out.println(s)
        );
        return set;
    }
}

image-20240718103642575

方法的运行结果如上图,可以正确获取到自己定义的接口(必须是通过注解定义的才能获取到!)