SpringBoot2核心技术-核心功能【05、Web开发】
目录
1、SpringMVC自动配置概览
2、简单功能分析
1、静态资源目录
2、静态资源访问前缀
2.2、欢迎页支持
2.3、自定义 Favicon
2.4、静态资源配置原理
3、请求参数处理
0、请求映射
1、rest使用与原理
2、请求映射原理
1、普通参数与基本注解
1.1、注解:
1.2、Servlet API:
1.3、复杂参数:
1.4、自定义对象参数:
2、POJO封装过程
3、参数处理原理
3.1、HandlerAdapter(处理器映射器)
3.2、执行目标方法
3.3、参数解析器-HandlerMethodArgumentResolver
3.4、返回值处理器
3.5、如何确定目标方法每一个参数的值
3.5.1、循环所有参数解析器,看哪个支持解析这个参数
3.5.2、解析这个参数的值
3.5.3、解析自定义类型参数-封装POJO
3.6、目标方法执行完成
4、数据响应与内容协商
1、响应JSON
原理解析
1.2、SpringMVC到底支持哪些返回值
1.3、HTTPMessageConverter原理
2、内容协商
1、SpringMVC自动配置概览
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
- Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.- 内容协商视图解析器和BeanName视图解析器
- Support for serving static resources, including support for WebJars (covered later in this document)).
- 静态资源(包括webjars)
- Automatic registration of
Converter
,GenericConverter
, andFormatter
beans.- 自动注册
Converter,GenericConverter,Formatter
- 自动注册
- Support for
HttpMessageConverters
(covered later in this document).- 支持
HttpMessageConverters
(后来我们配合内容协商理解原理)
- 支持
- Automatic registration of
MessageCodesResolver
(covered later in this document).- 自动注册
MessageCodesResolver
(国际化用)
- 自动注册
- Static
index.html
support.- 静态index.html 页支持
- Custom
Favicon
support (covered later in this document).- 自定义
Favicon
- 自定义
- Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document).- 自动使用
ConfigurableWebBindingInitializer
,(DataBinder负责将请求数据绑定到JavaBean上)
- 自动使用
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own
@Configuration
class of typeWebMvcConfigurer
but without@EnableWebMvc
.不用@EnableWebMvc注解。使用
@Configuration
+WebMvcConfigurer
自定义规则
If you want to provide custom instances of
RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
, orExceptionHandlerExceptionResolver
, and still keep the Spring Boot MVC customizations, you can declare a bean of typeWebMvcRegistrations
and use it to provide custom instances of those components.声明
WebMvcRegistrations
改变默认底层组件If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
, or alternatively add your own@Configuration
-annotatedDelegatingWebMvcConfiguration
as described in the Javadoc of@EnableWebMvc
.使用
@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC
2、简单功能分析
2.1、静态资源访问
1、静态资源目录
只要静态资源放在类路径下: called /static
(or /public
or /resources
or /META-INF/resources
访问:当前项目根目录/ + 静态资源名
原理:静态映射/**
接收到请求,先到dispatcherServlet转发找对应的Controller,若找不到对应的Controller就交给静态资源处理器。若静态资源处理器还找不到,就返回404响应页面。
改变默认的静态资源路径:
spring:mvc:static-path-pattern: /res/**resources:static-locations: [classpath:/haha/]
2、静态资源访问前缀
为了方便让拦截器直接放行静态资源的访问,可以给静态资源的访问配置一个统一的前缀。
默认是无前缀的。
spring:mvc:static-path-pattern: /res/**
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
示例:
原本请求路径:http://localhost:8080/res/xq.png
加了访问前缀后:http://localhost:8080/res/xq.png
2.2、欢迎页支持
可以有两种方式:静态资源、动态请求处理。
- 静态资源路径下:index.html
- 可以配置静态资源路径
- 但是不可以配置静态资源的访问前缀,会导致index.html不能被默认访问
spring:mvc:static-path-pattern: /res/** # 配置之后导致welcome page 功能失效resources:static-locations: [ classpath:/haha/ ]
- 编写controller处理 /index 请求
2.3、自定义 Favicon
访问图标。
favicon.ico 放在静态资源目录下即可。
2.4、静态资源配置原理
- SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
- SpringMVC的自动配置类 WebMvcAutoConfiguration。查看是否生效。
@Configuration(proxyBeanMethods = false
)
@ConditionalOnWebApplication(type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {..
- 查看自动配置类 给Spring容器中注册了哪些东西
@Configuration(proxyBeanMethods = false)
@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
- 配置文件的相关属性和xxx进行了绑定。
- WebMvcProperties==spring.mvc、
- ResourceProperties==spring.resources
1、配置类只有一个有参构造器
有参构造器的所有参数的值都会从容器中确定。
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath
//ServletRegistrationBean 给应用注册Servlet、Filter....public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,ObjectProvider<DispatcherServletPath> dispatcherServletPath,ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {this.resourceProperties = resourceProperties;this.mvcProperties = mvcProperties;this.beanFactory = beanFactory;this.messageConvertersProvider = messageConvertersProvider;this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();this.dispatcherServletPath = dispatcherServletPath;this.servletRegistrations = servletRegistrations;}
2、资源处理的默认规则
public void addResourceHandlers(ResourceHandlerRegistry registry) {if (!this.resourceProperties.isAddMappings()) {logger.debug("Default resource handling disabled");} else {Duration cachePeriod = this.resourceProperties.getCache().getPeriod();CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();if (!registry.hasMappingForPattern("/webjars/**")) {this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));}String staticPathPattern = this.mvcProperties.getStaticPathPattern();if (!registry.hasMappingForPattern(staticPathPattern)) {this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));}}
}
可以发现使用配置可以禁用静态资源规则。
spring:
# mvc:
# static-path-pattern: /res/**resources:add-mappings: false 禁用所有静态资源规则
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/","classpath:/resources/", "classpath:/static/", "classpath:/public/" };/*** Locations of static resources. Defaults to classpath:[/META-INF/resources/,* /resources/, /static/, /public/].*/private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
3、欢迎页的处理规则
HandlerMapping:处理器映射器。保存了每一个Handler能处理哪些请求。
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext
applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());return welcomePageHandlerMapping;
}
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {//要用欢迎页功能,必须是/**logger.info("Adding welcome page: " + welcomePage.get());setRootViewName("forward:index.html");}else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {// 调用Controller /indexlogger.info("Adding welcome page template: index");setRootViewName("index");}
}
4、favicon
浏览器会发送 /favicon 来获取图标,整个session期间不再获取。
这就解释了,在配置了静态资源前缀下,浏览器获取不到的ico的原因。
3、请求参数处理
0、请求映射
1、rest使用与原理
- @xxxMapping;
- Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
- 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
- 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
- 核心Filter;HiddenHttpMethodFilter
-
表单method=post,隐藏域 _method=put
-
@RestController
public class HelloController {@RequestMapping("/bug.jpg")public String hello() {return "hello";}@RequestMapping(value = "/user",method = RequestMethod.GET)public String getUser(){return "GET-张三";}@RequestMapping(value = "/user",method = RequestMethod.POST)public String saveUser(){return "POST-张三";}@RequestMapping(value = "/user",method = RequestMethod.PUT)public String putUser(){return "PUT-张三";}@RequestMapping(value = "/user",method = RequestMethod.DELETE)public String deleteUser(){return "DELETE-张三";}
}
源码:
@Bean@ConditionalOnMissingBean({FormContentFilter.class})@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter",name = {"enabled"},matchIfMissing = true)public OrderedFormContentFilter formContentFilter() {return new OrderedFormContentFilter();}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {HttpServletRequest requestToUse = request;if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {String paramValue = request.getParameter(this.methodParam);if (StringUtils.hasLength(paramValue)) {String method = paramValue.toUpperCase(Locale.ENGLISH);if (ALLOWED_METHODS.contains(method)) {requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);}}}filterChain.doFilter((ServletRequest)requestToUse, response);}
Rest原理(表单提交要使用Rest的时候)
- 表单提交带上 _method=PUT
- 请求过来被HiddenHttpMethodFilter 拦截
- 请求是否正常,并且是POST请求
- 获取到_method 的值,并转成大写英文。
- 兼容以下请求:PUT、DELETE、PATCH
- 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
- 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
- 请求是否正常,并且是POST请求
spring:mvc:hiddenmethod:filter:enabled: true
Rest使用客户端工具
- 如PostMan直接发送Put、delete等方式请求,无需Filter。
2、请求映射原理
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet 》 doDispatch
中开始。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 找到当前请求使用哪个Handler(Controller的方法)处理mappedHandler = getHandler(processedRequest);//HandlerMapping:处理器映射。/xxx->>xxxx
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。
所有的请求映射都在HandlerMapping(处理器映射器)中。
- SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
- SpringBoot自动配置了默认 的 RequestMappingHandlerMapping。
- 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler
- 如果没有就是下一个 HandlerMapping
-
我们如果需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}
1、普通参数与基本注解
1.1、注解:
- @PathVariable(路径变量)
- @RequestHeader(获取请求头)
- @ModelAttribute
- @RequestParam(获取请求参数)
- @MatrixVariable(矩阵变量)
- @CookieValue(获取cookie值)
- @RequestBody(获取请求体)
1.2、Servlet API:
- WebRequest、
- ServletRequest、
- MultipartRequest、
- HttpSession、
- javax.servlet.http.PushBuilder、
- Principal、
- InputStream、
- Reader、
- HttpMethod、
- Locale、
- TimeZone、
- ZoneId
ServletRequestMethodArgumentResolver (方法参数解析器)来解析以上部的
@Overridepublic 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);}
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.getMode() 返回一个 BindingAwareModelMap,而它是Model,也是Map。
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;}
@PostMapping("/saveuser")
public Person saveUser(Person person) {return person;
}
2、POJO封装过程
- 自定义类型的参数解析器为:
- ServletModelAttributeMethodProcessor
3、参数处理原理
- HandlerMapping(处理器映射器)中找到能处理请求的Handler(Controller.method()),并返回处理器执行链。
- 为当前Handler找到一个HandlerAdapter(处理器适配器);一般被@RequestMapping 标记的方法找到的都是 RequestMappingHandlerAdapter
- 适配器执行目标方法,并确定方法参数的每一个值。
3.1、HandlerAdapter(处理器映射器)
0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的
xxxxxx
3.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.3、参数解析器-HandlerMethodArgumentResolver
确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
- 支持就调用 resolveArgument
- 当前解析器是否支持解析这种参数
3.4、返回值处理器
3.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;}
3.5.1、循环所有参数解析器,看哪个支持解析这个参数
@Nullableprivate 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;}
3.5.2、解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可。
3.5.3、解析自定义类型参数-封装POJO
1、找到参数解析器。
ServletModelAttributeMethodProcessor 判断自定义类型是否为简单类型,不是,则作为解析器处理此参数。
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));}
2、WebDataBinder (Web数据绑定器)利用它里面的Converters(转换器)将请求数据转换成指定的数据类型。
核心源码:
@Override@Nullablepublic 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 instancetry {attribute = createAttribute(name, parameter, binderFactory, webRequest);}catch (BindException ex) {if (isBindExceptionRequired(parameter)) {// No BindingResult parameter -> fail with BindExceptionthrow ex;}// Otherwise, expose null/empty value and associated BindingResultif (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.Optionalif (!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 modelMap<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute;}
GenericConversionService:在设置每一个值的时候,遍历它里面所有的所有converter(124个),直到找到可以将当前参数的数据类型(request带来参数的字符串)转换到指定的类型(JavaBean -- Integer)。
3、WebDataBinder 将转换后的数据再次封装到指定的JavaBean中。
3.6、目标方法执行完成
将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
InternalResourceView:
@Overrideprotected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);// Expose helpers as request attributes, if any.exposeHelpers(request);// Determine the path for the request dispatcher.String dispatcherPath = prepareForRendering(request, response);// Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);if (rd == null) {throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +"]: Check that the corresponding file exists within your web application archive!");}// If already included or response already committed, perform include, else forward.if (useInclude(request, response)) {response.setContentType(getContentType());if (logger.isDebugEnabled()) {logger.debug("Including [" + getUrl() + "]");}rd.include(request, response);}else {// Note: The forwarded resource is supposed to determine the content type itself.if (logger.isDebugEnabled()) {logger.debug("Forwarding to [" + getUrl() + "]");}rd.forward(request, response);}}
关键点:
暴露模型作为请求域属性
// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {//model中的所有数据遍历挨个放在请求域中model.forEach((name, value) -> {if (value != null) {request.setAttribute(name, value);}else {request.removeAttribute(name);}});}
4、数据响应与内容协商
数据响应分为响应页面和响应数据。
- 响应页面:如何发送一个请求,跳转到指定页面。一般用于一个单体请求。后续在视图解析器详细说明。
- 响应数据:通常用来开发前后端分离的项目。前端发送请求给后端,后端响应数据、图片、xls、自定义协议数据等。
1、响应JSON
1.1、jackson.jar+@ResponseBody
引入web场景<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>而web场景自动引入了json场景<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope></dependency>
主要引入的依赖为:
测试代码与结果
@Controller
public class ResponseTestController {@ResponseBody@GetMapping("/test/person")public Person getPerson() {Person person = new Person();person.setAge(28);person.setBirth(new Date());person.setUserName("张三");return person;}
}
原理解析
1、返回值解析器
核心代码: this.returnValueHandlers.handleReturnValue
try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());}handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}
RequestResponseBodyMethodProcessor
@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage = createInputMessage(webRequest);ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// Try even with null return value. ResponseBodyAdvice could get involved.// 使用消息转换器进行写出操作writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}
2、返回值解析器原理
- 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType;
- 2、若支持,该返回值处理器调用 handleReturnValue 进行处理。
- 若标记了@ResponseBody注解,则调用处理器为RequestResponseBodyMethodProcessor
-
利用 MessageConverters 进行处理 将数据写为json,具体过程:
-
1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
-
2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据;
-
3、决定内容类型后,SpringMVC会遍历所有容器底层的 HttpMessageConverter(消息转换器)看谁能处理
-
1、得到MappingJackson2HttpMessageConverter可以将对象写为json
- 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
-
-
-
1.2、SpringMVC到底支持哪些返回值
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
1.3、HTTPMessageConverter原理
1、MessageConverter 接口规范
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:Person对象转为JSON。或者 JSON转为Person
2、默认的MessageConverter
0 - 只支持Byte类型的
1 - 只支持String
2 - 只支持String
3 - 只支持Resource
4 - 只支持ResourceRegion
5 - 只支持DOMSource.class \\ SAXSource.class) \\ StAXSource.class \\StreamSource.class \\Source.class
6 - 只支持MultiValueMap
7 - 直接返回true
8 - 直接返回true
9 - 支持注解方式xml处理的。
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
2、内容协商
根据客户端接收能力不同,返回不同媒体类型的数据。
假设现在有三种客户端,接收的数据类型不同:
- 浏览器:能接收所有类型内容
- 安卓客户端:只能接收json
- 另一种客户端:只能接收xml
1、引入xml依赖
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2、postman分别测试返回json和xml
只需要改变请求头中Accept字段。
Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
发现服务代码没改动情况下,具备同时返回xml跟json两种格式的能力。
3、开启浏览器参数方式内容协商功能
4、内容协商原理
- 1、判断当前响应头中是否已经有确定的媒体类型。MediaType
- 2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】