# 04.SpringBoot-Web开发-请求处理

# 1.简介

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver bea

    • 内容协商视图解析器和BeanName视图解析器
  • Support for serving static resources, including support for WebJars (covered later in this document))

    • 静态资源
  • Automatic registration of ConverterGenericConverter, and Formatter beans.

    • 自动注册 Converter,GenericConverter,Formatter
  • Support for HttpMessageConverters (covered later in this document).

    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
  • Static index.html support.

    • 静态index.html 页支持
  • Custom Favicon support (covered later in this document)

    • 自定义 Favicon
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document)

    • 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)

# 2.静态资源

1.静态资源目录

只要静态资源放在类路径下: /static or /public or /resources or/META-INF/resources

访问:项目根路径+静态资源名称

原理:静态映射/**

拦截静态资源和所有请求,但是,请求进来会先去找controller,看有没有匹配的路径,如果没有交给静态处理器进行处理

也可以改变默认的静态资源存访目录,只有此目录下的文件才能够被访问

spring:
  web:
    resources:
      static-locations: [classpath:/xuxu/]
1
2
3
4

2.静态资源访问前缀

spring:
  web:
    resources:
      static-locations: [classpath:/xuxu/]
  mvc:
    static-path-pattern: /resources/**
1
2
3
4
5
6

当前项目 + static-path-pattern + 静态资源名

3.webjar

可用jar方式添加css,js等资源文件

https://www.webjars.org/ (opens new window)

例如,添加jquery

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
</dependency>
1
2
3
4
5

自动映射,根据依赖里面的包路径访问

localhost:8888/webjars/jquery/3.5.1/jquery.js (opens new window)


# 3.欢迎页和小图标

1.欢迎页支持

  • 静态资源路径下 index.html。

    • 可以配置静态资源路径
    • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问

2.自定义Favicon

指网页标签上的小图标。

favicon.ico 放在静态资源目录下即可。


# 4.静态资源原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类WebMvcAutoConfiguration,生效(这些类都存在)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    ...
}
1
2
3
4
5
6
7
8
9
10
  • 给容器中配置的内容:
    • 配置文件的相关属性的绑定:WebMvcProperties对应配置文件spring.mvc下的内容、ResourceProperties==spring.resources
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
    ...
}
1
2
3
4
5
6
7

配置类中只有一个有参构造器,构造器所有参数都在容器中获取

public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
            this.resourceProperties = webProperties.getResources();
            this.mvcProperties = mvcProperties;
            this.beanFactory = beanFactory;
            this.messageConvertersProvider = messageConvertersProvider;
            this.resourceHandlerRegistrationCustomizer = (ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
            this.dispatcherServletPath = dispatcherServletPath;
            this.servletRegistrations = servletRegistrations;
            this.mvcProperties.checkConfiguration();
        }
1
2
3
4
5
6
7
8
9
10
  • ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象

  • WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象

  • ListableBeanFactory beanFactory Spring的beanFactory

  • HttpMessageConverters 找到所有的HttpMessageConverters

  • ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。

  • DispatcherServletPath

  • ServletRegistrationBean 给应用注册Servlet、Filter

资源处理的默认规则

...
public class WebMvcAutoConfiguration {
    ...
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
        ...
        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            super.addResourceHandlers(registry);
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
                return;
            }
            ServletContext servletContext = getServletContext();
            addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
            addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
                registration.addResourceLocations(this.resourceProperties.getStaticLocations());
                if (servletContext != null) {
                    registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
                }
            });
        }
        ...

    }
    ...
}
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

根据上述代码,我们可以同过配置禁止所有静态资源规则。

spring:
  web:
    resources:
      add-mappings: false 
1
2
3
4

静态资源规则:

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
            "classpath:/resources/", "classpath:/static/", "classpath:/public/" };

    /**
     * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
     * /resources/, /static/, /public/].
     */
    private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13

欢迎页的处理规则

...
public class WebMvcAutoConfiguration {
    ...
    public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
        ...
        @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
            WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
                    new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
                    this.mvcProperties.getStaticPathPattern());
            welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
            welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
            return welcomePageHandlerMapping;
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
                          ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
    if (welcomePage != null && "/**".equals(staticPathPattern)) {
        //要用欢迎页功能,必须是/**
        logger.info("Adding welcome page: " + welcomePage);
        setRootViewName("forward:index.html");
    }
    else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
        //调用Controller /index
        logger.info("Adding welcome page template: index");
        setRootViewName("index");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

这构造方法内的代码也解释了web场景-welcome与favicon功能中配置static-path-pattern了,welcome页面和小图标失效的问题。


# 5.请求处理-Rest映射及源码解析

1.用法

  • 开启页面表单的Rest功能
  • 页面 form的属性method=post,隐藏域 _method=put、delete等(如果直接get或post,无需隐藏域)
  • 编写请求映射
spring:
  mvc:
    hidden method:
      filter:
        enabled: true
1
2
3
4
5
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>lalla</title>
</head>
<body>
<form action="/user" method="get">
    <input value="REST-GET提交" type="submit" />
</form>

<form action="/user" method="post">
    <input value="REST-POST提交" type="submit" />
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT" />
    <input value="REST-PUT提交"type="submit" />
    <form>
</body>
</html>
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
    @GetMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET-张三";
    }

    @PostMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.POST)
    public String saveUser(){
        return "POST-张三";
    }

    @PutMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String putUser(){
        return "PUT-张三";
    }

    @DeleteMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE-张三";
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

2.原理

1)表单提交带上_method=PUT

2)拦截器HiddenHttpMethodFilter拦截

  • 请求正常且是POST

  • 获取_method的值(兼容PUT,DELETE,PATCH)

  • 原生request(post),包装模式requestWrapper重写了getMethod方法,返回的是传入的值

  • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest requestToUse = request;
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
            String paramValue = request.getParameter(this.methodParam);
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                if (ALLOWED_METHODS.contains(method)) {
                      //调用HttpMethodRequestWrapper的构造方法
                    requestToUse = new HttpMethodRequestWrapper(request, method);
                }
            }
        }
        //过滤后传入的是requestToUser,它实际上是HttpMethodRequestWrapper对象
        //所以以后的getMethod也是它
        filterChain.doFilter((ServletRequest)requestToUse, response);
    }

    static {
        ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    }

    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        private final String method;

        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {

            //调用父类的request,父类里面再调用父类,返回的还是传入的request,
            //所以是同一次请求
            super(request);
            this.method = method;
        }

        public String getMethod() {
            return this.method;
        }
    }
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

3.Rest使用客户端工具

  • 如PostMan可直接发送put、delete等方式请求。

补充:包装模式

包装模式核心角色 代码中的实现 作用说明
抽象组件(Component) HttpServletRequest 接口 定义了 HTTP 请求的核心方法(如 getMethod()),是被包装的「核心接口」;
具体组件(ConcreteComponent) 原生的 HttpServletRequest 实例(Tomcat 等容器创建的对象) 实现了 HttpServletRequest 接口,是「基础对象」,提供默认的 getMethod() 实现(返回真实请求方法,如 POST);
抽象包装类(Decorator)(不一定是抽象类) HttpServletRequestWrapper 实现了 HttpServletRequest 接口,且持有 HttpServletRequest 引用(通过父类构造器传入),是所有请求包装类的「抽象包装基类」;
具体包装类(ConcreteDecorator) HttpMethodRequestWrapper 内部类 继承 HttpServletRequestWrapper(抽象包装类),重写 getMethod() 方法,动态修改返回值(核心:添加「方法伪装」的额外功能);

HttpMethodRequestWrapper 包装 HttpServletRequest,重写特定方法实现功能扩展,不修改原生对象;


# 6.请求处理-怎么改变默认的_method

    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"}
    )
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
1
2
3
4
5
6
7
8
9

@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})意味着没有这个HiddenHttpMethodFilter才会注册到容器中,因此我们可以自己注册一个HiddenHttpMethodFilter到容器中,并修改他的属性名

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("_m");
        return hiddenHttpMethodFilter;
    }
1
2
3
4
5
6

# 7.请求处理-请求映射原理

前置知识,抽象类和子类的调用方式

请求 → 框架/代码拿到「抽象类类型的子类实例」 → 调用抽象类定义的方法 → 多态触发子类的具体实现

在这里插入图片描述

HttpServlet是一个抽象类,FramworkServlet是具体实现

请求调用HttpServlet的service方法→子类继承父类的基础方法再添加自己的处理

先转为HttpServletRequest

到子类处理

调用父类的最后一句this.service(request,response)跳转到父类

前置知识:只有抽象类子类没有重写方法时,才会执行父类的默认实现;子类重写后,优先执行子类实现

所以这里的this.get调用的是子类FrameworkServlet的doget方法

随后进行processRequest

 protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }
1
2
3

doservice,FrameworkServlet里面的doService是抽象方法,所以直接调用子类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的方法)处理
            mappedHandler = getHandler(processedRequest);

            //HandlerMapping:处理器映射。/xxx->>xxxx
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

this.handlerMappings在Debug模式下展现的内容:

其中,保存了所有@RequestMapping 和handler的映射规则。

所有的请求映射都在HandlerMapping中:

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;

  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping

  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。如果有就找到这个请求对应的handler如果没有就是下一个 HandlerMapping

  • 我门需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

最后映射到controller,返回结果


# 8.请求处理-常用参数注解使用

注解:

@PathVariable 路径变量 @RequestHeader 获取请求头 @RequestParam 获取请求参数(指问号后的参数,url?a=1&b=2) @CookieValue 获取Cookie值 @RequestAttribute 获取request域属性 @RequestBody 获取请求体[POST] @MatrixVariable 矩阵变量 @ModelAttribute

示例:

@RestController
public class ParameterTestController {

    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("user-agent") String userAgent,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("Idea-52388292") Cookie cookie
                                     ){
        HashMap<String, Object> map = new HashMap<>();
//        map.put("id",id);
//        map.put("name",name);
//        map.put("pv",pv);
//        map.put("userAgent",userAgent);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("cookie",cookie);
        System.out.println(cookie.getName()+":"+cookie.getValue());
        return map;
    }

    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }

}
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

RequestAttribute示例

@Controller
public class RequestAttributeController {

    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){
        request.setAttribute("msg","这是从goToPage controller传入的");
        return "forward:/success";
    }

    @GetMapping("/success")
    @ResponseBody
    public Map<String,Object> success(@RequestAttribute(value = "msg") String msg
                            , HttpServletRequest request){
        Object msg1 = request.getAttribute("msg");
        HashMap<String, Object> map = new HashMap<>();
        map.put("msg",msg);
        map.put("msg_1",msg1);
        return map;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

MatrixVariable 矩阵变量

语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd

1)SpringBoot默认是禁用了矩阵变量的功能

2)手动开启:原理。对于路径的处理。UrlPathHelper的removeSemicolonContent设置为false,让其支持矩阵变量的。

3)矩阵变量必须有url路径变量才能被解析

手动开启矩阵变量

@Configuration
public class webConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        // 不移除;后面的内容。矩阵变量功能就可以生效
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}
1
2
3
4
5
6
7
8
9
10

@MatrixVariable的用例

    //cars/sell;low=34;brand=byd,audi,yd
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                        @PathVariable("path") String path){
        Map<String,Object> map = new HashMap<>();

        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }

    // /boss/1;age=20/2;age=10

    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map = new HashMap<>();

        map.put("bossAge",bossAge);
        map.put("empAge",empAge);
        return map;

    }
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

# 9.请求处理-各种类型参数解析原理

大体思路:HandlerMapping找能处理请求的Handler,找适配器 HandlerAdapter ,找参数解析器-HandlerMethodArgumentResolver,解析返回

1.从DispatcherServlet开始说起:

public class DispatcherServlet extends FrameworkServlet {

    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);

                // 获取当前请求的映射,根据5种类型进行映射
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // 获取映射适配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                ...
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
  • HandlerMapping中找到能处理请求的Handler(Controller.method())。

  • 为当前Handler 找一个适配器 HandlerAdapter(默认会加载所有HandlerAdapter),用的最多的是RequestMappingHandlerAdapter。

  • 适配器执行目标方法并确定方法参数的每一个值。

2.4种类型的映射适配器,看是否支持该类型适配器,支持就返回

3.执行目标方法(以下内容都在此方法种)

public class DispatcherServlet extends FrameworkServlet {

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mv = null;

        ...
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
        }


        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        ...
        //本节重点
        // 适配器处理
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

HandlerAdapter接口实现类RequestMappingHandlerAdapter(主要用来处理@RequestMapping)

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {

    ...

    //AbstractHandlerMethodAdapter类的方法,RequestMappingHandlerAdapter继承AbstractHandlerMethodAdapter
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

        return handleInternal(request, response, (HandlerMethod) handler);
    }

    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ModelAndView mav;
        //handleInternal的核心
        mav = invokeHandlerMethod(request, response, handlerMethod);//解释看下节
        //...
        return mav;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

4.参数解析器解析参数

参数解析器介绍:

  • 确定将要执行的目标方法的每一个参数的值是什么;

  • SpringMVC目标方法能写多少种参数类型。取决于参数解析器argumentResolvers

有27种类型,比如说RequestParam方法参数解析器、PathVariable方法参数解析器等

若是无注解的则使用以下参数解析器

无注解参数类型 核心解析器
基础类型(int/long/String/boolean 等) RequestParamMethodArgumentResolver
自定义对象(如 User/Order) ServletModelAttributeMethodProcessor
Servlet 原生对象(HttpServletRequest/HttpServletResponse/HttpSession) ServletRequestMethodArgumentResolver
Model/Map/ModelMap ModelMethodArgumentResolver
MultipartFile/Part RequestPartMethodArgumentResolver
其他特殊类型(Locale/TimeZone) LocaleMethodArgumentResolver

这一步之前初始化了所有参数解析器

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {

    @Nullable
    private HandlerMethodArgumentResolverComposite argumentResolvers;

    @Override
    public void afterPropertiesSet() {
        ...
        if (this.argumentResolvers == null) {//初始化argumentResolvers
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        ...
    }

    //初始化了一堆的实现HandlerMethodArgumentResolver接口的
    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        resolvers.add(new RequestParamMapMethodArgumentResolver());
        resolvers.add(new PathVariableMethodArgumentResolver());
        resolvers.add(new PathVariableMapMethodArgumentResolver());
        resolvers.add(new MatrixVariableMethodArgumentResolver());
        resolvers.add(new MatrixVariableMapMethodArgumentResolver());
        resolvers.add(new ServletModelAttributeMethodProcessor(false));
        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new RequestHeaderMapMethodArgumentResolver());
        resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
        resolvers.add(new SessionAttributeMethodArgumentResolver());
        resolvers.add(new RequestAttributeMethodArgumentResolver());

        // Type-based argument resolution
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
        resolvers.add(new RedirectAttributesMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new ErrorsMethodArgumentResolver());
        resolvers.add(new SessionStatusMethodArgumentResolver());
        resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
        if (KotlinDetector.isKotlinPresent()) {
            resolvers.add(new ContinuationHandlerMethodArgumentResolver());
        }

        // Custom arguments
        if (getCustomArgumentResolvers() != null) {
            resolvers.addAll(getCustomArgumentResolvers());
        }

        // Catch-all
        resolvers.add(new PrincipalMethodArgumentResolver());
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));

        return resolvers;
    }

}
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

HandlerMethodArgumentResolverComposite类如下:(众多参数解析器argumentResolvers的包装类)。

如果没有这个「包装类」,InvocableHandlerMethod 就需要逐个遍历所有解析器、判断是否支持当前参数,代码会极其冗余;而 HandlerMethodArgumentResolverComposite 把这些逻辑封装起来,对外提供简洁的接口,这正是「包装 / 组合模式」的核心思想。

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

    private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();

    ...
    //注册解析器(把所有解析器加入集合)
    public HandlerMethodArgumentResolverComposite addResolvers(
            @Nullable HandlerMethodArgumentResolver... resolvers) {

        if (resolvers != null) {
            Collections.addAll(this.argumentResolvers, resolvers);
        }
        return this;
    }

    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

回顾执行目标方法

public class DispatcherServlet extends FrameworkServlet {
    ...
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mv = null;
        ...
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
1
2
3
4
5
6

RequestMappingHandlerAdapterhandle()方法:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {

    ...

    //AbstractHandlerMethodAdapter类的方法,RequestMappingHandlerAdapter继承AbstractHandlerMethodAdapter
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

        return handleInternal(request, response, (HandlerMethod) handler);
    }

    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ModelAndView mav;
        //handleInternal的核心
        mav = invokeHandlerMethod(request, response, handlerMethod);//解释看下节
        //...
        return mav;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

invokeAndHandle()方法如下:

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {

    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

        ...

        try {
            //returnValue存储起来
            this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            ...
        }
    }

    @Nullable//InvocableHandlerMethod类的,ServletInvocableHandlerMethod类继承InvocableHandlerMethod类
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        ////获取方法的参数值
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

        ...

        return doInvoke(args);
    }

    @Nullable
    protected Object doInvoke(Object... args) throws Exception {
        Method method = getBridgedMethod();//@RequestMapping的方法
        ReflectionUtils.makeAccessible(method);
        try {
            if (KotlinDetector.isSuspendingFunction(method)) {
                return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
            }
            //通过反射调用
            return method.invoke(getBean(), args);//getBean()指@RequestMapping的方法所在类的对象。
        }
        catch (IllegalArgumentException ex) {
            ...
        }
        catch (InvocationTargetException ex) {
            ...
        }
    }

}   
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
48
49
50
51

如何确定目标方法每一个参数的值

重点分析ServletInvocableHandlerMethodgetMethodArgumentValues方法

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
    ...

    @Nullable//InvocableHandlerMethod类的,ServletInvocableHandlerMethod类继承InvocableHandlerMethod类
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        ////获取方法的参数值
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

        ...

        return doInvoke(args);
    }

    //本节重点,获取方法的参数值
    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        MethodParameter[] parameters = getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        }

        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = findProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            //查看resolvers是否有支持
            if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }
            try {
                //支持的话就开始解析吧
                args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
            }
            catch (Exception ex) {
                ....
            }
        }
        return args;
    }

}
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
48

this.resolvers的类型为HandlerMethodArgumentResolverComposite

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return getArgumentResolver(parameter) != null;
    }

    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" +
                    parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        }
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }


    @Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            //挨个判断所有参数解析器那个支持解析这个参数
            for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, result);//找到了,resolver就缓存起来,方便稍后resolveArgument()方法使用
                    break;
                }
            }
        }
        return result;
    }
}
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

总结:

ha.handle(processedRequest, response, mappedHandler.getHandler());内部方法类关系总结

1.RequestMappingHandlerAdapter 初始化时加载所有「参数解析器(HandlerMethodArgumentResolver)」并封装到 HandlerMethodArgumentResolverComposite

2.执行时将解析器集合传递给 InvocableHandlerMethod

3.InvocableHandlerMethod 委托 HandlerMethodArgumentResolverComposite 找到「适配当前参数类型的解析器」(比如 RequestParamMethodArgumentResolver

4.最终由具体解析器(如 RequestParamMethodArgumentResolver)继承 AbstractNamedValueMethodArgumentResolver 完成参数的「命名值类型参数(@RequestParam/@RequestHeader 等)」统一处理

5.解析结果返回给 InvocableHandlerMethod 执行控制器方法。

6.最终返回给ha.handle(processedRequest, response, mappedHandler.getHandler())

示例

@RequestParam("name") String name 为例,解析流程:

  1. RequestMappingHandlerAdapter 触发 handle 方法,创建 InvocableHandlerMethod,并把「所有解析器组成的 Composite」传给它;

  2. InvocableHandlerMethod 遍历控制器方法的参数(如 name 参数),调用 Composite.supportsParameter(参数)

  3. Composite 遍历所有解析器,找到 RequestParamMethodArgumentResolver(它的 supportsParameter 返回 true);

  4. Composite 调用 RequestParamMethodArgumentResolver.resolveArgument(参数, request)

  5. RequestParamMethodArgumentResolver 继承 AbstractNamedValueMethodArgumentResolver,执行通用解析逻辑:

    • 解析 @RequestParam 注解的属性(name = "name"、required = true、defaultValue = "");
    • request.getParameter("name") 取值;
    • 处理默认值(如果没传值且有默认值则用默认值);
    • 类型转换(把 String 转为目标类型);
  6. 解析后的参数值返回给 InvocableHandlerMethod

  7. InvocableHandlerMethod 用反射执行控制器方法,传入解析后的参数值。


# 10.请求处理-Servlet API参数解析原理

  • WebRequest
  • ServletRequest
  • MultipartRequest
  • HttpSession
  • javax.servlet.http.PushBuilder
  • Principal
  • InputStream
  • Reader
  • HttpMethod
  • Locale
  • TimeZone
  • ZoneId

ServletRequestMethodArgumentResolver用来处理以上的参数


# 11.请求处理-Model、Map原理

  • ModelMap 是「同一数据的不同接口封装」:控制器方法中声明 ModelMap 参数,Spring 会注入同一个 BindingAwareModelMap 实例(Map 的子类 + Model 接口的实现类);
  • 所有存入 Model/Map 的数据,最终都会被归集到 ModelAndViewContainerModel 容器中;
  • 请求处理完成后,Model 中的数据会被暴露为视图的「请求域属性」(request.setAttribute),供页面渲染使用。

复杂参数:

  • Map

  • Model(map、model里面的数据会被放在request的请求域 request.setAttribute)

  • Errors/BindingResult

  • RedirectAttributes( 重定向携带数据)

  • ServletResponse(response)

  • SessionStatus

  • UriComponentsBuilder

  • ServletUriComponentsBuilder

Map<String,Object> map、Model model、HttpServletRequest request

上面三位都是可以给request域中放数据,用request.getAttribute()获取

接下来我们看看,Map<String,Object> map与Model model用什么参数处理器。

Map<String,Object> map参数用MapMethodProcessor处理:

public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
                parameter.getParameterAnnotations().length == 0);
    }

    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
        return mavContainer.getModel();
    }

    ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

mavContainer.getModel()如下:

public class ModelAndViewContainer {

    ...

    private final ModelMap defaultModel = new BindingAwareModelMap();

    @Nullable
    private ModelMap redirectModel;

    ...

    public ModelMap getModel() {
        if (useDefaultModel()) {
            return this.defaultModel;
        }
        else {
            if (this.redirectModel == null) {
                this.redirectModel = new ModelMap();
            }
            return this.redirectModel;
        }
    }

    private boolean useDefaultModel() {
        return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
    }
    ...

}
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

Model modelModelMethodProcessor处理:

public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Model.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
        return mavContainer.getModel();
    }
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

return mavContainer.getModel();这跟MapMethodProcessor的一致

Model也是另一种意义的Map


接下来看看Map<String,Object> map与Model model值是如何做到用request.getAttribute()获取的。

众所周知,所有的数据都放在 ModelAndView包含要去的页面地址View,还包含Model数据。

先看ModelAndView接下来是如何处理的?

public class DispatcherServlet extends FrameworkServlet {

    ...

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...

        try {
            ModelAndView mv = null;

            ...

            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            ...

            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            //处理分发结果
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        ...

    }

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
            @Nullable Exception exception) throws Exception {
        ...

        // Did the handler return a view to render?
        if (mv != null && !mv.wasCleared()) {
            render(mv, request, response);
            ...
        }
        ...
    }

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...

        View view;
        String viewName = mv.getViewName();
        if (viewName != null) {
            // We need to resolve the view name.
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                        "' in servlet with name '" + getServletName() + "'");
            }
        }
        else {
            // No need to lookup: the ModelAndView object contains the actual View object.
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                        "View object in servlet with name '" + getServletName() + "'");
            }
        }
        view.render(mv.getModelInternal(), request, response);

        ...
    }

}
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

在Debug模式下,view属为InternalResourceView类。

public class InternalResourceView extends AbstractUrlBasedView {

     @Override//该方法在AbstractView,AbstractUrlBasedView继承了AbstractView
    public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
            HttpServletResponse response) throws Exception {

        ...

        Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
        prepareResponse(request, response);

        //看下一个方法实现
        renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
    }

    @Override
    protected void renderMergedOutputModel(
            Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

        // Expose the model object as request attributes.
        // 暴露模型作为请求域属性
        exposeModelAsRequestAttributes(model, request);//<---重点

        // Expose helpers as request attributes, if any.
        exposeHelpers(request);

        // Determine the path for the request dispatcher.
        String dispatcherPath = prepareForRendering(request, response);

        // Obtain a RequestDispatcher for the target resource (typically a JSP).
        RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);

        ...
    }

    //该方法在AbstractView,AbstractUrlBasedView继承了AbstractView
    protected void exposeModelAsRequestAttributes(Map<String, Object> model,
            HttpServletRequest request) throws Exception {

        model.forEach((name, value) -> {
            if (value != null) {
                request.setAttribute(name, value);
            }
            else {
                request.removeAttribute(name);
            }
        });
    }

}
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
48
49
50

exposeModelAsRequestAttributes方法看出,Map<String,Object> mapModel model这两种类型数据可以给request域中放数据,用request.getAttribute()获取。


# 12.请求处理-自定义参数绑定

核心逻辑:WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中

在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter,可以将这个数据类型(request带来参数的字符串)转换到指定的类型

public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {

    @Override//本方法在ModelAttributeMethodProcessor类,
    public boolean supportsParameter(MethodParameter parameter) {
        return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
                (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
    }

    @Override
    @Nullable//本方法在ModelAttributeMethodProcessor类,
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

        ...

        String name = ModelFactory.getNameForParameter(parameter);
        ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
        if (ann != null) {
            mavContainer.setBinding(name, ann.binding());
        }

        Object attribute = null;
        BindingResult bindingResult = null;

        if (mavContainer.containsAttribute(name)) {
            attribute = mavContainer.getModel().get(name);
        }
        else {
            // Create attribute instance
            try {
                attribute = createAttribute(name, parameter, binderFactory, webRequest);
            }
            catch (BindException ex) {
                ...
            }
        }

        if (bindingResult == null) {
            // Bean property binding and validation;
            // skipped in case of binding failure on construction.
            WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
            if (binder.getTarget() != null) {
                if (!mavContainer.isBindingDisabled(name)) {
                    //web数据绑定器,将请求参数的值绑定到指定的JavaBean里面**
                    bindRequestParameters(binder, webRequest);
                }
                validateIfApplicable(binder, parameter);
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }
            // Value type adaptation, also covering java.util.Optional
            if (!parameter.getParameterType().isInstance(attribute)) {
                attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
            }
            bindingResult = binder.getBindingResult();
        }

        // Add resolved attribute and BindingResult at the end of the model
        Map<String, Object> bindingResultModel = bindingResult.getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);

        return attribute;
    }
}
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

# 完整流程:

# 1. 请求入口:DispatcherServlet

当一个HTTP请求到达时,DispatcherServletdoDispatch方法开始处理。它会根据请求路径找到可以处理该请求的处理器(Handler),也就是我们写的Controller方法。这个处理器和方法的所有信息(包括参数列表、注解等)被封装成一个HandlerMethod对象。

# 2. 获取参数解析器:HandlerMethodArgumentResolver

Spring需要决定由谁来负责将请求数据绑定到这个HandlerMethod的每一个参数上。这个决定是在HandlerMethodArgumentResolverComposite(参数解析器组合器)中完成的。

  • 它内部维护了一个参数解析器列表List<HandlerMethodArgumentResolver>),这个列表包含了Spring内置的以及我们自定义添加的所有解析器。

  • 对于每个方法参数,它会遍历这个列表,调用每个解析器的supportsParameter(MethodParameter parameter)方法,来判断该解析器是否“支持”处理当前参数。

# 3. 常见的参数解析器示例

  • 如果参数上有@RequestParam注解,那么RequestParamMethodArgumentResolver会返回true

  • 如果参数是Model类型,ModelMethodProcessor会返回true

  • 如果参数是自定义的User对象且没有注解,Spring默认会将其视为需要从表单提交的数据中绑定属性,这由ModelAttributeMethodProcessor处理(但它通常配合@ModelAttribute显式使用,或不加注解时用于简单类型绑定的兜底,而复杂对象绑定则需要ServletModelAttributeMethodProcessor,这取决于配置)。

# 4. 执行参数解析:绑定数据

找到合适的解析器后,DispatcherServlet会调用它的resolveArgument方法,这是数据绑定的核心。这个方法需要完成几件事:

  1. 从请求中获取数据:从HttpServletRequest中获取参数值(例如,通过request.getParameter("name"))。

  2. 类型转换:将获取到的字符串转换为参数需要的类型(例如,将String "18" 转换为Integer 18)。这背后依赖于Spring的类型转换体系ConversionService)。

  3. 数据绑定:如果参数是对象(如User),它会创建一个新的User实例,并通过数据绑定器(WebDataBinder,将请求中的同名参数(如name=张三&age=18)一一绑定到对象的对应属性上。

  4. 校验:如果参数上标记了@Valid@Validated注解,WebDataBinder还会触发数据校验,并将校验结果封装到紧随其后的BindingResult参数中。


# 13.请求处理-自定义消息解析器

未来我们可以在WebDataBinder里放自己的Convert

例如:

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {

                    @Override
                    public Pet convert(String source) {
                        if (!source.isEmpty()){
                            String[] split = source.split(",");
                            Pet pet = new Pet();
                            pet.setName(split[0]);
                            pet.setAge(split[1]);
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
最近更新: 9/19/2026, 1:27:08 PM
编程NOTE   |