目录
7.3.4.3 参数解析器HandlerMethodArgumentResolver
第七章 Web开发
7.3 请求参数处理
7.3.2 普通参数与基本注解
7.3.2.2 Servlet API
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
- ServletRequestMethodArgumentResolver 以上的部分参数,比如HttpServletRequest request就是由这个参数解析器来进行解析的。
@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);
}
7.3.2.3 复杂参数
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes(重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
@GetMapping("/params")
public String testParam(Map<String,Object> map,
Model model,
HttpServletRequest request,
HttpServletResponse response){
//给map、model添加数据就相当于在request域中添加数据
map.put("hello","world");
model.addAttribute("world","hello");
request.setAttribute("message","hello world");
Cookie cookie = new Cookie("c1","v1");
response.addCookie(cookie);
return "forward:/success";
}
//在"/success"的实现方法中可以通过request.getAttribute()方法来获取这些数据
- Map类型的参数解析器是MapMethodProcessor,Model类型的参数解析器是ModelMethodProcessor。都会返回 mavContainer.getModel();---> BindingAwareModelMap是Model也是Map。
- ModelAndViewContainer类下:
- 通过doDispatch()方法中对方法调用结束后的后续处理processDispatchResult方法将map和model的数据放到request域中。
7.3.2.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;
}
@PostMapping("/saveuser")
public Person saveuser(Person person) {
return person;
}
- 数据绑定:页面提交的请求数据(Get、Post)都可以和对象属性进行绑定。
7.3.3 POJO封装过程
参数解析器ServletModelAttributeMethodProcessor:解析自定义类型的参数。
- 先进入ModelAttributeMethodProcessor类内的supportsParameter方法判断是否是简单类型的参数
public static boolean isSimpleValueType(Class<?> type) {
return Void.class != type
&& Void.TYPE != 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);
}
- 使用对应的resolver对参数进行处理。
- ModelAttributeMethodProcessor类内的resolveArgument方法调用ServletModelAttributeMethodProcessor的createAttribute方法,会先创建出一个空实例对象person。
- 后续就要给这个空实例对象封装请求传过来的值,即数据绑定过程
- ModelAttributeMethodProcessor类内的resolveArgument方法中的核心代码:
if (bindingResult == null) {
//创建网页数据绑定器
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
//这一步就将请求里的值赋给了之前创建的空实例对象
this.bindRequestParameters(binder, webRequest);
}
this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
- 先创建一个web数据绑定器,将请求参数的值绑定到指定的JavaBean里面,而这个JavaBean就是attribute,即之前创建的空实例对象
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
-
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
-
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(如JavaBean的Integer类型),也有如何把字节流传换成文件byte -- > file
-
可以自定义Converters给WebDataBinder
-
private static final class StringToNumber<T extends Number> implements Converter<String, T>
泛型指的是把请求带来的String类型,转成自己想要的类型T
-
把请求中的数据绑定到对象上
bindRequestParameters(binder, webRequest);
7.3.3.1 自定义Converter转换器
假设此时不使用级联属性提交宠物,而是规定提交方式,逗号前是宠物名字,逗号后是宠物年龄
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@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;
}
});
}
};
}
7.3.4 参数处理原理
DispatcherServlet类中的方法doDispatcher是处理所有请求的起点:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
//这一步就是之前说的找到了处理请求的映射规则,即哪个方法可以处理这个请求
//mappedHandler封标了目标方法的信息
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
//mappedHandler.getHandler()得到哪个方法可以处理这个请求
//找到处理当前方法适合的适配器
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//真正指定handle,即执行当前请求对应的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
- HandlerMapping中找到能处理请求的Handler(Controller.method(),控制器中的哪个方法来处理当前请求)
- 为当前Handle找一个适配器HandlerAdapter。RequestMappingHandlerAdapter
7.3.4.1 HandlerAdapter适配器
HandlerAdapter是SpringMVC底层设计的一个接口,里面有几个方法:
public interface HandlerAdapter {
//是否支持处理这个handler
boolean supports(Object handler);
//支持的话就调用方法来处理
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/** @deprecated */
@Deprecated
long getLastModified(HttpServletRequest request, Object handler);
}
所有的HandlerAdapter如下:
- 0 - 支持方法上标注@RequestMapping
-
1 - 支持函数式编程的
遍历所有的HandlerAdapter,利用supports方法找到合适的那个HandlerAdapter:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
Iterator var2 = this.handlerAdapters.iterator();
//遍历寻找合适的适配器
while(var2.hasNext()) {
HandlerAdapter adapter = (HandlerAdapter)var2.next();
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
7.3.4.2 执行目标方法
//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);
7.3.4.3 参数解析器HandlerMethodArgumentResolver
HandlerMethodArgumentResolver确定将要执行的目标方法的每一个参数的值是什么。SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
参数解析器是一个接口:
- 先利用方法supportsParameter判断当前解析器是否支持解析这种参数
- 支持的话就调用方法resolveArgument
7.3.4.4 返回值处理器
决定目标方法能写多少种类型的返回值:
7.3.4.5 如何确定目标方法每一个参数的值
InvocableHandlerMethod类中的方法:
============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;
}
-
挨个判断所有参数解析器哪个支持解析这个参数
@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;
}
-
解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
7.3.4.6 目标方法处理完成
将所有的数据都放在ModelAndViewContainer。包含要去的页面地址View,还包含Model数据。
7.3.4.7 处理派发结果
核心方法就是doDispatch()方法里的:
processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
- doDispatch()内的render方法相当于接下来要去哪个页面,开始渲染。
- 渲染新的视图,进入AbstractView下的render()方法:
-
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
-
进入InternalResourceView:内的renderMergedOutputModel方法:
-
暴露模型作为请求域属性
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
- 此时就可以验证上面的结论,参数map、model内的数据最后会存在request域中。并且这个过程是在渲染视图的过程中操作完成的。
PS:根据尚硅谷视频整理,如有侵权,联系删除