Spring常见问题解决 - @PathVariable 解析带 / 的参数值报404

30 篇文章 3 订阅

一. @PathVariable 遇到 / 无法解析

SpringWeb开发过程中,我们往往使用@PathVariable注解来获取URL上指定的值。例如:

@Controller
public class MyController {
    @GetMapping("/hello/{name}")
    @ResponseBody
    public String hello(@PathVariable("name") String name) {
        return Math.random() + "name: " + name;
    }
}

访问对应的路径进行测试:
在这里插入图片描述
可见,程序自动识别到了我们的name变量,并将其值赋值给了对应的变量,然后输出到页面中。但是,假如我们对这个变量添加一个特殊字符,看看效果会怎么样。

1.1 案例复现

第一种:http://localhost:8080/hello/ljj/,结果如下:
在这里插入图片描述

第二种:http://localhost:8080/hello/ljj/hello,结果如下:
在这里插入图片描述

1.2 原理分析

Spring中,对于URL的解析工作,交给AbstractHandlerMethodMapping.lookupHandlerMethod()来完成:

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
	protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
		List<Match> matches = new ArrayList<>();
		// 1.根据URL进行精确匹配
		List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
		if (directPathMatches != null) {
			// 做一个缓存记录
			addMatchingMappings(directPathMatches, matches, request);
		}
		if (matches.isEmpty()) {
			// 遍历所有映射,根据请求来进行模糊匹配
			addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
		}

		if (!matches.isEmpty()) {
			Match bestMatch = matches.get(0);
			// 如果匹配结果有多个,那么可以进行筛选
			if (matches.size() > 1) {
				// ...
			}
			request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
			handleMatch(bestMatch.mapping, lookupPath, request);
			return bestMatch.handlerMethod;
		}
		else {
			// 匹配不上,报错
			return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
		}
	}
}

1.2.1 精准匹配

对应的代码:

List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);

此时我们的lookupPath就是我们的URL,例如:
在这里插入图片描述
我们跟进去这个函数:

@Nullable
public List<T> getMappingsByUrl(String urlPath) {
	return this.urlLookup.get(urlPath);
}

此时this.urlLookup中并没有我们访问的URL路径:
在这里插入图片描述
那么此时精确匹配出来的结果就是null,因此根据代码逻辑,需要在进行一次模糊匹配过程。

1.2.2 模糊匹配

这段逻辑对应代码:

addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);

我们先来看下getMappings()里面的东西是啥:
在这里插入图片描述
首先明确的是,我们的/hello/{name}已经在待匹配的候选列表中了。那么具体是如何匹配的呢?我们跟进代码:

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
	for (T mapping : mappings) {
		T match = getMatchingMapping(mapping, request);
		if (match != null) {
			matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
		}
	}
}

public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
	@Override
	protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
		return info.getMatchingCondition(request);
	}
	
	public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
		RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
		if (methods == null) {
			return null;
		}
		ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
		if (params == null) {
			return null;
		}
		HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
		if (headers == null) {
			return null;
		}
		ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
		if (consumes == null) {
			return null;
		}
		ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
		if (produces == null) {
			return null;
		}
		PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
		if (patterns == null) {
			return null;
		}
		RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
		if (custom == null) {
			return null;
		}

		return new RequestMappingInfo(this.name, patterns,
				methods, params, headers, consumes, produces, custom.getCondition());
	}
}

到这里我们可以看到,具体匹配的时候,会查到当前请求的所有相关信息,例如:请求头、参数、请求类型等。如果有一项不匹配,就直接提前返回null。当然,还会匹配这个请求的Pattern。以我们的案例http://localhost:8080/hello/ljj/为例:
在这里插入图片描述
那么也就是说,最终的匹配肯定看的是下述这段代码是否能够成功匹配到:

PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);

那么如果我们访问http://localhost:8080/hello/ljj/hello,将会在patterns的校验上提前返回:
在这里插入图片描述

那么为何ljj/就可以正常访问呢?我们跟进下getMatchingCondition函数,最终会走到

public class PatternsRequestCondition extends AbstractRequestCondition<PatternsRequestCondition> {
	private String getMatchingPattern(String pattern, String lookupPath) {
		// ..
		if (this.useTrailingSlashMatch) {
			if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {
				return pattern + "/";
			}
		}
		return null;
	}
}

说白了,Spring会尝试在URL的末尾加一个 / 然后在进行匹配,如果能匹配上,在最终返回 Pattern 时就隐式自动上一个加 /

二. 解决

既然Spring中的URL匹配,是根据Pattern来进行的,那么我们可以使用 * 去进行匹配。然后手动获取URL,通过split去进行分割取值。 同时要考虑到前缀重复的情况(即name的值依旧包含了相同的前缀)。

@Controller
public class MyController {
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @GetMapping("/hello/**")
    @ResponseBody
    public String hello(HttpServletRequest request) {
        String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
        String matchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
        String res = antPathMatcher.extractPathWithinPattern(matchPattern, path);
        return Math.random() + ",name: " + res;
    }
}

结果如下:
在这里插入图片描述
当然,最好的解决方案就是在使用URL上进行动态传承的同时,避免参数值带有 / 这样的特殊字符。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
以下是 Spring MVC 常见面试题: 1. 什么是 Spring MVC? Spring MVC 是一个基于 Spring 框架的 MVC 框架,它是一个轻量级的、模块化的、可扩展的 MVC 框架,处理请求和响应的过程中使用了分派器(DispatcherServlet)、控制器(Controller)、视图(View)等组件。 2. Spring MVC 的核心组件有哪些? Spring MVC 的核心组件包括:分派器(DispatcherServlet)、处理器映射器(HandlerMapping)、处理器适配器(HandlerAdapter)、控制器(Controller)、视图解析器(ViewResolver)等。 3. Spring MVC 的请求处理流程是怎样的? Spring MVC 的请求处理流程如下: 1) 客户端发送请求到分派器(DispatcherServlet)。 2) 分派器根据请求的 URL 查找对应的处理器映射器(HandlerMapping)。 3) 处理器映射器根据 URL 查找对应的控制器(Controller)。 4) 控制器根据请求参数等信息处理请求,然后返回一个逻辑视图名(Logical View Name)。 5) 分派器根据逻辑视图名查找对应的视图解析器(ViewResolver)。 6) 视图解析器根据逻辑视图名解析出对应的视图(View)。 7) 分派器将模型数据(Model)和视图(View)结合起来,渲染视图并返回响应。 4. Spring MVC 的优势有哪些? Spring MVC 的优势包括: 1) 轻量级和模块化设计,易于扩展。 2) 灵活的请求处理流程,可以适应各种场景。 3) 支持多种视图技术,如 JSP、Thymeleaf、Freemarker 等。 4) 支持多种数据格式,如 JSON、XML 等。 5) 集成了 Spring 框架的其他功能,如依赖注入、AOP 等。 5. Spring MVC 的缺点有哪些? Spring MVC 的缺点包括: 1) 学习曲线较陡峭,需要掌握 Spring 框架的基础知识。 2) 配置文件较多,容易出错。 3) 对初学者不够友好,需要花费一定的时间来理解和掌握。 6. Spring MVC 和 Struts2 有什么区别? Spring MVC 和 Struts2 都是流行的 MVC 框架,它们的区别如下: 1) Spring MVC 是基于 Spring 框架的 MVC 框架,而 Struts2 是基于 Struts 框架的 MVC 框架。 2) Spring MVC 的配置比 Struts2 更简单、更灵活,也更容易扩展。 3) Spring MVC 集成了 Spring 框架的其他功能,如依赖注入、AOP 等,而 Struts2 则需要通过插件来实现。 4) Spring MVC 对于 RESTful Web Services 的支持更加完善。 5) Struts2 对于 JSP 标签库和表单验证等功能更加强大。 7. Spring MVC 的常用注解有哪些? Spring MVC 的常用注解包括: 1) @Controller:标记一个类为控制器。 2) @RequestMapping:映射请求 URL 到控制器方法。 3) @RequestParam:获取请求参数的值。 4) @PathVariable:获取 URL 中的路径参数的值。 5) @ResponseBody:将响应数据转换为指定的数据格式,如 JSON、XML 等。 6) @ModelAttribute:用于绑定请求参数到模型对象中。 7) @SessionAttributes:用于指定模型对象中的属性需要存储到会话中。 8) @InitBinder:用于自定义数据绑定。 9) @ExceptionHandler:用于处理控制器方法中的异常。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值