zl程序教程

您现在的位置是:首页 >  其他

当前栏目

SpringBoot2基础-请求参数处理和原理

基础原理 处理 参数 请求 SpringBoot2
2023-09-14 09:06:16 时间

SpringBoot2基础-请求参数处理和原理

tags:

  • Spring Boot
  • 2021尚硅谷
  • 雷丰阳

categories:

  • 静态文件配置
  • 静态文件配置原理
  • 欢迎页和自定义 Favicon
  • Rest表单请求原理
  • 请求映射原理
  • 各种参数使用和原理

在这里插入图片描述

第一节 SpringMVC自动配置概览

  1. SpringBoot封装了SpringMVC, SpringBoot中大多场景我们都无需自定义配置。
  2. SpringBoot自动配置了哪些SpringMVC中哪些东西。官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-spring-mvc-auto-configuration
    • 内容协商视图解析器和BeanName视图解析器
    • 静态资源(包括webjars)
    • 自动注册 Converter,GenericConverter,Formatter。比如:日期格式等自动转换
    • 支持 HttpMessageConverters (后来我们配合内容协商理解原理)
    • 自动注册 MessageCodesResolver (国际化用的) 用处不大,如果真的要用到国际化,一般开发两个网站。
    • 静态index.html 页支持
    • 自定义 Favicon
    • 自动使用 ConfigurableWebBindingInitializer ,(DataBinder负责将请求数据绑定到JavaBean上)
    • 不用@EnableWebMvc注解。使用 @Configuration + WebMvcConfigurer 自定义规则
    • 声明 WebMvcRegistrations 改变默认底层组件
    • 使用 @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC

第二节 静态资源访问

  1. 官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-spring-mvc-static-content

2.1 静态资源目录

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

    访问 : 当前项目根路径/ + 静态资源名

    • static
    • public
    • resources
    • META-INF/resources
  2. 原理: 请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面

  3. 修改静态资源访问的前缀

    • 当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找:http://127.0.0.1:8080/res/aaaaa.jpg
  4. 改变默认的静态资源路径

spring:
    mvc:
    static-path-pattern: /res/** # 配置访问前缀

    web:
     resources:
       static-locations: [classpath:/haha/] # 配置静态资源路径,可以写一个数组,也可以只写一个
  1. webjar 这个

    用的比较少

    。相当于jquery弄成一个jar包。通过依赖引用。

    • 自动映射 :访问资源http://localhost:8080/webjars/jquery/3.5.1/jquery.js
    • https://www.webjars.org/
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.5.1</version>
        </dependency>

2.2 欢迎页和自定义 Favicon

  1. 静态资源路径下 index.html
    • 可以配置静态资源路径
    • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
  2. controller能处理/index
spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致welcome page功能失效
  1. 自定义 Favicon
    • favicon.ico 放在静态资源目录下即可。
spring:
#  mvc:
#    static-path-pattern: /res/**   这个会导致自定义 Favicon配置时效

2.3 静态资源配置流程

  1. 首先,SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)

  2. SpringMVC功能的自动配置类 WebMvcAutoConfiguration,判断生效

  3. 看下给容器中配了什么。

    • org\springframework\boot\spring-boot-autoconfigure\2.4.5\spring-boot-autoconfigure-2.4.5-sources.jar!\org\springframework\boot\autoconfigure\web\servlet\WebMvcAutoConfiguration.java
  4. 配置类中的配置类

    如下。配置文件的相关属性和xxx进行了绑定

    • 发现WebMvcProperties和spring.mvc配置文件进行绑定、
    • 发现ResourceProperties和spring.resources配置进行绑定
	@Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class,
			org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
  1. 拓展知识:如果一个配置类只有一个有参构造器,那么有参构造器所有参数的值都会从容器中确定
 //有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory, 容器工厂
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到资源处理器的自定义器。===着重===
//DispatcherServletPath  DispatcherServlet处理的路径
//ServletRegistrationBean   给应用注册Servlet、Filter....
    public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
                ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
                ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
                ObjectProvider<DispatcherServletPath> dispatcherServletPath,
                ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
            this.resourceProperties = resourceProperties;
            this.mvcProperties = mvcProperties;
            this.beanFactory = beanFactory;
            this.messageConvertersProvider = messageConvertersProvider;
            this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
            this.dispatcherServletPath = dispatcherServletPath;
            this.servletRegistrations = servletRegistrations;
        }
  1. 代码朝下找:找到函数addResourceHandlers 它处理资源处理的默认规则

2.4 资源处理的默认规则

  1. 函数addResourceHandlers, 在if上加断点看下默认规则怎么生效的。(我的是最新的框架,一些函数可能和视屏不一致)
    • 上面从容器中获得的这个resourceProperties(resource的配置文件),有isAddMappings属性。到配置文件中测试下这个属性的作用。
    • 发现默认为True, 如果配置成False下面一堆配置不生效。
		@Override
		protected void addResourceHandlers(ResourceHandlerRegistry registry) {
			super.addResourceHandlers(registry);
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			ServletContext servletContext = getServletContext();
            // webjars的规则
			addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
            // resouser
			addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
				registration.addResourceLocations(this.resourceProperties.getStaticLocations());
				if (servletContext != null) {
					registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
				}
			});
		}
  web:
    resources:
      static-locations: [classpath:/haha/]
      add-mappings: false # 禁用所有的静态资源配置
      cache:
        period: 11000 # 配置静态资源的缓存时间 以秒为单位
  1. 它调用了addResourceHandler,这个里面设置了静态资源的缓存
  2. 找静态资源的默认位置。resourceProperties.getStaticLocations()
		private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
				"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
  1. 欢迎页面的函数WelcomePageHandlerMapping
// HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。
		@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;
		}
		
// 上面代码点入这个WelcomePageHandlerMapping, 第一个if也解释了上面定义路径欢迎页找不到因为 "/**".equals(staticPathPattern)
	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)) {
			logger.info("Adding welcome page template: index");
			setRootViewName("index");
		}
	}

  1. favicon这个和我们的代码没什么关系了,浏览器会默认发/favicon.ico

第三节 请求参数处理-请求映射

3.1 rest使用与原理

  1. 表单Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)

    • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
    • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
  2. 前端表单中的method并不支持delete和put, 默认用get处理

    ,如何让它可以处理我们

    表单

    中的delete和put。

    • 第一步:给我们带一个DEFAULT_METHOD_PARAM = "_method"的隐藏字段可以。
    • 第二步:开启配置手动开启,hiddenHttpMethodFilter配置(默认不开启)
<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>
12345678
# 开启配置手动开启
  mvc:
    hiddenmethod:
      filter:
        enabled: true

  1. 函数流程:WebMvcAutoConfiguration中找到hiddenHttpMethodFilter,点击去->在点到父类中。找到org.springframework.web.filter.HiddenHttpMethodFilter的DEFAULT_METHOD_PARAM配置。
// 测试代码
  //@RequestMapping(value = "/user",method = RequestMethod.GET)
    // 简写
    @GetMapping("/user")
    public String getUser(){
        return "GET-张三";
    }

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


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

    //@RequestMapping(value = "/user",method = RequestMethod.DELETE)
    // 简写
    @DeleteMapping("/user")
    public String deleteUser(){
        return "DELETE-张三";
    }

  1. Rest原理(表单提交要使用REST的时候)
    • 表单提交会带上_method=PUT
    • 请求过来被HiddenHttpMethodFilter拦截
    • 请求是否正常,并且是POST
    • 获取到_method的值。兼容以下请求;PUT.DELETE.PATCH (传过来的值不区分大小写)
    • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
    • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		HttpServletRequest requestToUse = request;
		// 这里判断必须用POST提交 而且没有错误
		if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
			String paramValue = request.getParameter(this.methodParam);
			if (StringUtils.hasLength(paramValue)) {
				// 这里不区分大小写
				String method = paramValue.toUpperCase(Locale.ENGLISH);
				if (ALLOWED_METHODS.contains(method)) {
					// 包装模式requesWrappe重写了getMethod方法
					requestToUse = new HttpMethodRequestWrapper(request, method);
				}
			}
		}
		// 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的
		filterChain.doFilter(requestToUse, response);
	}

  1. Rest使用客户端工具

    ,上面只是指的是

    表单请求

    • 如PostMan直接发送Put、delete等方式请求,无需Filter
    • 所以它选择开启,我们实际不会用它做页面。都是前后端分离的模式,页面都是别人写的,只要掉我们接口就可以了。
  2. 如果我们不想用_method而想,自己定义一个隐藏字段比如:_m。写一个配置类修改,重新启动工程。

package com.atguigu.boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;

// 没有依赖 效率高
@Configuration(proxyBeanMethods = false)
public class WebConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        // 修改成_m 之前的_method就不可以用了
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }
}

3.2 请求映射原理

  1. CTRL+N全局搜索类DispatcherServlet,CTRL+H搜索继承树。去找重写原生HTTPServerlet的doget和dopost请求的方法。在FrameworkServlet中可以找到。
  2. 调用过程:doGet -> processRequest -> doService(抽象) -> 子类的doService(DispatcherServlet) -> doDispatch(这才是我们要研究的方法,每个请求都经过它)
    在这里插入图片描述
  3. SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet的doDispatch方法。断点打在这个函数上。运行。
    • 发现:getHandler(processedRequest)是确定请求用哪个Handler处理器处理的方法
    • 点击去:this.handlerMappings处理器映射(所有的请求映射都在HandlerMapping中),默认有五个。展开后可以看到一些细节。
      • 0中是RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。
      • 1中是SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
        在这里插入图片描述在这里插入图片描述
  4. 配置类org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration中。requestMappingHandlerMapping注册了处理标了注解的方法。
    • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
    • 如果有就找到这个请求对应的handler
    • 如果没有就是下一个 HandlerMapping
  5. 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

第四节 请求参数处理-普通参数和基本注解

4.1 注解方式-常用注解

  1. @PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
    //  car/2/owner/zhangsan
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String, Object> getCar(
                                    // 路径变量id
                                     @PathVariable("id") Integer id,
                                    // 路径变量username
                                     @PathVariable("username") String name,
                                    // 获取所有的路径变量
                                     @PathVariable Map<String,String> pv,
                                    //获取请求头中User-Agent
                                     @RequestHeader("User-Agent") String userAgent,
                                    //获取所有请求头
                                     @RequestHeader Map<String,String> header,
                                    // 获取请求参数age ?age=18
                                     @RequestParam("age") Integer age,
                                    // 获取请求参数inters
                                     @RequestParam("inters") List<String> inters,
                                    // 获取所有的请求参数
                                     @RequestParam Map<String,String> params,
                                    // 获取cookie中的_ga的值
                                     @CookieValue("_ga") String _ga,
                                    // 获取cookie中所有信息
                                     @CookieValue("_ga") Cookie cookie){
        Map<String, Object> map = new HashMap<>();

//        map.put("id",id);
//        map.put("name",name);
//        map.put("pv",pv);
//        map.put("userAgent",userAgent);
//        map.put("headers",header);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
    }

    // 获取请求体中的值, 只有Post方式有请求体
    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }

4.2 注解方式- @RequestAttribute

  1. @RequestAttribute
@Controller // 普通的控制器,方法的返回 是要进行跳转的
public class RequestController {


    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){

        request.setAttribute("msg","成功了...");
        request.setAttribute("code",200);
        return "forward:/success";  //转发到  /success请求
    }


    @GetMapping("/params")
    public String testParam(Map<String,Object> map,
                            Model model,
                            HttpServletRequest request,
                            HttpServletResponse response){
        map.put("hello","world666");
        model.addAttribute("world","hello666");
        request.setAttribute("message","HelloWorld");

        Cookie cookie = new Cookie("c1","v1");
        response.addCookie(cookie);
        return "forward:/success";
    }


    @ResponseBody
    @GetMapping("/success")
    public Map success(
                        // 获取请求域中的msg 上面转发的时设置的属性 required = false请求域中这个属性不是必须的
                        @RequestAttribute(value = "msg",required = false) String msg,
                        // 这个没写可以获取全部属性 点进去看看
                        @RequestAttribute(value = "code",required = false)Integer code,
                        // 通过原生请求获取request
                        HttpServletRequest request){
        Object msg1 = request.getAttribute("msg");
        Map<String,Object> map = new HashMap<>();
        Object hello = request.getAttribute("hello");
        Object world = request.getAttribute("world");
        Object message = request.getAttribute("message");

        map.put("reqMethod_msg",msg1);
        map.put("annotation_msg",msg);
        map.put("hello",hello);
        map.put("world",world);
        map.put("message",message);
        return map;
    }
}

4.3 注解方式-矩阵变量@MatrixVariable使用

  1. 矩阵变量@MatrixVariable使用

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

    • 面试题:页面开发,cookie禁用了,session里面的内容怎么使用;

      • 默认:session.set(a,b)—> jsessionid —> cookie ----> 每次发请求携带。
      • 可以使用url重写:/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递.
    • SpringBoot默认是禁用了矩阵变量的功能

      .

      • 自动配置类中configurePathMatch这个进行处理。
      • 手动开启:原理。对于路径的处理。UrlPathHelper进行解析,点进去发现。removeSemicolonContent = true;(移除分号内容)支持矩阵变量。
<a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable(矩阵变量)</a>
<a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable(矩阵变量)</a>
<a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a>

  1. 手动开启矩阵变量的功能,两种方式
    • 第一种写法:@Bean 给容器中直接放入WebMvcConfigurer组件
    • 第二种写法:实现WebMvcConfigurer,因为有默认实现只用修改需要修改的方法。
// 第一种写法: 给容器中放入WebMvcConfigurer组件
@Configuration(proxyBeanMethods = false)
public class WebConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }

    @Bean
    public  WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }
}

// 第二种写法 实现WebMvcConfigurer
@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");
        return methodFilter;
    }
    
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}

  1. 矩阵变量必须有url路径变量才能被解析:要写成路径变量的表示方法{path}
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                        // 获取真正的访问路径就是sell
                        @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;
    }

4.4 各种参数解析原理

  1. 初始:和之前一样依旧CTRL+N, 搜org.springframework.web.servlet.DispatcherServlet下的doDispatch。打断点调试。

    • http://127.0.0.1:8080/car/3/owner/lisi?age=18&inters=basketball&inters=game
  2. 第一步:mappedHandler = getHandler(processedRequest);点进去,发现HandlerMapping中找到能处理请求的Handler(Controller.method())

    • mappedHandler = getHandler(processedRequest);
  3. 第二步:为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter

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

    • HandlerAdapter是SpringMVC底层设计的接口。两个重要函数1. 支持接口,2. 调用处理

    • getHandlerAdapter点击去,在所有的HandlerAdapter中

      确定共有四种

      • 0 - 支持方法上标注@RequestMapping (默认)
      • 1 - 支持函数式编程的
      • xxxxxx
  4. 第三步:执行目标方法. 还是在DispatcherServlet中的 doDispatch

    • mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    • 先追综getHandle,在连续追踪handle。找到org.springframework.web.servlet.ModelAndView。
      • 执行目标方法:mav = invokeHandlerMethod(request, response, handlerMethod);
      • 继续追进去看下目标方法怎么执行。发现字段,发现参数解析器和返回值处理器
        • argumentResolvers参数解析器。27种
        • 确定将要执行的目标方法的每一个参数的值是什么;
        • SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
        • returnValueHandlers返回值处理器。15种
          在这里插入图片描述
  5. 第四步:真正执行目标方法。

    • 通过把invocableMethod.invokeAndHandle(webRequest, mavContainer);。继续点进去。
    • Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);这个会先回到我们的Controller然后在回到下面函数。setResponseStatus(webRequest);
      • 继续点进去invokeForRequest看怎么执行控制器方法的。
        • 获取方法参数值:Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        • 继续点进去getMethodArgumentValues可以看到:如何获取参数值
          在这里插入图片描述
  6. 第五步:如何获取参数值。

    • 获取每个参数的详细信息:MethodParameter[] parameters = getMethodParameters();
    • 声明一个参数长度相同的args,最终把它返回。Object[] args = new Object[parameters.length];
    • 遍历参数:确定每一个参数参数解析器是否支持。if (!this.resolvers.supportsParameter(parameter)) 。具体是增强for循环一个个判断所有参数解析器的。
    • 解析参数值:确定参数支持后通过args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);解析参数值。点进去。
    • AbstractNamedValueMethodArgumentResolver中resolveArgument是具体解析函数
  7. 第六步:目标方法执行完成

    • 将所有的数据都放在mavContainer;包含要去的页面地址View。还包含Model数据
      在这里插入图片描述
  8. 第七步:处理派发结果

    • org.springframework.web.servlet.DispatcherServlet#processDispatchResult
    • processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    • 一直追踪到视图解析方法:InternalResourceView:
      -org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel

4.2 Servlet API

  1. WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
  2. 如下函数参数就是:HttpServletRequest request 类型。
    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){

        request.setAttribute("msg","成功了...");
        request.setAttribute("code",200);
        return "forward:/success";  //转发到  /success请求
    }

  1. 访问路径打断点。看下流程。
    • org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver中解析到ServletRequestMethodArgumentResolver对象
    • 点击进去发现下面:
@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
				(Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

4.3 复杂参数

  1. 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()获取
    @GetMapping("/params")
    public String testParam(Map<String,Object> map,
                            Model model,
                            HttpServletRequest request,
                            HttpServletResponse response){
        map.put("hello","world666");
        model.addAttribute("world","hello666");
        request.setAttribute("message","HelloWorld");

        Cookie cookie = new Cookie("c1","v1");
        response.addCookie(cookie);
        return "forward:/success";
    }

  1. 和上面分析一样断点追踪:
    • Map、Model类型的参数,底层都会返回 mavContainer.getModel();
    • BindingAwareModelMap 是Model 也是Map,mavContainer.getModel(); 获取到值的

4.4 自定义类型的参数对象

  1. 可以自动类型转换与格式化,可以级联封装。
package com.atguigu.boot.bean;

import lombok.Data;

import java.util.Date;

/**
 *     姓名: <input name="userName"/> <br/>
 *     年龄: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     宠物姓名:<input name="pet.name"/><br/>
 *     宠物年龄:<input name="pet.age"/>
 */
@Data
public class Person {

    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;

}

@Data
public class Pet {

    private String name;
    private Integer age;

}
  1. 先运行一下让index.html出来,在加断点点击表单请求。断点看下原理, Person怎么把页面中的数据跟我们Person的每个属性一一绑定。
    @PostMapping("/saveuser")
    public Person saveuser(Person person){

        return person;
    }

  1. 确定是ServletModelAttributeMethodProcessor这个参数处理器完成的。

  2. WebDataBinder :web数据绑定器

    ,将请求参数的值绑定到指定的JavaBean里面

    • WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
    • WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
    • GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
      byte – > file
  3. 添加自定义转换器让它可以识别 这种对象

  // 添加自定义转换器让它可以识别 <input name="pet" value="啊猫,3"/> 这种对象
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new Converter<String, Pet>() {

            @Override
            public Pet convert(String source) {
                // 啊猫,3
                if(StringUtils.hasLength(source)){
                    Pet pet = new Pet();
                    String[] split = source.split(",");
                    pet.setName(split[0]);
                    pet.setAge(Integer.parseInt(split[1]));
                    return pet;
                }
                return null;
            }
        });
    }