北京十佳網(wǎng)站建設(shè)廣告網(wǎng)站大全
目錄
1、SpringMVC自動配置概覽?
2、簡單功能分析
2.1、靜態(tài)資源訪問
1、靜態(tài)資源目錄
2、靜態(tài)資源訪問前綴
2.2、歡迎頁支持
2.3、自定義 Favicon
2.4、靜態(tài)資源配置原理
3、請求參數(shù)處理
0、請求映射
1、rest使用與原理
2、請求映射原理
1、普通參數(shù)與基本注解
1.1、注解:
1.2、Servlet API:
1.3、復(fù)雜參數(shù):
1.4、自定義對象參數(shù):
2、POJO封裝過程
3、參數(shù)處理原理
3.1、HandlerAdapter(處理器映射器)
3.2、執(zhí)行目標(biāo)方法
?3.3、參數(shù)解析器-HandlerMethodArgumentResolver
3.4、返回值處理器
3.5、如何確定目標(biāo)方法每一個參數(shù)的值
3.5.1、循環(huán)所有參數(shù)解析器,看哪個支持解析這個參數(shù)
3.5.2、解析這個參數(shù)的值
3.5.3、解析自定義類型參數(shù)-封裝POJO
3.6、目標(biāo)方法執(zhí)行完成
4、數(shù)據(jù)響應(yīng)與內(nèi)容協(xié)商
1、響應(yīng)JSON
原理解析
1.2、SpringMVC到底支持哪些返回值
1.3、HTTPMessageConverter原理
2、內(nèi)容協(xié)商
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.- 內(nèi)容協(xié)商視圖解析器和BeanName視圖解析器
- Support for serving static resources, including support for WebJars (covered later in this document)).
- 靜態(tài)資源(包括webjars)
- Automatic registration of
Converter
,GenericConverter
, andFormatter
beans.- 自動注冊
Converter,GenericConverter,Formatter
- 自動注冊
- Support for
HttpMessageConverters
(covered later in this document).- 支持
HttpMessageConverters
(后來我們配合內(nèi)容協(xié)商理解原理)
- 支持
- Automatic registration of
MessageCodesResolver
(covered later in this document).- 自動注冊
MessageCodesResolver
(國際化用)
- 自動注冊
- Static
index.html
support.- 靜態(tài)index.html 頁支持
- Custom
Favicon
support (covered later in this document).- 自定義
Favicon
- 自定義
- Automatic use of a
ConfigurableWebBindingInitializer
bean (covered later in this document).- 自動使用
ConfigurableWebBindingInitializer
,(DataBinder負責(zé)將請求數(shù)據(jù)綁定到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
自定義規(guī)則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、靜態(tài)資源訪問
1、靜態(tài)資源目錄
只要靜態(tài)資源放在類路徑下: called?/static
?(or?/public
?or?/resources
?or?/META-INF/resources
訪問:當(dāng)前項目根目錄/ + 靜態(tài)資源名
原理:靜態(tài)映射/**
接收到請求,先到dispatcherServlet轉(zhuǎn)發(fā)找對應(yīng)的Controller,若找不到對應(yīng)的Controller就交給靜態(tài)資源處理器。若靜態(tài)資源處理器還找不到,就返回404響應(yīng)頁面。
改變默認的靜態(tài)資源路徑:
spring:mvc:static-path-pattern: /res/**resources:static-locations: [classpath:/haha/]
2、靜態(tài)資源訪問前綴
為了方便讓攔截器直接放行靜態(tài)資源的訪問,可以給靜態(tài)資源的訪問配置一個統(tǒng)一的前綴。
默認是無前綴的。
spring:mvc:static-path-pattern: /res/**
當(dāng)前項目 + static-path-pattern + 靜態(tài)資源名 = 靜態(tài)資源文件夾下找
示例:
原本請求路徑:http://localhost:8080/res/xq.png
加了訪問前綴后:http://localhost:8080/res/xq.png
2.2、歡迎頁支持
可以有兩種方式:靜態(tài)資源、動態(tài)請求處理。
- 靜態(tài)資源路徑下:index.html
- 可以配置靜態(tài)資源路徑
- 但是不可以配置靜態(tài)資源的訪問前綴,會導(dǎo)致index.html不能被默認訪問
spring:mvc:static-path-pattern: /res/** # 配置之后導(dǎo)致welcome page 功能失效resources:static-locations: [ classpath:/haha/ ]
- 編寫controller處理 /index 請求
2.3、自定義 Favicon
訪問圖標(biāo)。
favicon.ico 放在靜態(tài)資源目錄下即可。
2.4、靜態(tài)資源配置原理
- 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 {
- 配置文件的相關(guān)屬性和xxx進行了綁定。
- WebMvcProperties==spring.mvc、
- ResourceProperties==spring.resources
1、配置類只有一個有參構(gòu)造器
有參構(gòu)造器的所有參數(shù)的值都會從容器中確定。
//ResourceProperties resourceProperties;獲取和spring.resources綁定的所有的值的對象
//WebMvcProperties mvcProperties 獲取和spring.mvc綁定的所有的值的對象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 資源處理器的自定義器。=========
//DispatcherServletPath
//ServletRegistrationBean 給應(yīng)用注冊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、資源處理的默認規(guī)則
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));}}
}
可以發(fā)現(xiàn)使用配置可以禁用靜態(tài)資源規(guī)則。
spring:
# mvc:
# static-path-pattern: /res/**resources:add-mappings: false 禁用所有靜態(tài)資源規(guī)則
@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、歡迎頁的處理規(guī)則
?? ?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)) {// 調(diào)用Controller /indexlogger.info("Adding welcome page template: index");setRootViewName("index");}
}
4、favicon
瀏覽器會發(fā)送 /favicon 來獲取圖標(biāo),整個session期間不再獲取。
這就解釋了,在配置了靜態(tài)資源前綴下,瀏覽器獲取不到的ico的原因。
3、請求參數(shù)處理
0、請求映射
1、rest使用與原理
- @xxxMapping;
- Rest風(fēng)格支持(使用HTTP請求方式動詞來表示對資源的操作)
- 以前:/getUser ? 獲取用戶? /deleteUser 刪除用戶 ? /editUser ?修改用戶 ? /saveUser 保存用戶
- 現(xiàn)在: /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 的值,并轉(zhuǎn)成大寫英文。
- 兼容以下請求:PUT、DELETE、PATCH
- 原生request(post),包裝模式requesWrapper重寫了getMethod方法,返回的是傳入的值。
- 過濾器鏈放行的時候用wrapper。以后的方法調(diào)用getMethod是調(diào)用requesWrapper的。
- 請求是否正常,并且是POST請求
spring:mvc:hiddenmethod:filter:enabled: true
Rest使用客戶端工具
- 如PostMan直接發(fā)送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);// 找到當(dāng)前請求使用哪個Handler(Controller的方法)處理mappedHandler = getHandler(processedRequest);//HandlerMapping:處理器映射。/xxx->>xxxx
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射規(guī)則。
所有的請求映射都在HandlerMapping(處理器映射器)中。
- SpringBoot自動配置歡迎頁的 WelcomePageHandlerMapping 。訪問 /能訪問到index.html;
- SpringBoot自動配置了默認 的 RequestMappingHandlerMapping。
- 請求進來,挨個嘗試所有的HandlerMapping看是否有請求信息。
- 如果有就找到這個請求對應(yīng)的handler
- 如果沒有就是下一個 HandlerMapping
-
我們?nèi)绻枰恍┳远x的映射處理,我們也可以自己給容器中放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、普通參數(shù)與基本注解
1.1、注解:
- @PathVariable(路徑變量)
- @RequestHeader(獲取請求頭)
- @ModelAttribute
- @RequestParam(獲取請求參數(shù))
- @MatrixVariable(矩陣變量)
- @CookieValue(獲取cookie值)
- @RequestBody(獲取請求體)
1.2、Servlet API:
- WebRequest、
- ServletRequest、
- MultipartRequest、
- HttpSession、
- javax.servlet.http.PushBuilder、
- Principal、
- InputStream、
- Reader、
- HttpMethod、
- Locale、
- TimeZone、
- ZoneId
?ServletRequestMethodArgumentResolver (方法參數(shù)解析器)來解析以上部的
@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、復(fù)雜參數(shù):
- Map、Model(map、model里面的數(shù)據(jù)會被放在request的請求域中,相當(dāng)于調(diào)用?request.setAttribute)
- Errors/BindingResult
- RedirectAttributes( 重定向攜帶數(shù)據(jù))
- ServletResponse(response)
- SessionStatus
- UriComponentsBuilder
- ServletUriComponentsBuilder
Map<String,Object> map, Model model, HttpServletRequest request 都是可以給request域中放數(shù)據(jù),
等價于 request.getAttribute();
無論是Map、Model類型的參數(shù),底層都是調(diào)用 mavContainer.getMode() 返回一個?BindingAwareModelMap,而它是Model,也是Map。
?
1.4、自定義對象參數(shù):
可以自動類型轉(zhuǎn)換與格式化,可以級聯(lián)封裝。
/*** 姓名: <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封裝過程
- 自定義類型的參數(shù)解析器為:
- ServletModelAttributeMethodProcessor
3、參數(shù)處理原理
- HandlerMapping(處理器映射器)中找到能處理請求的Handler(Controller.method()),并返回處理器執(zhí)行鏈。
- 為當(dāng)前Handler找到一個HandlerAdapter(處理器適配器);一般被@RequestMapping 標(biāo)記的方法找到的都是?RequestMappingHandlerAdapter
- 適配器執(zhí)行目標(biāo)方法,并確定方法參數(shù)的每一個值。
3.1、HandlerAdapter(處理器映射器)
0 - 支持方法上標(biāo)注@RequestMapping
1 - 支持函數(shù)式編程的
xxxxxx
3.2、執(zhí)行目標(biāo)方法
// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); //執(zhí)行目標(biāo)方法//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//獲取方法的參數(shù)值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
?3.3、參數(shù)解析器-HandlerMethodArgumentResolver
確定將要執(zhí)行的目標(biāo)方法的每一個參數(shù)的值是什么;
SpringMVC目標(biāo)方法能寫多少種參數(shù)類型。取決于參數(shù)解析器。
- 支持就調(diào)用 resolveArgument
- 當(dāng)前解析器是否支持解析這種參數(shù)
3.4、返回值處理器
3.5、如何確定目標(biāo)方法每一個參數(shù)的值
============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、循環(huán)所有參數(shù)解析器,看哪個支持解析這個參數(shù)
@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、解析這個參數(shù)的值
調(diào)用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可。
3.5.3、解析自定義類型參數(shù)-封裝POJO
1、找到參數(shù)解析器。
ServletModelAttributeMethodProcessor 判斷自定義類型是否為簡單類型,不是,則作為解析器處理此參數(shù)。
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數(shù)據(jù)綁定器)利用它里面的Converters(轉(zhuǎn)換器)將請求數(shù)據(jù)轉(zhuǎn)換成指定的數(shù)據(jù)類型。
核心源碼:?
@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:在設(shè)置每一個值的時候,遍歷它里面所有的所有converter(124個),直到找到可以將當(dāng)前參數(shù)的數(shù)據(jù)類型(request帶來參數(shù)的字符串)轉(zhuǎn)換到指定的類型(JavaBean -- Integer)。
3、WebDataBinder 將轉(zhuǎn)換后的數(shù)據(jù)再次封裝到指定的JavaBean中。
3.6、目標(biāo)方法執(zhí)行完成
將所有的數(shù)據(jù)都放在 ModelAndViewContainer;包含要去的頁面地址View。還包含Model數(shù)據(jù)。
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);}}
關(guān)鍵點:
暴露模型作為請求域?qū)傩?// Expose the model object as request attributes.exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,HttpServletRequest request) throws Exception {//model中的所有數(shù)據(jù)遍歷挨個放在請求域中model.forEach((name, value) -> {if (value != null) {request.setAttribute(name, value);}else {request.removeAttribute(name);}});}
4、數(shù)據(jù)響應(yīng)與內(nèi)容協(xié)商
數(shù)據(jù)響應(yīng)分為響應(yīng)頁面和響應(yīng)數(shù)據(jù)。
- 響應(yīng)頁面:如何發(fā)送一個請求,跳轉(zhuǎn)到指定頁面。一般用于一個單體請求。后續(xù)在視圖解析器詳細說明。
- 響應(yīng)數(shù)據(jù):通常用來開發(fā)前后端分離的項目。前端發(fā)送請求給后端,后端響應(yīng)數(shù)據(jù)、圖片、xls、自定義協(xié)議數(shù)據(jù)等。
1、響應(yīng)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>
?主要引入的依賴為:
測試代碼與結(jié)果
@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.// 使用消息轉(zhuǎn)換器進行寫出操作writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}
2、返回值解析器原理?
- 1、返回值處理器判斷是否支持這種類型返回值 supportsReturnType;
- 2、若支持,該返回值處理器調(diào)用 handleReturnValue 進行處理。
- 若標(biāo)記了@ResponseBody注解,則調(diào)用處理器為RequestResponseBodyMethodProcessor
-
利用 MessageConverters 進行處理 將數(shù)據(jù)寫為json,具體過程:
-
1、內(nèi)容協(xié)商(瀏覽器默認會以請求頭的方式告訴服務(wù)器他能接受什么樣的內(nèi)容類型)
-
2、服務(wù)器最終根據(jù)自己自身的能力,決定服務(wù)器能生產(chǎn)出什么樣內(nèi)容類型的數(shù)據(jù);
-
3、決定內(nèi)容類型后,SpringMVC會遍歷所有容器底層的 HttpMessageConverter(消息轉(zhuǎn)換器)看誰能處理
-
1、得到MappingJackson2HttpMessageConverter可以將對象寫為json
- 2、利用MappingJackson2HttpMessageConverter將對象轉(zhuǎn)為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 接口規(guī)范
HttpMessageConverter: 看是否支持將 此 Class類型的對象,轉(zhuǎn)為MediaType類型的數(shù)據(jù)。
例子:Person對象轉(zhuǎn)為JSON?;蛘?JSON轉(zhuǎn)為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 把對象轉(zhuǎn)為JSON(利用底層的jackson的objectMapper轉(zhuǎn)換的)
2、內(nèi)容協(xié)商
根據(jù)客戶端接收能力不同,返回不同媒體類型的數(shù)據(jù)。
假設(shè)現(xiàn)在有三種客戶端,接收的數(shù)據(jù)類型不同:
- 瀏覽器:能接收所有類型內(nèi)容
- 安卓客戶端:只能接收json
- 另一種客戶端:只能接收xml
1、引入xml依賴
<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2、postman分別測試返回json和xml
只需要改變請求頭中Accept字段。
Http協(xié)議中規(guī)定的,告訴服務(wù)器本客戶端可以接收的數(shù)據(jù)類型。
發(fā)現(xiàn)服務(wù)代碼沒改動情況下,具備同時返回xml跟json兩種格式的能力。
3、開啟瀏覽器參數(shù)方式內(nèi)容協(xié)商功能
4、內(nèi)容協(xié)商原理
- 1、判斷當(dāng)前響應(yīng)頭中是否已經(jīng)有確定的媒體類型。MediaType
- 2、獲取客戶端(PostMan、瀏覽器)支持接收的內(nèi)容類型。(獲取客戶端Accept請求頭字段)【application/xml】