springboot学习11——springmvc(中)

一、处理器映射

Web工程使用Spring MVC,在启动阶段会将注解@RequestMapping所配置的内容保存到处理器映射(HandlerMapping)机制中去,然后等待请求的到来,
通过拦截请求信息与HandlerMapping进行匹配,找到对应的处理器(它包含控制器的逻辑),并将处理器及其拦截器保存到HandlerExecutionChain对象中,返回给DispatcherServlet,这样DispatcherServlet就可以运行它们了。

HandlerMapping:将请求定位到具体的处理器上,了解下它的源码。

package org.springframework.web.bind.annotation;
/**** imports ****/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
    // 配置请求映射名称
    String name() default "";

    // 通过路径映射
    @AliasFor("path")
    String[] value() default {};

    // 通过路径映射回path配置项
    @AliasFor("value")
    String[] path() default {};

    // 限定只响应HTTP请求类型,如GET、POST、HEAD、OPTIONS、PUT、 TRACE等
    // 默认的情况下,可以响应所有的请求类型
    RequestMethod[] method() default {};

    // 当存在对应的HTTP参数时才响应请求
    String[] params() default {};

    // 限定请求头存在对应的参数时才响应
    String[] headers() default {};

    // 限定HTTP请求体提交类型,如"application/json"、"text/html"
    String[] consumes() default {};

    // 限定返回的内容类型,仅当HTTP请求头中的(Accept)类型中包含该指定类型时才返回
    String[] produces() default {};
}

value或者path:必须配置项,设置请求的URL,从而让对应的请求映射到控制器或其方法上(value和path可以通过正则表达式,但不建议,原则上希望一个路径对应一个方法,这样代码可读性更高,便于维护)。

method:常用的配置项,可以限定HTTP的请求类型,区分HTTP的GET或者POST等不同的请求。在Spring 4.3的版本之后,为了简化method配置项的配置新增了几个注解,如@GetMapping、@PostMapping、@PatchMapping、@PutMapping和@DeleteMapping。
@GetMapping对应的是HTTP的GET方法,@PostMapping对应的是HTTP的POST方法,其他的配置项则与@RequestMapping无太大的区别,通过它们就可以不再设置@RequestMapping的method配置项了。

二、获取控制器参数

处理器Handler是对控制器的包装,在处理器运行的过程中会调度控制器的方法,只是它在进入控制器方法之前会对HTTP的参数和上下文进行解析,将它们转换为控制器所需的参数。
针对HTTP的参数与方法参数的转换,Spring MVC已提供了大量的转换规则,处理器可以直接使用。

1.无注解下获取参数:在没有注解的情况下,Spring MVC也可以获取参数,参数允许为空,唯一的要求是参数名称和HTTP请求的参数名称保持一致。

/**
 * 在无注解下获取参数,要求参数名称和HTTP请求参数名称一致
 * @param intVal  -- 整数
 * @param longVal -- 长整型
 * @param str --字符串
 * @return 响应JSON参数
 */
@GetMapping("/no/annotation")
@ResponseBody
public Map<String, Object> noAnnotation(
		Integer intVal, Long longVal, String str) {
	Map<String, Object> paramsMap = new HashMap<>();
	paramsMap.put("intVal", intVal);
	paramsMap.put("longVal", longVal);
	paramsMap.put("str", str);
	return paramsMap;
}

测试:http://localhost:8080/paramTest/no/annotation?int_val=1&long_val=2&str_val=str

2.使用@RequestParam获取参数:注解@RequestParam可以指定HTTP参数和方法参数的映射关系,注意默认的情况下@RequestParam标注的参数是不能为空的,为了让它能够为空,可以配置其属性required为false,即@RequestParam(value=“str_val”, required = false) String strVal。

/**
 * 通过注解@RequestParam获取参数
 * @param intVal  -- 整数
 * @param longVal -- 长整型
 * @param strVal --字符串
 * @return 响应JSON数据集
 */
@GetMapping("/requestParam")
@ResponseBody
public Map<String, Object> requestParam(
		@RequestParam("int_val") Integer intVal,
		@RequestParam("long_val") Long longVal,
		@RequestParam("str_val") String strVal) {
	Map<String, Object> paramsMap = new HashMap<>();
	paramsMap.put("intVal", intVal);
	paramsMap.put("longVal", longVal);
	paramsMap.put("strVal", strVal);
	return paramsMap;
}

测试:http://localhost:8080/paramTest/requestParam?int_val=1&long_val=2&str_val=str

3.传递数组:Spring MVC内部能够支持用逗号分隔的数组参数

@GetMapping("/requestArray")
@ResponseBody
public Map<String, Object> requestArray(
        int [] intArr, Long []longArr, String[] strArr) {
    Map<String, Object> paramsMap = new HashMap<>();
    paramsMap.put("intArr", intArr);
    paramsMap.put("longArr", longArr);
    paramsMap.put("strArr", strArr);
    return paramsMap;
}

测试:http://localhost:8080/paramTest/requestArray?intArr=1,2,3&longArr=4,5,6&strArr=str1,str2,str3

4.传递JSON:方法的参数标注为@RequestBody,意味着它将接收前端提交的JSON请求体,而在JSON请求体与User类之间的属性名称是保持一致的,这样Spring MVC就会通过这层映射关系将JSON请求体转换为User对象。

@PostMapping("/insert")
@ResponseBody
public AppUserEntity insert(@RequestBody AppUserEntity user) {
	//假装新增了
	return user;
}

测试:http://localhost:8080/paramTest/insert
其中body:{“name”:“aa”}
注意:
1.User对象要有一个有参数的构造器,可以使用注解@AllArgsConstructor
2.请求头中要有:Content-Type:application/json,不然会报415,传入类型不对

5.通过URL传递参数:Spring MVC对此也提供了良好的支持,可以通过处理器映射和注解@PathVariable的组合获取URL参数

@GetMapping("/{id}")
@ResponseBody
public AppUserEntity get(@PathVariable("id") Integer id) {
	return AppUserEntity.builder().id(id).build();
}

测试:http://localhost:8080/paramTest/2

6.获取格式化参数
在一些应用中,往往需要格式化数据,其中最为典型的当属日期和货币。
例如,在一些系统中日期格式约定为yyyy-MM-dd,金额约定为货币符号和用逗号分隔,如100万美元写作$1,000,000.00等。

Spring MVC也对此提供了良好的支持。对日期和数字类型的转换注解进行处理,分别是@DateTimeFormat和@NumberFormat。
@DateTimeFormat是针对日期进行格式化的,
@NumberFormat则是针对数字进行格式化的。

format方法参数加粗的代码使用了注解@DateTimeFormat和@NumberFormat,它们配置了格式化所约定的格式,所以Spring会根据约定的格式把数据转换出来,这样就可以完成参数的转换。
测试:http://localhost:8080/paramTest/format

在Spring Boot中,日期参数的格式化也可以不使用@DateTimeFormat,而只在配置文件application.properties中加入如下配置项即可:
spring.mvc.date-format=yyyy-MM-dd

三、自定义参数转换规则

HTTP的请求包含请求头(Header)、请求体(Body)、URL和参数等内容,服务器还包含其上下文环境和客户端交互会话(Session)机制,而这里的消息转换是指请求体的转换。

1.处理器获取参数逻辑
当一个请求来到时,在处理器Handler执行的过程中,它首先会从HTTP请求和上下文环境来得到参数。如果是简易的参数它会以简单的转换器进行转换,而这些简单的转换器是Spring MVC自身已经提供了的。
但是如果是转换HTTP请求体(Body),它就会调用HttpMessageConverter接口的方法对请求体的信息进行转换,首先它会先判断能否对请求体进行转换,如果可以就会将其转换为Java类型。

HttpMessageConverter接口源码

package org.springframework.http.converter;
/**** imports ****/
public interface HttpMessageConverter<T> {
    // 是否可读,其中clazz为Java类型,mediaType为HTTP请求类型
    boolean canRead(Class<?> clazz, MediaType mediaType);

	// 判断clazz类型是否能够转换为mediaType媒体类型
	// 其中clazz为java类型,mediaType为HTTP响应类型
    boolean canWrite(Class<?> clazz, MediaType mediaType);

    // 可支持的媒体类型列表
    List<MediaType> getSupportedMediaTypes();

    // 当canRead验证通过后,读入HTTP请求信息
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;

    //当canWrite方法验证通过后,写入响应
    void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;
}

这里需要讨论的是canRead和read方法,canWrite和write方法将在后续讨论。

如果代码中控制器方法的参数标注了@RequestBody,处理器会采用请求体(Body)的内容进行参数转换,而前端的请求体为JSON类型,所以首先它会调用canRead方法来确定请求体是否可读。
如果判定可读后,接着就是使用read方法,将前端提交的用户JSON类型的请求体转换为控制器的类型参数,这样控制器就能够得到参数了。

在Spring MVC中,是通过WebDataBinder机制来获取参数的,它的主要作用是解析HTTP请求的上下文,然后在控制器的调用之前转换参数并且提供验证的功能,为调用控制器方法做准备。
处理器会从HTTP请求中读取数据,然后通过三种接口来进行各类参数转换,这三种接口是Converter、Formatter和GenericConverter。

在Spring MVC的机制中这三种接口的实现类都采用了注册制,默认情况下Spring MVC已经在注册机内注册了许多的转换器,可以实现大部分的数据类型的转换,
所以大部分的情况下无须开发者再提供转换器,同样地,当需要自定义转换规则时,只需要在注册机上注册自己的转换器就可以了。

实际上,WebDataBinder机制还有一个重要的功能,那就是验证转换结果。关于验证机制,后面会再讨论。有了参数的转换和验证,最终控制器就可以得到合法的参数。
得到这些参数后,就可以调用控制器的方法了。

HTTP请求体(Body)的消息转换全流程:
在这里插入图片描述

这个图严格来说是请求体转换的全流程,但是有些时候Spring MVC并不会走完全流程,而是根据现实情况来处理消息的转换。
控制器的参数是处理器通过Converter、Formatter和GenericConverter这三个接口转换出来的,先谈谈这三个接口的不同之处。
Converter:一个普通的转换器,例如,有一个Integer类型的控制器参数,而从HTTP对应的为字符串,对应的Converter就会将字符串转换为Integer类型;
Formatter:一个格式化转换器,类似那些日期字符串就是通过它按照约定的格式转换为日期的;
GenericConverter:将HTTP参数转换为数组。

对于数据类型转换,Spring MVC提供了一个服务机制去管理,它就是ConversionService接口。在默认的情况下,会使用这个接口的子类DefaultFormattingConversionService对象来管理这些转换类。
Converter、Formatter和GenericConverter可以通过注册机接口进行注册,这样处理器就可以获取对应的转换器来实现参数的转换。

上面讨论的是普通Spring MVC的参数转换规则,而在Spring Boot中还提供了特殊的机制来管理这些转换器。
Spring Boot的自动配置类WebMvcAutoConfiguration还定义了一个内部类WebMvcAutoConfigurationAdapter,
在这里插入图片描述

ConversionService转化机制设计

Spring Boot的自动注册机制

// 注册各类转换器,registry实际为DefaultFormattingConversionService对象
@Override
public void addFormatters(FormatterRegistry registry) {
    // 遍历IoC容器,找到Converter类型的Bean注册到服务类中
    for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
        registry.addConverter(converter);
    }
    // 遍历IoC容器,找到GenericConverter类型的Bean注册到服务类中
    for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
		registry.addConverter(converter);
    }
    // 遍历IoC容器,找到Formatter类型的Bean注册到服务类中
    for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
        registry.addFormatter(formatter);
    }
}

通过这个方法,在Spring Boot的初始化时,会将对应用户自定义的Converter、Formatter和GenericConverter的实现类所创建的Spring Bean自动地注册到DefaultFormattingConversionService对象中。
这样对于开发者只需要自定义Converter、Formatter和GenericConverter的接口的Bean,Spring Boot就会通过这个方法将它们注册到ConversionService对象中。只是格式化Formatter接口,在实际开发中使用率比较低,所以不再论述。

2.一对一转换器(Converter)
Converter是一对一的转化器,也就是从一种类型转换为另外一种类型。

Converter接口源码

package org.springframework.core.convert.converter;
public interface Converter<S, T> {
    // 转换方法,S代表原类型,T代表目标类型
    T convert(S source);
}

这个接口的类型有源类型(S)和目标类型(T)两种,它们通过convert方法进行转换。
例如,HTTP的类型为字符串(String)型,而控制器参数为Long型,那么就可以通过Spring内部提供的StringToNumber进行转换。

假设前端要传递一个用户的信息,这个用户信息的格式是{id}-{userName}-{note},而控制器的参数是User类对象。因为这个格式比较特殊,Spring当前并没有对应的Converter进行转换,
因此需要自定义转换器。这里需要的是一个从String转换为User的转换器。

字符串用户转换器

package com.springboot.chapter10.converter;
/**** imports ****/
/**
 * 自定义字符串用户转换器
 */
@Component
public class StringToUserConverter implements Converter<String, User> {
    /**
     * 转换方法
     */
    @Override
    public User convert(String userStr) {
        User user = new User();
        String []strArr = userStr.split("-");
        Long id = Long.parseLong(strArr[0]);
        String userName = strArr[1];
        String note = strArr[2];
        user.setId(id);
        user.setUserName(userName);
        user.setNote(note);
        return user;
    }
}

这里的类标注为@Component,并且实现了Converter接口,这样Spring就会将这个类扫描并装配到IoC容器中。
对于Spring Boot,之前分析过它会在初始化时把这个类自动地注册到转换机制中,所以注册这步并不需要人工再处理。
这里泛型指定为String和User,这样Spring MVC就会通过HTTP的参数类型(String)和控制器的参数类型(User)进行匹配,就可以从注册机制中发现这个转换类,这样就能够将参数转换出来。

使用控制器方法接收用户参数

@GetMapping("/converter")
@ResponseBody
public User getUserByConverter(User user) {
    return user;
}

测试:http://localhost:8080/user/converter?user=1-user_name_1-note_1

3.GenericConverter集合和数组转换
GenericConverter是数组转换器。因为Spring MVC自身提供了一些数组转换器,需要自定义的并不多,所以这里只介绍Spring MVC自定义的数组转换器。
假设需要同时新增多个用户,这样便需要传递一个用户列表(List)给控制器。此时Spring MVC会使用StringToCollectionConverter转换它,这个类实现了GenericConverter接口,并且是Spring MVC内部已经注册的数组转换器。
它首先会把字符串用逗号分隔为一个个的子字符串,然后根据原类型泛型为String、目标类型泛型为User类,找到对应的Converter进行转换,将子字符串转换为User对象。
上节我们已经自定义了转换器StringToUserConverter,这样它就可以发现这个转换器,从而将字符串转换为User类。这样控制器就能够得到List类型的参数。

使用集合(List)传递多个用户

@GetMapping("/list")
@ResponseBody
public List<User> list(List<User> userList) {
    return userList;
}

这样只要在浏览器地址栏中输入URL请求这个方法:
http://localhost:8080/user/list?userList=1-user_name_1-note_1,2-user_name_2-note_2,3-user_name_3-note_3
这里的参数使用了一个个逗号分隔,StringToCollectionConverter在处理时也就通过逗号分隔,然后通过之前自定义的转换器StringToUserConverter将其变为用户类对象,再组成为一个列表(List)传递给控制器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值