2021年6月19日——springboot核心功能(二)

二、Web开发

1. SpringMVC自动配置概览

springmvc auto-configuration 自动添加以下功能:

  • ContentNegotiatingViewResolver 和 BeanNameViewResolver
  • 支持静态资源访问,包括WebJar
  • 自动注册 Converter,GenericConverter,Formatter
  • 支持 HttpMessageConverters
  • 自动注册 MessageCodesResolver (国际化用)
  • 静态index.html 页支持
  • 自定义 Favicon
  • 自动使用 ConfigurableWebBindingInitializer

这都什么呀(⊙ˍ⊙)

自定义规则:

  • @Configuration + WebMvcConfigurer 部分自定义

  • @EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面自定义

  • 声明 WebMvcRegistrations 改变默认底层组件

2. 简单功能分析

2.1 静态资源访问
2.1.1 静态资源目录

默认位置:classpath下的

  • static/
  • public/
  • resources/
  • META-INF/resources

如何访问?举个例子:

  • 文件A位于static/data/fileA
  • 浏览器访问:http://localhost:8080/data/fileA
  • 文件B位于public/data/fileB
  • 浏览器访问:http://localhost:8080/data/fileB

原理:

  • 请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
  • 所以如果Controller的Mapping和静态资源一样,只会访问Controller

自定义静态资源路径:

spring:
# 映射地址,从此访问静态资源都要加static前缀
  mvc:
    static-path-pattern: /static/**
# 静态资源目录,修改后只有classpath:/myresources/下的资源才能被访问到
  web:
    resources:
      static-locations: [classpath:/myresources/]
2.1.2 webjar

什么是webjar:

  • webjar就是前端框架打成的jar包,它的访问不同于普通静态资源,自定义静态资源路径也不会影响到它

默认映射地址:

  • /webjars/**

例子:访问jQuery

  • 引入jQuery
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
</dependency>
2.2 首页

首页位置:

  • 静态资源路径/index.html
  • 为了能成功访问,不要重新配置静态资源的映射地址
2.3 自定义Favicon
  • 将favicon.ico放在静态资源目录下就行了
  • 直接改后缀名没有影响
  • 访问不到,不是服务器缓存就是浏览器缓存,可以Ctrl + shift + R刷新浏览器,还不行就重启服务器。
2.4 静态资源配置原理
  • SpringBoot启动加载 xxxAutoConfiguration 类(自动配置类)
  • 因为写了web-starter,所以WebMvcAutoConfiguration生效

WebMvcAutoConfiguration部分代码

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class,WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class,
                     TaskExecutionAutoConfiguration.class, 							
                     ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {}
  • 里面存在一个内部类
  • WebMvcAutoConfiguration部分代码
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
  • 配置文件的相关属性和属性类进行了绑定。WebMvcProperties==spring.mvc、ResourceProperties==spring.resources
  • 该内部类只有一个有参构造,传入的参数是什么(⊙ˍ⊙)?
public WebMvcAutoConfigurationAdapter(
	WebProperties webProperties, 
	WebMvcProperties mvcProperties, 
	ListableBeanFactory beanFactory, 
	ObjectProvider<HttpMessageConverters> messageConvertersProvider, 
	ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> 	
    	resourceHandlerRegistrationCustomizerProvider, 	
    ObjectProvider<DispatcherServletPath> dispatcherServletPath, 
    ObjectProvider<ServletRegistrationBean<?>> servletRegistrations
) {
    this.mvcProperties = mvcProperties;
    this.beanFactory = beanFactory;
    this.messageConvertersProvider = messageConvertersProvider;
    this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
    this.dispatcherServletPath = dispatcherServletPath;
    this.servletRegistrations = servletRegistrations;
    this.mvcProperties.checkConfiguration();
}
  • webjar 和 静态资源 的处理

WebMvcAutoConfiguration部分代码

protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    super.addResourceHandlers(registry);
    // resources.add-mappings=false 禁用所有静态资源规则
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
    } else {
        ServletContext servletContext = this.getServletContext();
        // webjar 
        this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        // 静态资源
        this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (servletContext != null) {
                registration.addResourceLocations(new Resource[]{new ServletContextResource(servletContext, "/")});
            }

        });
    }
}
  • 欢迎页的处理

WebMvcAutoConfiguration部分代码

@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
    return welcomePageHandlerMapping;
}

WelcomePageHandlerMapping的构造函数

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
    // 欢迎页的处理
    if (welcomePage != null && "/**".equals(staticPathPattern)) {
        logger.info("Adding welcome page: " + welcomePage);
        this.setRootViewName("forward:index.html");
    } else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
        logger.info("Adding welcome page template: index");
        this.setRootViewName("index");
    }
}

3. 请求参数处理

3.1 rest使用与原理
3.1.1 什么是rest
  • 以前:用不同的mapping来区分功能
    • /getUser 获取用户
    • /deleteUser 删除用户
    • /editUser 修改用户
    • /saveUser 保存用户
  • 现在:使用同一个mapping——/user,不过请求方法不同
    • GET请求-获取用户
    • DELETE-删除用户
    • PUT-修改用户
    • POST-保存用户
3.1.2 如何实现rest
  • 请求表单:
<!DOCTYPE html>
<html lang="zh-CH">
<head>
    <meta charset="UTF-8">
    <title>rest</title>
</head>
<body>
    <form method="get" action="/user">
        <!-- 
			因为表单只能发get和post请求,所以使用一个隐藏域来发送_method请求参数 
			服务器的filter就会拿到这个请求参数,以此来做一些操作
		-->
        <input type="hidden" value="GET" name="_method" >
        <input type="submit" value="GET">
    </form>
    <form method="post" action="/user">
        <input type="hidden" value="POST" name="_method" >
        <input type="submit" value="POST">
    </form>
    <form method="post" action="/user">
        <input type="hidden" value="DELETE" name="_method" >
        <input type="submit"  value="DELETE">
    </form>
    <form method="post" action="/user">
        <input type="hidden" value="PUT" name="_method" >
        <input type="submit"  value="PUT">
    </form>
</body>
</html>
  • controller
@RestController
public class MyRestController {
     // 下面两个注解效果一样
    @GetMapping("/user")
//    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public String getUser() {
        return "GET-USER";
    }

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

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

    @PutMapping("/user")
    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    public String putUser() {
        return "PUT-USER";
    }
}
  • 配置属性

上面说到,我们的表单会带着_method参数交给服务器的filter处理。这个filter就是,HiddenHttpMethodFilter。这个filter在WebMvcAutoConfiguration中有配置。

WebMvcAutoConfiguration 部分代码

	@Bean
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	// 只要配置 spring.mvc.hiddenmethod.filter.enabled=true,这个filter就可以加入到容器中
    @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }

application.yaml

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

OrderedHiddenHttpMethodFilter 继承自 HiddenHttpMethodFilter,所以直接看后者

HiddenHttpMethodFilter 部分代码

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)) {
            // _method参数全部变大写
            String method = paramValue.toUpperCase(Locale.ENGLISH);
            // ALLOWED_METHODS包括:PUT.DELETE.PATCH
            if (ALLOWED_METHODS.contains(method)) {
                // 里面重写了getMethod方法,使之返回_method参数
                requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
            }
        }
    }

    // 放行
    filterChain.doFilter((ServletRequest)requestToUse, response);
}
  • 我能换成其他的名字吗?不用_method

自定义filter

net.tiejiankudan.part03_web.config.Myconfig

@Configuration
public class Myconfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
        // 改成想要的
        filter.setMethodParam("_m");
        return filter;
    }
}
  • Rest使用客户端工具,如PostMan直接发送Put、delete等方式请求,无需Filter。直接就可以实现rest,所以filter默认是没有的
3.2 请求映射原理

学过SSM,我们都知道DispatcherServlet负责找到对应路径的Handler,所以只需研究清楚DispatcherServlet即可。

3.2.1 DispatcherServlet的继承树
  • Ctrl + N 搜索 DispatcherServlet 类,打开这个类,Ctrl + H 查看继承关系

在这里插入图片描述

  • 可以看出它继承自 Httpservlet ,这个相比都很熟悉了。它里面的doGet和doPost方法用来处理请求,
  • 所以经过一列盘查,最后由 DispatcherServlet 的 doDispatch 方法间接实现了处理请求的方法
  • 所以重点看 doDispatch 方法
3.2.2 doDispatche方法

doDispatche部分代码

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

getHandler方法

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        // handlerMappings 保存了很多handlerMapping
        Iterator var2 = this.handlerMappings.iterator();

        while(var2.hasNext()) {
            HandlerMapping mapping = (HandlerMapping)var2.next();
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }

    return null;
}

在这里插入图片描述

  • 然后程序会在这几个handlerMapping一一匹配

在这里插入图片描述

  • mappingRegistry里保存了映射关系

至于getHandler处理的细节以及如何自定义HandlerMapping,这次就先略过吧༼ つ ◕_◕ ༽つ

3.3 方法参数与基本注解
3.3.1 注解
  • @PathVariable

利用此注解可以拿到请求路径中的占位参数,搭配rest使用

net.tiejiankudan.part03_web.controll.AnnotationController

@RequestMapping("/car/{id}/owner/{name}")
@ResponseBody
public Map<String, Object> getCar(@PathVariable("id") int id,
                                  @PathVariable("name") String name,
                                  // 将所有的参数封装成map,类似“占位符”:"值"的键值对
                                  // 注意键值都得是String
                                  @PathVariable Map<String, String> pv) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("id", id);
    map.put("name", name);
    map.put("pv", pv);
    return map;
}


/************************************************************
	输入:http://localhost:8080/car/1/owner/leo
	输出:{"pv":{"id":"1","name":"leo"},"name":"leo","id":1}
	思考:
		路径变量mapping:/hehe/{temp} 
		普通路径mapping: /hehe/haha
		请求路径:/hehe/haha 访问谁?
		访问普通路径
*************************************************************/
  • @RequestHeader

利用此注解可以拿到请求头

net.tiejiankudan.part03_web.controll.AnnotationController

@RequestMapping("/header")
@ResponseBody
public Map<String, Object> getHeader(@RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String, String> pv) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("User-Agent", userAgent);
    map.put("pv", pv);
    return map;
}


/************************************************************
	输入:http://localhost:8080/header
	输出:{
          "pv": {
            "host": "localhost:8080",
            "connection": "keep-alive",
            "sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"90\", \"Microsoft Edge\";v=\"90\"",
            "sec-ch-ua-mobile": "?0",
            "upgrade-insecure-requests": "1",
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.66",
            "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
            "sec-fetch-site": "none",
            "sec-fetch-mode": "navigate",
            "sec-fetch-user": "?1",
            "sec-fetch-dest": "document",
            "accept-encoding": "gzip, deflate, br",
            "accept-language": "zh-CN,zh;q=0.9,en-GB;q=0.8,en-US;q=0.7,en;q=0.6",
            "cookie": "Idea-e0268f82=6f27a1af-cf8d-46bb-aafe-57ecbd8976d3; locale=zh; chii_theme=light"
          },
          "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.66"
        }
*************************************************************/
  • @RequestParam

利用此注解可以拿到请求参数

net.tiejiankudan.part03_web.controll.AnnotationController

@RequestMapping("/request")
@ResponseBody
public Map<String, Object> getRequestPara(@RequestParam("name") String name,
                                          @RequestParam("age") int age,
                                          @RequestParam Map<String, String> pv) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", name);
    map.put("age", age);
    map.put("pv", pv);
    return map;
}


/************************************************************
	输入:http://localhost:8080/request?name=leo&age=17
	输出:{"pv":{"name":"leo","age":"17"},"name":"leo","age":17}
*************************************************************/
  • @CookieValue

利用此注解可以拿到Cookie信息

net.tiejiankudan.part03_web.controll.AnnotationController

@RequestMapping("/cookie")
@ResponseBody
public Map<String, Object> getCookie(@CookieValue("locale") String localeS,
                                          @CookieValue("locale") Cookie locale,
                                          @CookieValue @RequestParam Map<String, String> cookie) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("locale", locale);
    map.put("localeS", localeS);
    map.put("cookie", cookie);
    return map;
}


/************************************************************
	输入:http://localhost:8080/cookie
	输出:{
          "localeS": "zh",
          "cookie": {},
          "locale": {
            "name": "locale",
            "value": "zh",
            "version": 0,
            "comment": null,
            "domain": null,
            "maxAge": -1,
            "path": null,
            "secure": false,
            "httpOnly": false
          }
        }
	结论:Cookie貌似不能自动封装成一个map
*************************************************************/
  • @RequestBody

post.html

<!DOCTYPE html>
<html lang="zh-CH">
<head>
    <meta charset="UTF-8">
    <title>post</title>
</head>
<body>
    <form method="post" action="/body">
        <div><input type="text" name="name" placeholder="用户名"></div>
        <div><input type="text" name="password" placeholder="密码"></div>
        <div><input type="submit" value="提交"></div>
    </form>
</body>
</html>

net.tiejiankudan.part03_web.controll.AnnotationController

@RequestMapping("/body")
@ResponseBody
public Map<String, Object> getRequestBody(@RequestBody String content) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("content", content);
    return map;
}


/************************************************************
	输入:http://localhost:8080/post.html
	输出:{"content":"name=%E5%BC%A0%E4%B8%89&password=2333"}
*************************************************************/
  • @RequestAttribute

利用这个注解可以得到request域中的数据

net.tiejiankudan.part03_web.controll.AnnotationController

@RequestMapping("/set")
public String setAttribute(HttpServletRequest request) {
    request.setAttribute("name", "leo");
    return "forward:/attribute";
}

@RequestMapping("/attribute")
@ResponseBody
public Map<String, Object> getAttribute(@RequestAttribute("name") String name) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", name);
    return map;
}


/************************************************************
	输入:http://localhost:8080/set
	输出:{"name":"leo"}
*************************************************************/
  • @MatrixVariable

利用这个注解可以获得矩阵变量

问:什么是矩阵变量?

答:跟在路径变量后面,形如 ;jessionid=xxxx;uid=xxx就叫矩阵变量,请记住一定要搭配路径变量使用,他们是一个组合

net.tiejiankudan.part03_web.controll.AnnotationController

@RequestMapping("/matrix/{user}")
@ResponseBody
public Map<String, Object> getAttribute(@MatrixVariable("age") String age,
                                        @MatrixVariable("gender") String gender,
                                        @PathVariable("user") String user) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("age", age);
    map.put("gender", gender);
    map.put("user", user);
    return map;
}

Spring-boot 默认将矩阵变量给忽略了,所以如果不配置的话,就会包找不到矩阵变量的错误。

所以在需要往容器中自定义WebMvcConfigurer

net.tiejiankudan.part03_web.config.Myconfig

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

再次访问

/************************************************************
	输入:http://localhost:8080/matrix/leo;gender=male;age=19
	输出:{"gender":"male","user":"leo","age":"19"}
*************************************************************/

如果有多个路径变量,每个路径变量都可以有自己的矩阵变量

net.tiejiankudan.part03_web.controll.AnnotationController

@RequestMapping("/matrix/{user1}/{user2}")
@ResponseBody
// 用pathVar决定取哪个路径变量的矩阵变量
public Map<String, Object> getAttributes(@MatrixVariable(value = "age", pathVar = "user1") String age1,
                                         @MatrixVariable(value = "gender", pathVar = "user1") String gender1,
                                         @MatrixVariable(value = "age", pathVar = "user1") String age2,
                                         @MatrixVariable(value = "gender", pathVar = "user1") String gender2) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("age1", age1);
    map.put("gender1", gender1);
    map.put("age2", age2);
    map.put("gender2", gender2);
    return map;
}


/************************************************************
	输入:http://localhost:8080/matrix/leo;gender=male;age=19/difa;gender=female;age=18
	输出:{"gender1":"male","gender2":"female","age2":"18","age1":"19"}
*************************************************************/
  • @ModelAttribute

此注解标注的方法会在每个controller调用之前调用一次,利用这个注解可以往模型里放数据,同一次请求,不管是从request域中还是model域中都能获得之前存放的数据。如果直接往model里放数据,如果转发到其它controller不一定能获得,不过倒是可以通过request域获得。

3.3.2 Servlet API

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

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) ||
            InputStream.class.isAssignableFrom(paramType) ||
            Reader.class.isAssignableFrom(paramType) ||
            HttpMethod.class == paramType ||
            Locale.class == paramType ||
            TimeZone.class == paramType ||
            ZoneId.class == paramType);
}
3.3.2 复杂参数
  • Map、**Model(map、model里面的数据在方法返回后,返回值响应前会被放在request的请求域中)、**Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
  • Map 和 Model 其实参数解析器解析的结果是同一个,都是**mavContainer.getModel()**的返回值——BindingAwareModelMap
3.3.3 自定义对象参数

自定义对象可以自动完成封装,支持级联封装

person.html

<!DOCTYPE html>
<html lang="zh-CH">
<head>
    <meta charset="UTF-8">
    <title>person</title>
</head>
<body>
    <form action="/person" method="post">
        姓名: <input name="userName" type="text"/> <br/>
        年龄: <input name="age" type="text"/> <br/>
        // 时间格式XXXX/XX/XX XX:XX:XX 类型转换器的问题
        生日: <input name="birth" type="text"/> <br/>
        宠物姓名:<input name="pet.name" type="text"/><br/>
        宠物年龄:<input name="pet.age" type="text"/><br>
        <input type="submit" value="提交"/>
    </form>
</body>
</html>

Person.java和Pet.java

@Data
public class Person {
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
}

@Data
public class Pet {
    private String name;
    private String age;
}

net.tiejiankudan.part03_web.controll.AnnotationController

@RequestMapping("/person")
@ResponseBody
public Person getPerson(Person person) {
    return person;
}


/************************************************************
	输入:http://localhost:8080/person.html
	输出:{"userName":"张三","age":19,"birth":"2003-12-14T16:00:00.000+00:00","pet":{"name":"汤姆","age":"3"}}
*************************************************************/
3.3.4 自定义类型转换器

net.tiejiankudan.part03_web.config.Myconfig

@Bean
public WebMvcConfigurer webMvcConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addConverter(new Converter<String, Pet>() {
                @Override
                public Pet convert(String s) {
                    String[] split = s.split(";");
                    Pet pet = new Pet();
                    pet.setName(split[0]);
                    pet.setAge(split[1]);
                    return pet;
                }
            });
        }
    };
}
3.4 参数处理原理
  • 在HandlerMapping中找到能够处理请求的Handler
    • mappedHandler = this.getHandler(processedRequest);
  • 为当前Handler找到一个适配器HandlerAdapter
    • HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
  • 适配器执行方法并确定方法参数的每一个值
3.4.1 HandlerAdpater

在这里插入图片描述

其中,RequestMappingHandlerAdapter 支持标注@RequestMapping 的方法。

3.4.2 执行目标方法
// ha 就是HandlerAdpater
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  • 进入此方法后
// 发现有执行此方法
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {}
  • 进入发现handleInternal的核心方法是
// mav 就是要返回的ModeAndView
mav = this.invokeHandlerMethod(request, response, handlerMethod);
  • 进入上面的方法内观察
// 做了一系列准备工作后,执行此方法
invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
  • 进入上面的方法,发现执行下面方法就得到返回值了
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
  • 所以继续进入上面的方法
// 一上来就调用方法确定了传入参数值
Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
  • 进入这个得到args的方法,查看他是怎么得到的
// parameters 保存了所有参数的详细信息,如变量类型,参数名,注解信息
MethodParameter[] parameters = this.getMethodParameters();
...
// 判断参数解析器是否支持这个参数的解析
if (!this.resolvers.supportsParameter(parameter)) {
	throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
  • 上面提到了参数解析器,那是什么?根据标注的注解和参数类型会有这么多解析器

在这里插入图片描述

  • 让我们进入this.resolvers.supportsParameter(parameter)内部,看一看是怎么判断的
// 原来就是判断是否能由当前参数得到一个解析器
this.getArgumentResolver(parameter) != null;
  • 在进入内部,看它是怎么得到解析器的
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = (HandlerMethodArgumentResolver)this.argumentResolverCache.get(parameter);
    if (result == null) {
        Iterator var3 = this.argumentResolvers.iterator();

        while(var3.hasNext()) {
            // 就是遍历所有的参数解析器,调用各自的supportsParameter方法判断而已,至于怎么判断的略过
            HandlerMethodArgumentResolver resolver = (HandlerMethodArgumentResolver)var3.next();
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                this.argumentResolverCache.put(parameter, resolver);
                break;
            }
        }
    }

    return result;
}
  • 现在我们得到了相应的解析器并加入到了缓存当中,现在我们回到getMethodArgumentValues方法,就是上面判断是否存在一个解析器解析参数的方法中
// 用参数解析器解析参数
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
  • 现在我们就得到了所有的参数值了,回到invokeForRequest方法,利用反射调用方法得到返回值

  • 最后我们回到invokeAndHandle方法,就一目了然了

// 经过了这么多步,我们终于得到了返回值
Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
  • 得到返回值后我们又要调用返回值处理器,来处理返回值。下面列出了所有返回值处理器

在这里插入图片描述

  • 同样的方式,遍历所有处理器看哪个能处理就给他处理,后续的步骤就略过了o( ̄▽ ̄)d
3.5 POJO封装过程

pojo参数的处理过程,只不过pojo是复杂数据类型所以用到的参数解析器是ServletModelAttributeMethodProcessor,利用这解析器就将我们自定义的对象封装好了,然后传给方法执行。那么这个解析器是怎么工作的呢?

  • 由 **WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name)**创建出binder

  • WebDataBinder:web数据绑定器,将请求参数的值绑定到指定的JavaBean里面

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

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

3.6 处理派发结果

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值