请求参数处理
0、请求映射
1、rest使用与原理
- @xxxMapping;
- Rest风格支持(使用HTTP请求方式动词来表示对资源的操作) - 以前:*/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser *保存用户- *现在: /user **GET-**获取用户 **DELETE-**删除用户 **PUT-**修改用户 **POST-*保存用户
// @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-张三";
}
- 核心Filter;HiddenHttpMethodFilter - 用法: 表单只能发送get和post请求,那么怎么发送post和delete请求呢?表单发送方式method=post,带上一个隐藏域 _method=put
WebMvcAutoConfiguration中是否通过hiddenHttpMethodFilter来进行表单提交方式的验证默认是false不开启
手动开启页面表单的Rest功能
- 扩展:如何把_method 这个名字换成我们自己喜欢的。
WebMvcAutoConfiguration中因为我们没有HiddenHttpMethodFilter这个过滤器,所以为我们new了一个,而HiddenHttpMethodFilter内部的DEFAULT_METHOD_PARAM就是被设置为"_method"
因此我们自己写一个HiddenHttpMethodFilter来修改这个参数
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}
}
===================index.html=================
<form method="post" action="/user">
<!-- <input type="hidden" name="_method" value="DELETE"/>-->
<input type="hidden" name="_m" value="DELETE"/>
<input type="submit" value="post-保存用户"/>
</form>
Rest原理(表单提交要使用REST的时候)
●表单提交会带上_method=PUT
●请求过来被HiddenHttpMethodFilter拦截
○请求是否正常,并且是POST
■获取到_method的值。
■兼容以下请求;PUT、DELETE、PATCH
ALLOWED_METHODS.contains(method)代表是不是兼容的网页请求方式
■原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
■过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
Rest使用客户端工具
●如PostMan直接发送Put、delete等方式请求,无需Filter。
1、普通参数与基本注解
1.1、注解:
@PathVariable、@RequestHeader,点进这两个注解,都可以看到以下这段注释
意思是可以在参数中使用一个Map<String, String> 用对应的注解标记,那么它会帮将所有的参数放进去,比如PathVariable会将所有的路径变量放进对应的map,而RequestHeader则是将所有的headers信息全部放进map中
@RestController
public class ParameterTestController {
@GetMapping("/car/{id}/user/{name}")
public Map<String, Object> getCar(@PathVariable("id") Integer id,
@PathVariable("name") String name,
@PathVariable Map<String, String> pv,
@RequestHeader("User-Agent") String useragent,
@RequestHeader Map<String, String> headers) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("id", id);
map.put("name", name);
map.put("pv", pv);
map.put("user-agent", useragent);
map.put("headers", headers);
return map;
}
}
@RequestParam
@GetMapping("/like")
public Map<String, Object> getCar(@RequestParam("age") Integer age,
@RequestParam("hobby") List<String> hobby) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("age", age);
map.put("hobby", hobby);
return map;
}
@RequestBody
@PostMapping("/save")
public Map<String, Object> save(@RequestBody String content) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("content", content);
return map;
}
@RequestAttribute 获取request域属性
@Controller
public class RequestAttributeTestController {
@GetMapping("/goto")
public String gotoPage(HttpServletRequest request) {
request.setAttribute("msg","页面转发成功");
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map<String, Object> success(HttpServletRequest request,
@RequestAttribute("msg") String msg) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("req",msg);
String msg1 = (String) request.getAttribute("msg");
map.put("anno", msg1);
return map;
}
}
@MatrixVariable 矩阵变量,SpringBoot默认关闭,需要手动开启。在WebMvcAutoConfiguration有一个方法configurePathMatch()设置了UrlPathHelper
在 UrlPathHelper有一个变量removeSemicolonContent控制着矩阵变量的开启,意思是移除分号内容,默认为true,所以为了开启,我们需要自定义
可以在配置类中继承WebMvcConfigurer重载configurePathMatch方法,我们为其安上UrlPathHelper,也可以创建一个WebMvcConfigurer放入容器
@Configuration(proxyBeanMethods = false)
public class WebConfig /*implements WebMvcConfigurer*/ {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}
// @Override
// public void configurePathMatch(PathMatchConfigurer configurer) {
// UrlPathHelper urlPathHelper = new UrlPathHelper();
// urlPathHelper.setRemoveSemicolonContent(false);
// configurer.setUrlPathHelper(urlPathHelper);
// }
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
};
}
}
//1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
//2、SpringBoot默认是禁用了矩阵变量的功能
// 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
// removeSemicolonContent(移除分号内容)支持矩阵变量的
//3、矩阵变量必须有url路径变量才能被解析
@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;
}
// 两个矩阵变量就使用pathvar
// /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;
}
矩阵变量的作用:
当cookie被禁用后,如何使用session的内容?
因为session.getAttribute实际上流程是
session.set(a,b)---> jsessionid ---> cookie ----> 每次发请求携带ur1重写:所以cookie禁用后无法找到session
解决方法:
/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递
1.2、Servlet API:
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
@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);
}
ServletRequestMethodArgumentResolver 以上的部分参数
1.3、复杂参数:
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、Model类型的参数,会返回 mavContainer.getModel();---> BindingAwareModelMap 是Model 也是Map
mavContainer.getModel(); 获取到值的
1.4、自定义对象参数:
可以自动类型转换与格式化,可以级联封装。
/**
* 姓名: <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 String age;
}
result
2、POJO封装过程
- ServletModelAttributeMethodProcessor
3、参数处理原理
我们首先在DispatcherServlet的doDispatch方法这打断点
- HandlerMapping中找到能处理请求的Handler(Controller.method())
- 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
- 适配器执行目标方法并确定方法参数的每一个值
1、HandlerAdapter
0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的
xxxxxx
2、执行目标方法
// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法
//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
3、参数解析器-HandlerMethodArgumentResolver
确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
- 当前解析器是否支持解析这种参数
- 支持就调用 resolveArgument
4、返回值处理器
5、如何确定目标方法每一个参数的值
============InvocableHandlerMethod==========================
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;
}
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) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
5.1、挨个判断所有参数解析器那个支持解析这个参数
@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);
break;
}
}
}
return result;
}
** 5.2、解析这个参数的值**
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
5.3、自定义类型参数 封装POJO
ServletModelAttributeMethodProcessor 这个参数处理器支持解析POJO类参数
是否为简单类型。
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
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 (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
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)) {
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;
}
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean -- Integer)
byte -- > file
@FunctionalInterface **public interface **Converter<S, T>
未来我们可以给WebDataBinder里面放自己的Converter;
**private static final class **StringToNumber<T **extends **Number> **implements **Converter<String, T>
额外:自定义 Converter
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
版权归原作者 zoeil 所有, 如有侵权,请联系我们删除。