SpringBootWeb开发-2-3
上一级页面:ssm-spring-boot速成学习索引
前言
33、请求处理-【源码分析】-Servlet API参数解析原理
- WebRequest
- ServletRequest
- MultipartRequest
- HttpSession
- javax.servlet.http.PushBuilder
- Principal
- InputStream
- Reader
- HttpMethod
- Locale
- TimeZone
- ZoneId
通过上一节的调试方法得到:
ServletRequestMethodArgumentResolver用来处理以上的参数
public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Nullable
private static Class<?> pushBuilder;
static {
try {
pushBuilder = ClassUtils.forName("javax.servlet.http.PushBuilder",
ServletRequestMethodArgumentResolver.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// Servlet 4.0 PushBuilder not found - not supported for injection
pushBuilder = null;
}
}
@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) && !parameter.hasParameterAnnotations()) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
// WebRequest / NativeWebRequest / ServletWebRequest
if (WebRequest.class.isAssignableFrom(paramType)) {
if (!paramType.isInstance(webRequest)) {
throw new IllegalStateException(
"Current request is not of type [" + paramType.getName() + "]: " + webRequest);
}
return webRequest;
}
// ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
return resolveNativeRequest(webRequest, paramType);
}
// HttpServletRequest required for all further argument types
return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}
private <T> T resolveNativeRequest(NativeWebRequest webRequest, Class<T> requiredType) {
T nativeRequest = webRequest.getNativeRequest(requiredType);
if (nativeRequest == null) {
throw new IllegalStateException(
"Current request is not of type [" + requiredType.getName() + "]: " + webRequest);
}
return nativeRequest;
}
@Nullable
private Object resolveArgument(Class<?> paramType, HttpServletRequest request) throws IOException {
if (HttpSession.class.isAssignableFrom(paramType)) {
HttpSession session = request.getSession();
if (session != null && !paramType.isInstance(session)) {
throw new IllegalStateException(
"Current session is not of type [" + paramType.getName() + "]: " + session);
}
return session;
}
else if (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) {
return PushBuilderDelegate.resolvePushBuilder(request, paramType);
}
else if (InputStream.class.isAssignableFrom(paramType)) {
InputStream inputStream = request.getInputStream();
if (inputStream != null && !paramType.isInstance(inputStream)) {
throw new IllegalStateException(
"Request input stream is not of type [" + paramType.getName() + "]: " + inputStream);
}
return inputStream;
}
else if (Reader.class.isAssignableFrom(paramType)) {
Reader reader = request.getReader();
if (reader != null && !paramType.isInstance(reader)) {
throw new IllegalStateException(
"Request body reader is not of type [" + paramType.getName() + "]: " + reader);
}
return reader;
}
else if (Principal.class.isAssignableFrom(paramType)) {
Principal userPrincipal = request.getUserPrincipal();
if (userPrincipal != null && !paramType.isInstance(userPrincipal)) {
throw new IllegalStateException(
"Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal);
}
return userPrincipal;
}
else if (HttpMethod.class == paramType) {
return HttpMethod.resolve(request.getMethod());
}
else if (Locale.class == paramType) {
return RequestContextUtils.getLocale(request);
}
else if (TimeZone.class == paramType) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone : TimeZone.getDefault());
}
else if (ZoneId.class == paramType) {
TimeZone timeZone = RequestContextUtils.getTimeZone(request);
return (timeZone != null ? timeZone.toZoneId() : ZoneId.systemDefault());
}
// Should never happen...
throw new UnsupportedOperationException("Unknown parameter type: " + paramType.getName());
}
/**
* Inner class to avoid a hard dependency on Servlet API 4.0 at runtime.
*/
private static class PushBuilderDelegate {
@Nullable
public static Object resolvePushBuilder(HttpServletRequest request, Class<?> paramType) {
PushBuilder pushBuilder = request.newPushBuilder();
if (pushBuilder != null && !paramType.isInstance(pushBuilder)) {
throw new IllegalStateException(
"Current push builder is not of type [" + paramType.getName() + "]: " + pushBuilder);
}
return pushBuilder;
}
}
}
用例:
@Controller
public class RequestController {
@GetMapping("/goto")
public String goToPage(HttpServletRequest request){
request.setAttribute("msg","成功了...");
request.setAttribute("code",200);
return "forward:/success"; //转发到 /success请求
}
}
34、请求处理-【源码分析】-Model、Map原理
复杂参数:
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){
//下面三位都是可以给request域中放数据
map.put("hello","world666");
model.addAttribute("world","hello666");
request.setAttribute("message","HelloWorld");
Cookie cookie = new Cookie("c1","v1");
response.addCookie(cookie);
return "forward:/success";
}
@ResponseBody
@GetMapping("/success")
public Map success(@RequestAttribute(value = "msg",required = false) String msg,
@RequestAttribute(value = "code",required = false)Integer code,
HttpServletRequest request){
Object msg1 = request.getAttribute("msg");
Map<String,Object> map = new HashMap<>();
Object hello = request.getAttribute("hello");//得出testParam方法赋予的值 world666
Object world = request.getAttribute("world");//得出testParam方法赋予的值 hello666
Object message = request.getAttribute("message");//得出testParam方法赋予的值 HelloWorld
map.put("reqMethod_msg",msg1);
map.put("annotation_msg",msg);
map.put("hello",hello);
map.put("world",world);
map.put("message",message);
return map;
}
Map<String,Object> map
Model model
HttpServletRequest request
上面三位都是可以给request域中放数据,用request.getAttribute()
获取
接下来我们看看,Map<String,Object> map
与Model model
用什么参数处理器。
Map<String,Object> map
参数用MapMethodProcessor
处理:
public class MapMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
parameter.getParameterAnnotations().length == 0);
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
return mavContainer.getModel();
}
...
}
注意到
return mavContainer.getModel();
mavContainer.getModel()
如下:
public class ModelAndViewContainer {
...
private final ModelMap defaultModel = new BindingAwareModelMap();
@Nullable
private ModelMap redirectModel;
...
public ModelMap getModel() {
if (useDefaultModel()) {
return this.defaultModel;
}
else {
if (this.redirectModel == null) {
this.redirectModel = new ModelMap();
}
return this.redirectModel;
}
}
private boolean useDefaultModel() {
return (!this.redirectModelScenario || (this.redirectModel == null && !this.ignoreDefaultModelOnRedirect));
}
...
}
注意到
if (useDefaultModel()) {
return this.defaultModel;
}
查看this.defaultModel
的类型是BindingAwareModelMap
,进一步探究发现底层是
public class ExtendedModelMap extends ModelMap implements Model {
//..
}
即BindingAwareModelMap
既是Model也是Map
Model model
用ModelMethodProcessor
处理:
public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Model.class.isAssignableFrom(parameter.getParameterType());
}
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
return mavContainer.getModel();
}
...
}
也是return mavContainer.getModel();
。这里跟MapMethodProcessor
方法内的处理办法一致。
最终也是使用BindingAwareModelMap
类型,BindingAwareModelMap
既是Model也是Map
查看BindingAwareModelMap
的继承链:
![Pasted image 20230125175143.png](https://webdav-1309345210.file.myqcloud.com/images/Pasted image 20230125175143.png)
来到InvocableHandlerMethod
类的getMethodArgumentValues
方法
查看args,可以看到Map和Model实际上是用同一个对象来处理的
![Pasted image 20230125175559.png](https://webdav-1309345210.file.myqcloud.com/images/Pasted image 20230125175559.png)
一直“恢复程序执行”,直到来到ServletInvocableHandlerMethod#invokeAndHandle
方法
查看mavContatiner的值,可以看到Map和Model实际上是用同一个对象来处理的,处理结果是图中的“hello”和"world"
![Pasted image 20230125180305.png](https://webdav-1309345210.file.myqcloud.com/images/Pasted image 20230125180305.png)
目标方法执行完成
目标方法执行完成,将所有的数据都放在ModelAndViewContainer
(mavContaioner).
mavContainer包含要去的地址url,还包含Model的数据
![Pasted image 20230125182100.png](https://webdav-1309345210.file.myqcloud.com/images/Pasted image 20230125182100.png)
将数据填充到Model And View
步入getModelAndView
方法
这个方法首先更新Model数据,
![Pasted image 20230125182233.png](https://webdav-1309345210.file.myqcloud.com/images/Pasted image 20230125182233.png)
然后将数据填充到ModelAndView
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
//...
// 注意这句
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
如果你的数据是重定向到新网页的,这个方法还会帮你把数据绑定到新网页的请求上下文(RequestContex),以完成旧网页request携带的内容转移到新网页request上。
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
// ...
// 注意这里
if (model instanceof RedirectAttributes redirectAttributes) {
Map<String, ?> flashAttributes = redirectAttributes.getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
定位processDispatchResult
方法
步出,回到org.springframework.web.servlet.DispatcherServlet#doDispatch
,即DispatcherServlet类的doDispatch
方法,
- 后文全部参照这种
类#方法
的表示形式
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//...
mappedHandler.applyPostHandle(processedRequest, response, mv);
// ...
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
现在ha.handle
执行完成
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
方法继续往下执行,来到核心语句,这里执行拦截器的,后文会进行解析
mappedHandler.applyPostHandle(processedRequest, response, mv);
继续步过,来到核心语句,处理分发结果。这是下一小节的解析重点。
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
如何获取Map<String,Object> map
与Model model
的值
看看Map<String,Object> map
与Model model
值是如何做到用request.getAttribute()
获取的。
根据上一小节的内容,所有的数据都放在 ModelAndView ,它包含要去的页面地址View,还包含Model数据。
回顾上一小节的末尾,先看ModelAndView接下来是如何处理的?
public class DispatcherServlet extends FrameworkServlet {
...
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
try {
ModelAndView mv = null;
...
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//处理分发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
...
}
它包含这三个核心语句
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//...
mappedHandler.applyPostHandle(processedRequest, response, mv);
// ...
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
步入processDispatchResult
方法,方法首先获取view的name,然后对view进行render,
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// ...
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
其中的核心语句:
render(mv, request, response);
步入render
方法
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// ...
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
//...
}
注意到核心语句:
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
resolveViewName
方法进入视图解析器的流程,以后会分析。
继续看render
方法的后半段:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// ...
try {
if (mv.getStatus() != null) {
request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
response.setStatus(mv.getStatus().value());
}
view.render(mv.getModelInternal(), request, response);
}
// ...
}
注意到核心语句
view.render(mv.getModelInternal(), request, response);
步入view.render
方法,来到InternalResourceView#render
方法
- 这里的
view
属为InternalResourceView
类。
public class InternalResourceView extends AbstractUrlBasedView {
@Override//该方法在AbstractView,AbstractUrlBasedView继承了AbstractView
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
//...
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//核心语句
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
注意到核心语句:
// 创建一个要合并的输出Model
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//...
// 核心语句
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
步入createMergedOutputModel
方法,这个方法将Model数据转移到转移到mergedModel(即HashMap中)
protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
HttpServletRequest request, HttpServletResponse response) {
// ...
// 把model里的数据全部放进mergedModel,转移到HashMap中
if (model != null) {
mergedModel.putAll(model);
}
//...
}
步出,回到InternalResourceView#render
方法,核心语句如下:
// 创建一个要合并的输出Model
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
//...
// 核心语句
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
注意到核心语句:
// 核心语句
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
步入renderMergedOutputModel
方法
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
// 暴露模型作为请求域属性
exposeModelAsRequestAttributes(model, request);//<---重点
//...
}
注意到核心语句:
// Expose the model object as request attributes.
// 暴露模型作为请求域属性
exposeModelAsRequestAttributes(model, request);//<---重点
步入exposeModelAsRequestAttributes
方法,方法遍历Model中的所有数据,将数据放在request域中
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
HttpServletRequest request) throws Exception {
model.forEach((name, value) -> {
if (value != null) {
request.setAttribute(name, value);
}
else {
request.removeAttribute(name);
}
});
}
}
exposeModelAsRequestAttributes
方法看出,
Map<String,Object> map
,Model model
这两种类型数据可以给request域中放数据,- 这个操作是在
渲染view
(view.render()
)时进行的, - 跳转操作发生在这之后
- 这个操作是在
- 使用数据时,用
request.getAttribute()
获取。
exposeModelAsRequestAttributes
方法是业务逻辑的最底层了,再往下是工具类。
该方法的继承关系如下:
![Pasted image 20230125200905.png](https://webdav-1309345210.file.myqcloud.com/images/Pasted image 20230125200905.png)
35、请求处理-【源码分析】-自定义参数绑定原理
假设有下面的场景
@RestController
public class ParameterTestController {
/**
* 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
* @param person
* @return
*/
@PostMapping("/saveuser")
public Person saveuser(Person person){
return person;
}
}
/**
* 姓名: <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;
}
封装过程用到ServletModelAttributeMethodProcessor
public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodProcessor {
@Override//本方法在ModelAttributeMethodProcessor类,
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
}
@Override
@Nullable//本方法在ModelAttributeMethodProcessor类,
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
...
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 (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)) {
//web数据绑定器,将请求参数的值绑定到指定的JavaBean里面**
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 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型
36、请求处理-【源码分析】-自定义Converter原理
未来我们可以给WebDataBinder里面放自己的Converter;
下面演示将字符串“啊猫,3”
转换成Pet
对象。
//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;
}
});
}
};
}