1. 请求映射
- @xxxMapping:
- Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
- 现在:/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
- 核心Filter:HiddenHttpMethodFilter
- 用法:表单method=post,隐藏域_method=put
- 需要在SpringBoot中手动开启
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
spring:
mvc:
hiddenmethod:
filter:
enable: true #开启页面表单的Rest风格
Rest原理(表单提交使用Rest风格时)
- 表单提交会带上_method=PUT
- 请求过来被HiddenHttpMethodFilter拦截
- 请求是否正常,并且是POST
- 获取_method的值
- 兼容以下请求:PUT、DELETE、PATCH
- 原生request(post),包装模式requestWrapper重写了getMethod()方法,返回的是传入的值
- 过滤器链放行的时候用wrapper作为request放行,以后的方法调用getMethod()是调用requestWrapper的
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
// 表单上的方法必须声明为POST
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
// 获得_method请求参数
String paramValue = request.getParameter(this.methodParam);
// 是否为空
if (StringUtils.hasLength(paramValue)) {
// 不为空,将字符串转换成大写,所以在前端声明时大小写都可以
String method = paramValue.toUpperCase(Locale.ENGLISH);
// 判断允许的请求方式中是否包含该请求方式
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
修改请求方式参数名
// 组件之间没有依赖
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
// 修改请求方式参数名
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
Rest使用客户端工具
- 如axios、PostMan等直接发送put、delete等方式请求,无需filter开启
请求映射
继承关系
请求进来,先调用HttpServlet的doGet()方法,在doGet方法中会调用FrameworkServlet的processRequest(HttpServletRequest request, HttpServletResponse response)方法,在processRequest中调用DispatcherServlet的doService()方法,再调用doDispatch(每个请求都会调用)
SpringMVC的功能分析都从org.springframework.web.servlet.DispatcherServlet-->doDispatch()方法开始
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
// 请求是否使用异步,使用了,就使用异步管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 检查是否是文件上传请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 找到当前请求使用哪个handler->controller处理当前请求
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// HanlderMapping,处理器映射 /xxx->xxxController->method
// Request Mapping HandlerMapping:保存了所有@RequestMapping 和 handler的映射规则
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
所有的请求映射都保存在Handler Mapping中
- SpringBoot自己注册了欢迎页的请求映射"/"可以访问index.html
- SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
- 请求进来,尝试所有的HanlderMapping看是否有请求信息
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个HandlerMapping
- 我们需要一些自定义的映射处理,我们也可自己给容器中注册自定义的HandlerMapping