如何在spring boot中获取所有RequestMapping的URL路径列表集?

本文介绍了一种在Spring MVC中获取所有URL到Controller Method映射的方法,通过使用Spring框架提供的API来实现对请求映射信息的获取。

本文是“著”模式。但是本文写完之后,我直接搜索了一下结论的核心,发现好些达人都已经实现了,无知好尴尬……

 

为什么要做这事儿?

自动化、可视化、授权……总之,谁用谁知道

 

如何在Controller类的方法内获取到系统内已存的所有RequestMapping的路径列表集?

简单来说:在带有@Controller注解的类中,添加以下的代码:

@Autowired
ApplicationContext applicationContext;
public void getAllRequestMappingInfo() {
    AbstractHandlerMethodMapping<RequestMappingInfo> objHandlerMethodMapping = (AbstractHandlerMethodMapping<RequestMappingInfo>)application.getBean(“requestMappingHandlerMapping”);
    Map<RequestMappingInfo, HandlerMethod> mapRet = objHandlerMethodMapping.getHandlerMethods();
}

实现过程,没仔细看书,只能蛮力调试了。

本文基于的主要库版本:

spring-boot:1.5.2.RELEASE

*spring-xx:4.3.7.RELEASE

*:spring-xx中的xx包含core、context、beans等

 

以下是调试流程:

1、在项目的App.main方法(App类有@SpringBootApplication注解)中,使用SpringApplication.run方法,最终会创建一个SpringApplication类的对象,并调用其run(String…args)方法;

2、在run方法中,使用SpringApplication.createApplicationContext()方法,创建了ApplicationContext对象,其具体类型由SpringApplication对象属性的webEnvironment布尔值所决定,由于后者的值为true,因此ApplicationContext对象被初始化为org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext类型;run方法内将创建的对象临时赋值给了方法体内定义的context(ConfigurableApplicationContext类型)局部变量;再具体的细节这里不表;

3、run方法内,继续到this.refreshContext(context),在后者内调用refresh方法;

4、refresh最终会调用org.springframework.context.support.AbstractApplicationContext类的refresh方法;

5、在AbstractApplicationContext.refresh方法内,调用finishBeanFactoryInitialization方法对GenericApplicationContext.beanFactory成员变量进行初始化(GenericApplicationContext类是直接从AbstractApplicationContext类派生出来的,后者并没有定义beanFactory成员变量,但是定义了一个抽象方法getBeanFactory,其实获取的就是前者的成员变量,因为前者不仅定义了成员变量,而且也实现了这个抽象方法),这个beanFactory成员变量在GenericApplicationContext类的构造函数里被创建,其类型为DefaultListableBeanFactory;

6、再回到finishBeanFactoryInitialization方法内,beanFactory会调用DefaultListableBeanFactory.preInstantiateSingletons方法,暂停一下,我们下面说一个beanFactory;

6.1、在第2步的run方法内使用createApplicationContext方法创建上下文时,会通过org.springframework.beans.BeanUtils类,最终在org.springframework.context.annotation.AnnotationConfigUtils类内的registerAnnotationsConfigProcessors方法中,对beanFactory进行一些初始化。beanFactory对象的beanDefinitionNames成员变量(ArrayList)保存着系统内的bean名称(这里,我觉得使用GenericApplicationContext.getDefaultListableBeanFactory()方法可以获取到beanFactory,然后进行获取到bean名称集)

6.2、接上面,在AnnotationConfigUtils.registerAnnotationsConfigProcessors方法内,会对beanFactory对象实际添加几个系统内最初的bean,将其名称保存在DefaultListableBeanFactory的beanDefinitionNames成员变量内,将<bean名称,对象>保存在beanDefinitionMap成员变量内。整个系统内的第一个bean名称是org.springframework.context.annotation.internalConfigurationAnnotationProcessor,其值为org.springframework.beans.factory.support.RootBeanDefinition类型的对象,且后者传进去的参数为org.springframework.context.annotation.ConfigurationClassPostProcessor类型。

6.3、whatever,最终在DefaultListableBeanFactory.registerBeanDefinition方法内,对bean进行添加。总之,在AnnotationConfigUtils.registerAnnotationsConfigProcessors方法内,会最终人为的添加6个bean:

名称

对象类型

org.springframework.context.annotation.internalConfigurationAnnotationProcessor

org.springframework.beans.factory.support.RootBeanDefinition类型,参数为org.springframework.context.annotation.ConfigurationClassPostProcessor

org.springframework.context.annotation.internalAutowiredAnnotationProcessor

org.springframework.beans.factory.support.RootBeanDefinition类型,参数为org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor

org.springframework.context.annotation.internalRequiredAnnotationProcessor

org.springframework.beans.factory.support.RootBeanDefinition类型,参数为org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor

org.springframework.context.annotation.internalCommonAnnotationProcessor

org.springframework.beans.factory.support.RootBeanDefinition类型,参数为org.springframework.beans.factory.annotation.CommonAnnotationBeanPostProcessor

org.springframework.context.annotation.internalPersistenceAnnotationProcessor

org.springframework.beans.factory.support.RootBeanDefinition类型,参数为org.springframework.beans.factory.annotation.PersistenceAnnotationBeanPostProcessor

org.springframework.context.event.internalEventListenerProcessor

org.springframework.beans.factory.support.RootBeanDefinition类型,参数为org.springframework.context.event.EventListenterMethodProcessor

org.springframework.context.event.internalEventListenerFactory

org.springframework.beans.factory.support.RootBeanDefinition类型,参数为org.springframework.context.event.DefaultEventListenerFactory

 

7、回到AbstractApplicationContext.refresh方法内,在其中的this.invokeBeanFactoryPostProcessors步骤,会扫描获取到系统内自定义的所有的bean名称,并将其装入到beanFactory对象内。

8、在AbstractApplicationContext.finishBeanFactoryInitialization方法内,beanFactory对象使用preInstantiateSingletons方法,对单件bean对象进行预初始化;

9、单件的bean对象很多,比如requestMappingHandlerMapping就是其中之一,还有error、beanNameHandlerMapping等等;

10、这里我们主要看名称为requestMappingHandlerMapping的bean,这个名称的bean最终通过一系列手段会对应org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping类型的对象;

11、RequestMappingHandlerMapping类对象在创建后,会调用bean的一个约定方法(它本身改写了)afterPropertiesSet(),然后在其中又调用了它的父类AbstractHandlerMethodMapping的afterPropertiesSet()方法,后者内调用了initHandlerMethods()方法;

11.1、在AbstractHandlerMethodMapping.initHandlerMethods方法内,首先会获取到系统内所有的bean names,然后一一枚举这些bean,主要是为了判断bean对象的类上有没有@Controller注解,或者方法上是不是有@RequestMapping注解。这个实现是通过这种方式达成的:RequestMappingHandlerMapping类实现了AbstractHandlerMethodMapping定义的抽象方法isHandler(),在前者的实体内,

12、在AbstractHandlerMethodMapping.initHandlerMethods方法内,会通过detectHandlerMethods()方法->registerHandlerMethods()方法,最终调用内部类AbstractHandlerMethodMapping.MappingRegistry的register方法,将@RequestMapping注解后的mapping字符串、Controller类对象、Mehod对象三者存放在MappingRegistry类对象的成员变量mappingLookup这个Map<T,HandlerMethod>类型的映身内,后者是一个LinkedHashMap类型的映射;

13、MappingRegistry类提供了getMapping()方法返回mappingLookup成员变量的引用;

14、AbstractHandlerMethodMapping类提供了getMappingRegistry()方法,返回MappingRegistry类对象,但是这个方法并不是一个public方法,因此只能在org.springframework.web.servlet.handler包内被使用了。

15、RequestMappingHandlerMapping类从AbstractHandlerMethodMapping类派生而来,通过getBean()方法,能够获取到其对象,但由于14步的影响,还是无法获取到@RequestMapping的路径;

16、另外一种方法,AbstractHandlerMethodMapping类内提供了一个公共的方法getHandlerMethods(),这个方法返回MappingRegistry类的mappingLookup成员变量的不可更改视图;AbstractHandlerMethodMapping类还提供了其他几个有用的方法,暂时略过;

17、ApplicationContext可使用ApplicationObjectSupport.getApplicationContext()方法获取;所有的bean names可通过org.springframework.beans.factory.ListableBeanFactory.getBeansNamesForType()方法获取到(此方法真正在org.springframework.beans.factory.support.DefaultListableBeanFactory类内被实现;

 

 

其他有用的方法:

1、如何通过bean名称获取bean的实际类型?

使用org.springframework.beans.factory.BeanFactory接口提供的getType方法(此方法在ApplicationContext被实现了)

 

最后,其实,在使用springboot调试时,控制台的输出信息里,已经打印出了所有映射路径及对应的处理方法信息,比本文最开始的方法打印的更全(本文仅做了RequestMappingHandlerMapping)

 

 

事后发现的一些达人的文章:

SpringMVC项目中获取所有URL到Controller Method的映射:http://www.cnblogs.com/yuananyun/archive/2014/08/25/3934371.html

Spring 反射得到所有controller与method:http://183615215-qq-com.iteye.com/blog/1866281

 

 


<think> 我们正在讨论Spring Boot中如何设计接口接收URL,并根据URL路径调用不同的服务。用户进一步询问了关于动态URL参数解析的方法。 根据引用[1],Spring Boot能够自动解析URL地址中的参数并注入到方法参数中。但这里用户关注的是“动态URL参数解析”,结合上下文,我认为用户可能指的是: 1. 如何从请求URL中提取变量路径(即路径参数),如RESTful风格中的`/users/{userId}`。 2. 如何将这些路径参数传递给服务方法。 在Spring Boot中,动态URL参数解析主要依赖`@PathVariable`注解,同时也可以结合`@RequestParam`处理查询参数。 以下详细说明: ### 一、基础方法:使用`@PathVariable` ```java @RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{userId}") public ResponseEntity<User> getUserById(@PathVariable String userId) { // 根据userId查询用户 User user = userService.findUserById(userId); return ResponseEntity.ok(user); } @GetMapping("/{userId}/orders/{orderId}") public ResponseEntity<Order> getOrder( @PathVariable String userId, @PathVariable String orderId) { // 根据userId和orderId查询订单 Order order = orderService.findOrder(userId, orderId); return ResponseEntity.ok(order); } } ``` ### 二、可选路径参数(Spring Boot 2.3+) 从Spring Boot 2.3开始,支持在`@PathVariable`中设置`required=false`,但要注意路径模式必须允许可选(通常用两个版本): ```java @GetMapping({"/profiles/{name}", "/profiles"}) public String getProfile(@PathVariable(required = false) String name) { return name != null ? "Profile: " + name : "Default Profile"; } ``` ### 三、高级解析:正则表达式匹配 可以在路径变量中使用正则表达式约束: ```java @GetMapping("/products/{code:[A-Z]{3}\\d{3}}") public Product getProduct(@PathVariable String code) { // code必须匹配3个大写字母+3个数字 return productService.findByCode(code); } ``` ### 四、多级路径参数(通配符) 使用`/**`捕获多级路径: ```java @GetMapping("/files/{*path}") public String handleFile(@PathVariable String path) { // 例如请求/files/images/logo.png, 则path为"images/logo.png" return "Requested file: " + path; } ``` ### 五、自定义参数解析器(引用[2]) 当需要统一处理某些参数(如Token)时,可实现`HandlerMethodArgumentResolver`: ```java public class TokenArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(Token.class); } @Override public Object resolveArgument(...) { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); return request.getHeader("Authorization"); // 实际根据需求解析 } } // 注册解析器 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new TokenArgumentResolver()); } } // 使用自定义注解 @GetMapping("/secure/data") public String secureData(@Token String token) { // 使用token进行验证 return "Data protected by token: " + token; } ``` ### 六、URL路径解析原理(引用[3]) Spring MVC通过`UrlPathHelper`解析请求路径,其核心方法`getPathWithinApplication`用于获取相对于上下文路径路径。我们可以通过配置自定义路径解析行为: ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper helper = new UrlPathHelper(); helper.setRemoveSemicolonContent(false); // 保留分号内容 configurer.setUrlPathHelper(helper); } } ``` ### 七、常见问题解决 1. **路径冲突问题**: - 精确路径优先于通配路径 - 更长的路径模式优先 2. **参数类型转换**: - 默认支持基本类型转换(如String转int) - 自定义转换器:实现`Converter<String, T>`接口并注册 3. **URL编码问题**: - 默认自动解码URL,可通过配置`server.servlet.encoding.charset=UTF-8`设置编码 ### 示例:综合使用路径参数和查询参数 ```java @GetMapping("/search/{category}") public List<Product> search( @PathVariable String category, @RequestParam(required = false, defaultValue = "0") int minPrice, @RequestParam(required = false, defaultValue = "100000") int maxPrice) { return productService.search(category, minPrice, maxPrice); } ``` §§相关问题§§ 1. 如何在Spring Boot中处理URL中的特殊字符? 2. 如何全局处理路径参数的类型转换错误? 3. 在RESTful API设计中,路径参数和查询参数的最佳使用场景分别是什么? 4. 如何实现多层嵌套的路径参数解析(如`/country/{country}/city/{city}`)? 5. 自定义参数解析器与拦截器(Interceptor)在处理请求参数时的区别是什么? 以上方法均经过Spring Boot官方文档验证,可放心使用[^1][^2][^3]。 </think> 在 Spring Boot 中实现动态 URL 参数解析主要有以下几种方法,结合引用内容详细说明如下: ### 一、基础路径参数解析(@PathVariable) ```java @GetMapping("/users/{userId}/orders/{orderId}") public ResponseEntity<?> getOrder( @PathVariable String userId, @PathVariable String orderId) { // 路径参数自动注入:/users/1001/orders/2002 → userId=1001, orderId=2002 Order order = orderService.findOrder(userId, orderId); return ResponseEntity.ok(order); } ``` - **原理**:Spring MVC 自动将 URL 占位符`{xxx}`绑定到同名方法参数 - **特性**: - 支持类型自动转换(String → Long/Integer 等) - 支持正则表达式验证:`@PathVariable @Pattern(regexp="\\d+") String id` - 引用内容验证:符合引用[1]的自动参数解析机制[^1] ### 二、通配符路径解析(** 匹配) ```java @GetMapping("/files/{*filePath}") public ResponseEntity<?> handleFile(@PathVariable String filePath) { // 请求 /files/docs/report.pdf → filePath="docs/report.pdf" return fileService.process(filePath); } ``` - **说明**:`{*var}`语法捕获从声明位置开始的所有路径段 - **应用场景**:文件路径路由、多级目录处理等 ### 三、自定义参数解析器(高级场景) 实现`HandlerMethodArgumentResolver`接口(引用[2]): ```java public class CustomParamResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(CustomParam.class); } @Override public Object resolveArgument(...) { HttpServletRequest request = (HttpServletRequest) context.getNativeRequest(); String token = request.getHeader("X-Token"); // 自定义逻辑 return authService.validate(token); // 返回解析后的对象 } } // 注册解析器 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new CustomParamResolver()); } } // 控制器使用 @GetMapping("/secure/data") public ResponseEntity<?> getSecureData(@CustomParam User user) { return dataService.fetchForUser(user); } ``` - **解析流程**: 1. 检查参数是否有`@CustomParam`注解 2. 通过`resolveArgument`执行自定义解析逻辑 3. 将解析结果注入控制器方法 - 引用内容验证:符合引用[2]的自定义解析器实现方案[^2] ### 四、路径解析底层原理(引用[3]) Spring MVC 使用`UrlPathHelper`处理路径: ```java // 获取应用内路径的核心方法 String path = new UrlPathHelper().getPathWithinApplication(request); ``` - **关键配置属性**: ```properties spring.mvc.pathmatch.use-suffix-pattern=false # 禁用后缀匹配 spring.mvc.pathmatch.matching-strategy=ant_path_matcher # 匹配策略 ``` - **路径处理流程**: 1. 移除上下文路径(contextPath) 2. 标准化路径(移除重复斜杠) 3. 解码 URL 编码字符 4. 移除分号内容(`;jsessionid=xxx`) ### 五、最佳实践建议 1. **RESTful 设计原则**: - 资源层级不超过两级:`/api/v1/users/{userId}/orders` - 避免动词在路径中出现:使用 HTTP 方法区分操作 2. **参数验证组合**: ```java @GetMapping("/products/{id}") public Product getProduct( @PathVariable @Min(1000) Long id, @RequestParam @DateTimeFormat(iso=ISO.DATE) LocalDate date) { // 路径参数+查询参数组合验证 } ``` 3. **全局异常处理**: ```java @ExceptionHandler(MethodArgumentTypeMismatchException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleTypeMismatch() { return new ErrorResponse("ERR-400", "参数类型错误"); } ``` ### 六、性能优化 1. **路径匹配优化**: ```java @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setUseTrailingSlashMatch(false); // 禁用尾斜杠匹配 } } ``` 2. **缓存路由映射**: - Spring Boot 默认缓存@RequestMapping映射关系 - 开发阶段可通过`spring.mvc.log-resolved-exception=true`查看解析过程 ### 应用场景示例 | URL 格式 | 解析方式 | 适用场景 | |---------|---------|---------| | `/api/v1/users/1001` | `@PathVariable` | 资源单体查询 | | `/static/images/**` | `{*path}`通配符 | 静态资源服务 | | `/search;region=CN` | `UrlPathHelper` | 区域化搜索(保留分号内容) |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值