文章目录
一、前言二、类型转换器Converter1、自定义类型转换器 三、参数解析器1、自定义分页参数解析器2、自定义注解参数解析器一、前言
Spring MVC源码分析相关文章已出:
Spring MVC <Form>表单中支持REST风格DELETE、PUT类型方法的方式和原理Spring MVC请求执行流程Spring MVC如何将请求映射到Controller使用FastJsonHttpMessageConverter解析@RequestBody参数Spring MVC多种请求入参处理方式都在这了
更多Spring系列源码分析文章见SpringBoot专栏:
精通Spring Boot
二、类型转换器Converter
Spring官方文档
Spring 3.0 引入了一个core.convert
,并提供通用类型转换系统的包。系统定义了一个 SPI 来实现类型转换逻辑,并定义一个 API 来在运行时执行类型转换。
这套类型转换系统的顶层接口为:Converter
@FunctionalInterfacepublic interface Converter<S, T> {/*** Convert the source object of type {@code S} to target type {@code T}.* @param source the source object to convert, which must be an instance of {@code S} (never {@code null})* @return the converted object, which must be an instance of {@code T} (potentially {@code null})* @throws IllegalArgumentException if the source cannot be converted to the desired target type*/@NullableT convert(S source);}
Converter的作用是将类型S
转换为T
,在参数解析器中使用该接口的实现类 将前端请求参数 转换成 控制器方法(Controller#Method) 需要的参数类型。
此外,还有ConverterFactory<S, R>
(将类型S 转换为 R及其子类型)、ConversionService
(用来在运行时执行类型转换);
Spring 提供的所有支持的类型转换器实现类都在org.springframework.core.convert.support
包下,大多数转换器的convert()方法都很简单,感兴趣可以自己看一下源码。
比如:
StringToCollectionConverter将String转换为集合;例如:ids -> 1,2,3
能够用List<Long>
接收;StringToBooleanConverter将String转换为Boolean,例如:enable -> no
能够用Boolean
接收;
1、自定义类型转换器
要实现的效果:
请求中传递JavaModel数据,多个属性之间以英文逗号,
分隔;
比如:POST请求http://127.0.0.1:38888/person/other?person=saint,15,true,otherInfo&other=hahaha
后端Controller的方法中使用JavaModel接收;
1> Java Model类:
public class Person {private String name;private Integer age;private Boolean sex;private String otherInfo;}
2> 自定义Converter:
逻辑很简单,就是将Spring字符串用英文逗号,
分隔,按Person类的属性顺序,将请求中的参数填充到Person对象中。
@Configurationpublic class WebMvcConfig {/*** 自定义参数类型转换器* WebMvcConfigurer#addFormatters()方法用来添加自定义的参数类型转换器;*/@Beanpublic WebMvcConfigurer webMvcConfigurer() {return new WebMvcConfigurer() {@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverter(new Converter<String, Person>() {@Overridepublic Person convert(String source) {if (StringUtils.isEmpty(source)) {return null;}final String[] sourceArgs = source.split(",");Person person = new Person();person.setName(sourceArgs[0]);person.setAge(Integer.valueOf(sourceArgs[1]));person.setSex(Boolean.valueOf(sourceArgs[2]));person.setOtherInfo(String.valueOf(sourceArgs[3]));return person;}});}};}}
3> controller方法:
@PostMapping("/person/other")public String otherPerson(Person person, String other) {return person.toString() + other;}
4> 效果:
三、参数解析器
Spring中参数解析器的最上层接口为HandlerMethodArgumentResolver,其中有两个方法:
supportsParameter() 判断当前参数解析器是否支持解析的方法参数;resolveArgument() 从请求数据中解析出当前方法参数对应的参数值。
public interface HandlerMethodArgumentResolver {/*** Whether the given {@linkplain MethodParameter method parameter} is* supported by this resolver.*/boolean supportsParameter(MethodParameter parameter);/*** Resolves a method parameter into an argument value from a given request.* A {@link ModelAndViewContainer} provides access to the model for the* request. A {@link WebDataBinderFactory} provides a way to create* a {@link WebDataBinder} instance when needed for data binding and* type conversion purposes.*/@NullableObject resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;}
1、自定义分页参数解析器
前端请求传递两个分页参数 pageNum、pageSize,后端用一个IPage模型接收;
参数解析器策略要支持的是 IPage.class,核心是 HandlerMethodArgumentResolver 的两个方法实现。
这里基于spring-data-common
包下的PageableHandlerMethodArgumentResolver
做一个扩展。
1> maven依赖:
<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-commons</artifactId></dependency>
2> 自定义参数解析器PageHandlerMethodArgumentResolver
package com.saint.config;import com.saint.model.IPage;import org.springframework.core.MethodParameter;import org.springframework.data.domain.PageRequest;import org.springframework.data.domain.Pageable;import org.springframework.data.web.PageableHandlerMethodArgumentResolver;import org.springframework.web.bind.support.WebDataBinderFactory;import org.springframework.web.context.request.NativeWebRequest;import org.springframework.web.method.support.HandlerMethodArgumentResolver;import org.springframework.web.method.support.ModelAndViewContainer;/*** 自定义分页参数解析器** @author Saint*/public class PageHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {private static final int DEFAULT_PAGE = 0;private static final int DEFAULT_SIZE = 10;private static final String DEFAULT_PAGE_PARAM = "pageNum";private static final String DEFAULT_SIZE_PARAM = "pageSize";/*** 组合 `spring-data-commons` 包下的PageableHandlerMethodArgumentResolver,实现分页参数解析功能*/private final PageableHandlerMethodArgumentResolver pageableArgumentResolver;public PageHandlerMethodArgumentResolver() {PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();resolver.setFallbackPageable(PageRequest.of(DEFAULT_PAGE, DEFAULT_SIZE));resolver.setSizeParameterName(DEFAULT_SIZE_PARAM);resolver.setPageParameterName(DEFAULT_PAGE_PARAM);resolver.setOneIndexedParameters(true);this.pageableArgumentResolver = resolver;}@Overridepublic boolean supportsParameter(MethodParameter parameter) {return IPage.class.equals(parameter.getParameterType());}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {Pageable pageable =pageableArgumentResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);return new IPage(pageable.getPageNumber() + 1, pageable.getPageSize());}}
3> 将自定义的参数解析器添加到Spring MVC 的参数解析器集合中:
@Configurationpublic class WebMvcConfig {@Beanpublic WebMvcConfigurer webMvcConfigurer() {return new WebMvcConfigurer() {@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(new PageHandlerMethodArgumentResolver());}};}}
4> Controller方法:
/*** http://localhost:38888/page?pageNum=9&pageSize=20*/@GetMapping("/page")public String page(IPage page) {return page.toString();}
5> 效果:
GET /xxx?pageNum=1&pageSize=10
请求的分页参数被解析成了IPage对象
2、自定义注解参数解析器
自定义一个注解用于标注某个JavaModel,JavaModel中的信息是根据请求中的某些数据间接得到。
1> 自定义的注解@UserParam:
@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)public @interface UserParam {}
2> 自定义的JavaModel:
@Data@NoArgsConstructor@AllArgsConstructorpublic class UserInfo {private String userCode;private String userName;private String address;}
3> 自定义的参数解析器:
package com.saint.config;import com.saint.annotation.UserParam;import com.saint.model.UserInfo;import com.saint.service.UserInfoService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.MethodParameter;import org.springframework.web.bind.support.WebDataBinderFactory;import org.springframework.web.context.request.NativeWebRequest;import org.springframework.web.method.support.HandlerMethodArgumentResolver;import org.springframework.web.method.support.ModelAndViewContainer;import javax.servlet.http.HttpServletRequest;/*** @author Saint*/public class UserInfoArgumentResolver implements HandlerMethodArgumentResolver {@Autowiredprivate UserInfoService userInfoService;@Overridepublic boolean supportsParameter(MethodParameter methodParameter) {return methodParameter.getParameterType() == UserInfo.class&& methodParameter.hasParameterAnnotation(UserParam.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);String token = request.getHeader("token");UserInfo userInfo = userInfoService.getUserInfoByToken(token);return userInfo;}}
4> UserInfoService:
package com.saint.service;import com.saint.model.UserInfo;import org.springframework.stereotype.Service;/*** @author Saint*/@Servicepublic class UserInfoService {public UserInfo getUserInfoByToken(String token) {// todo 调用用户中心返回用户的信息UserInfo user = new UserInfo("code01", "saint", "你心里 " + token);return user;}}
5> 将自定义的参数解析器添加到Spring MVC 的参数解析器集合中:
一定要先将自定义的参数解析器实例化一个Bean到Spring容器中,否则其中无法调用其他SpringBean。
package com.saint.config;import com.saint.model.Person;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.convert.converter.Converter;import org.springframework.format.FormatterRegistry;import org.springframework.util.StringUtils;import org.springframework.web.method.support.HandlerMethodArgumentResolver;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;/*** @author Saint*/@Configurationpublic class WebMvcConfig {@Beanpublic UserInfoArgumentResolver getUserInfoArgumentResolver() {return new UserInfoArgumentResolver();}@Beanpublic WebMvcConfigurer webMvcConfigurer() {return new WebMvcConfigurer() {@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(getUserInfoArgumentResolver());}};}}
6> 效果: