SpringBoot_第五章(Web和原理分析)
目录
1:静态资源
1.1:静态资源访问
1.2:静态资源源码解析-到WebMvcAutoConfiguration
2:Rest请求绑定(设置put和delete)
2.1:代码实例
2.2:源码分析到-WebMvcAutoConfiguration
3:请求参数处理
3.1:代码实例
3.2:转发重定向
3.2:源码分析到-DispatcherServlet
4:响应返回值处理
4.1:代码实例
4.2:源码分析到-DispatcherServlet
1:静态资源
1.1:静态资源访问
在springBoot项目中,我们首先来查看静态资源的访问位置,当我们配置项目,我们直接访问项目可以查看这些路径中的资源。
一个问题:
1:我们怎么知道静态资源放到哪里可以直接访问?
首先查看项目路径和配置文件如下
#默认静态资源访问路径 默认路径路径是/static(或/public或/resources或/META-INF/resources) #3.png路径错误访问不了
spring.mvc.static-path-pattern=/**
##修改静态资源访问路径 http://localhost:8080/res/4.png 静态资源前边必须加 res 和文件路径无关
#配置自定义路径 欢迎页面进不来 需要http://localhost:8080/res/index.html
#spring.mvc.static-path-pattern=/res/**上边的下边的二选一配置即可#自定义静态资源位置 add-mappings=false 禁用掉静态资源访问
spring.web.resources.add-mappings=true#静态资源缓存 默认是秒
spring.web.resources.cache.period=1001
#自定义 静态资源缓存 会覆盖掉系统默认的静态资源路径
spring.web.resources.static-locations=classpath:/自定义静态资源路径/#开启rest请求 WebMvcAutoConfiguration中查看源码
spring.mvc.hiddenmethod.filter.enabled=true
然后访问浏览器
http://localhost:8080/ 进入static下边的index.html
http://localhost:8080/1.png 或者2.png 4.png都可以访问,但是3.png不行
1.2:静态资源源码解析-到WebMvcAutoConfiguration
回到上边的问题,我们怎么知道静态资源应该放到哪里?可以顺利访问呢?
查看源码如下,主要看第一的解释,所以3.png的路径不对 访问不了
@Configuration(proxyBeanMethods = false)@Import(WebMvcAutoConfiguration.EnableWebMvcConfiguration.class)//第一:WebMvcProperties.class spring.mvc 配置//默认静态资源访问路径 默认路径路径是/static(或/public或/resources或/META-INF/resources) 3.png路径错误访问不了//spring.mvc.static-path-pattern=/*//修改静态资源访问路径 http://localhost:8080/res/4.png 静态资源前边必须加res,和文件路径无关,配置自定义路径,欢迎页面进不来//#spring.mvc.static-path-pattern=/res/**//第二:WebProperties.class spring.web 配置
// 自定义静态资源位置 add-mappings=false 禁用掉静态资源访问
// spring.web.resources.add-mappings=true
// 静态资源缓存 默认是秒
// spring.web.resources.cache.period=1001
// spring.web.resources.static-locations=classpath:/自定义静态资源路径/
// 开启rest请求 WebMvcAutoConfiguration中查看源码
// spring.mvc.hiddenmethod.filter.enabled=true@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })@Order(0)public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {//此处代码省略public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,ObjectProvider<DispatcherServletPath> dispatcherServletPath,ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {//在getResources() 可以看到默认的访问路径是
// private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
// "classpath:/resources/", "classpath:/static/", "classpath:/public/" };this.resourceProperties = webProperties.getResources();this.mvcProperties = mvcProperties;this.beanFactory = beanFactory;this.messageConvertersProvider = messageConvertersProvider;this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();this.dispatcherServletPath = dispatcherServletPath;this.servletRegistrations = servletRegistrations;this.mvcProperties.checkConfiguration();}//静态资源文件路径 处理方法public void addResourceHandlers(ResourceHandlerRegistry registry) {if (!this.resourceProperties.isAddMappings()) {logger.debug("Default resource handling disabled");return;}addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {//resourceProperties.getStaticLocations() 自己不配置 默认是系统的静态资源路径//resourceProperties.getStaticLocations() 自己配置的话自定义的文件路径//spring.web.resources.static-locations=classpath:/自定义静态资源路径/registration.addResourceLocations(this.resourceProperties.getStaticLocations());if (this.servletContext != null) {ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);registration.addResourceLocations(resource);}});}
源码分析我们知道在WebMvcAutoConfiguration类中,默认了静态资源的位置,所以才可以直接访问的。如果自己配置了自定义的静态资源,那么系统默认的失效,以自己为准。
2:Rest请求绑定(设置put和delete)
2.1:代码实例
Java代码如下:
@RestController
public class RestControllerTest {@GetMapping(value = "user")public String get() {System.out.println("get请求!");return "get请求!";}@PostMapping(value = "user")public String post() {System.out.println("post请求!");return "post请求!";}@PutMapping(value = "user")public String put() {System.out.println("put请求!");return "put请求!";}@DeleteMapping(value = "user")public String delete() {System.out.println("delete请求!");return "delete请求!";}
}
html表单如下:
<form method="get" action="user"><input type="submit" value="get提交">
</form><form method="post" action="user"><input type="submit" value="post提交">
</form><form method="post" action="user"><!-- 隐藏表单,设置除了get、post以外的表单属性,表单必须是post,name必须是_method--><input name="method" value="PUT" hidden="hidden"><input type="submit" value="put提交">
</form><form method="post" action="user"><!-- 隐藏表单,设置除了get、post以外的表单属性,表单必须是post,name必须是_method--><input name="method" value="DELETE" hidden="hidden"><input type="submit" value="delete提交">
</form>
开启rest请求
#开启rest请求 WebMvcAutoConfiguration中查看源码
spring.mvc.hiddenmethod.filter.enabled=true
结果如下:点击put,到后台的put的Controller
总结:表单要有隐藏域,name默认是_method,提交方式是post
2.2:源码分析到WebMvcAutoConfiguration
//如果没有HiddenHttpMethodFilter的bean
//如果没有开启配置 默认是spring.mvc.hiddenmethod.filter=false
//不会创建HiddenHttpMethodFilter 只有配置了true 才会执行下边的代码//默认spring.mvc.hiddenmethod.filter=false 没有开启处理Rest的filter@Bean@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {return new OrderedHiddenHttpMethodFilter();}//HiddenHttpMethodFilter 代码分析/** 默认的name参数必须是_method */public static final String DEFAULT_METHOD_PARAM = "_method";private String methodParam = DEFAULT_METHOD_PARAM;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {HttpServletRequest requestToUse = request;//代码分析 form表单只有post才能设置 put delete请求if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
//获取input标签中的 name= "_method" 的属性 这里是put deleteString paramValue = request.getParameter(this.methodParam);if (StringUtils.hasLength(paramValue)) {String method = paramValue.toUpperCase(Locale.ENGLISH);
//大小写转换 装饰器模式 创建新的HttpMethodRequestWrapper 这里是装饰器模式request
//ALLOWED_METHODS=(delete,put,patch)if (ALLOWED_METHODS.contains(method)) {requestToUse = new HttpMethodRequestWrapper(request, method);}}}
//执行过滤链 这里调用链模式filterChain.doFilter(requestToUse, response);}//分割线 我的表单时name是method不是_method这个时候就需要我们配置自己的HiddenHttpMethodFilter@Beanpublic HiddenHttpMethodFilter hiddenHttpMethodFilter(){HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
//设置自定义的name为method 这个时候会覆盖系统的_method hiddenHttpMethodFilter.setMethodParam("method");return hiddenHttpMethodFilter;}
3:请求参数处理
3.1:代码实例
@RestController
public class 请求参数Controller {/*** 路径参数 @PathVariable* 请求头 @RequestHeader* 方法参数 @RequestParam* cookie参数 @CookieValue* 他们都能封装成map 但是map有缺陷 key想同会丢失参数** @RequestParam Map<String, String> paramMap 会丢失key一致的参数 多个likes 只能保存一个**///http://localhost:8080/t1/1/blog/test?name=张三&age=28&likes=语文&likes=数学@GetMapping(value = "/t1/{id}/blog/{type}")public Map t1(@PathVariable("id") Integer id,//绑定指定的路径id到参数@PathVariable("type") String type,//绑定指定的路径type到参数@PathVariable Map<String,String> pathMap,//绑定所有的路径参数到Map@RequestHeader("Accept") String accept, //绑定指定的head到参数@RequestHeader Map<String,String> headMap, //绑定所有的head到Map@RequestParam(value = "name",required = false) String name,//绑定指定参数@RequestParam(value = "age",required = false) Integer age,//绑定指定参数@RequestParam(value = "likes",required = false) List<String> likes,//绑定指定参数到likes@RequestParam Map<String, String> paramMap, //绑定所有的参数到Map 因为是map类型 会丢失key一致的参数 比如like@CookieValue(value = "__utma",required = false) String cookieId,@CookieValue(value = "__utmv",required = false) Cookie cookie) {System.out.println("=======请求路径参数==========");System.out.println("rest路径参数id:"+id);System.out.println("rest路径参数type:"+type);pathMap.forEach((key,value)-> System.out.println(key+"=="+value));System.out.println();System.out.println("=======请求头参数==========");System.out.println("rest路径head参数Accept:"+accept);headMap.forEach((key,value)->{System.out.println(key+"=="+value);});System.out.println();System.out.println("=======请求参数==========");System.out.println("rest路径请求参数name:"+name);System.out.println("rest路径请求参数age:"+age);for (String like : likes) {System.out.println("rest路径请求参数like:"+like);}paramMap.forEach((key,value)->{System.out.println(key+"=="+value);});System.out.println();System.out.println("=======请求cookie==========");System.out.println("rest路径请求参数cookie:"+cookieId);System.out.println(cookie.getName()+":"+cookie.getValue());Map<String, Object> map = new HashMap();map.put("请求","参数");return map;}/*** @RequestBody 获取post的请求体参数*///http://localhost:8080/t2/2 body的参数postMan 自己创造@PostMapping(value = "/t2/{id}")public Map t2(@PathVariable("id") Integer id,@RequestParam("name") String name,@RequestBody String body //获取post的请求体参数) {System.out.println("=======post获取请求体==========");System.out.println("rest路径参数id:"+id);System.out.println("rest请求参数name:"+name);System.out.println("rest请求body的参数:"+body);Map<String, Object> map = new HashMap();map.put("请求","参数");return map;}/*** 矩阵变量 @PathVariable* http://localhost:8080/t3/2;low=34;like=eng;like=yuwen* http://localhost:8080/t3/1;low=34;likes=eng;likes=yuwen/2;demo=dd*/@GetMapping(value = "/t3/{path}/{path1}")public Map t3(@PathVariable("path") String path,@PathVariable("path1") String path1,@MatrixVariable(value = "low",pathVar = "path") String low,@MatrixVariable(value = "likes",pathVar = "path") List<String> likes,@MatrixVariable(value = "demo",pathVar = "path1") String demo) {System.out.println("=======get获取矩阵变量==========");System.out.println("rest路径矩阵变量path:"+path);System.out.println("rest路径矩阵变量path1:"+path1);System.out.println("rest路径矩阵变量demo:"+demo);System.out.println("rest路径矩阵变量low:"+low);for (String like : likes) {System.out.println("rest路径矩阵变量like:"+like);}Map<String, Object> map = new HashMap();map.put("请求","参数");return map;}/*** 请求参数转换实体* http://localhost:8080/person?id=1&name=张三&date=2023-03-04 14:30:38&cat.id=2&cat.name=波斯猫*/@GetMapping("/person")public Person get(Person person){System.out.println(person);return person;}
}
3.2:转发重定向
@Controller
public class 转发重定向Controller {/**** http://localhost:8080/request1* 转发测试 forward 不能使用@RestController* @RequestAttribute(value = "name") 绑定转发的请求属性** http://localhost:8080/request1*/@GetMapping("/request1")public String request1(HttpServletRequest request){System.out.println("进入转发请求1");request.setAttribute("name","中文");return "forward:/request2";}@GetMapping("/request2")@ResponseBodypublic String request2(@RequestAttribute(value = "name") String name){System.out.println("进入转发请求2");System.out.println("获取转发的请求参数name:"+name);return "转发";}/**** http://localhost:8080/request11** 参数的map、model 的数据会放到request域中,相当于setAttribute*/@GetMapping("/request11")public String request11(HttpServletRequest request, HttpServletResponse response, Map map, Model model){System.out.println("进入转发请求11");map.put("map","hello map");model.addAttribute("model","hello model");request.setAttribute("name","中文");Cookie cookie=new Cookie("cookie","cookie_value");response.addCookie(cookie);return "forward:/request22";}@GetMapping("/request22")@ResponseBodypublic String request22(@RequestAttribute(value = "name") String name,HttpServletRequest request){System.out.println("进入转发请求22");System.out.println("获取转发的请求参数name:"+name);System.out.println("request获取属性name:"+request.getAttribute("name"));System.out.println("request获取属性map:"+request.getAttribute("map"));System.out.println("request获取属性model:"+request.getAttribute("model"));Cookie[] cookies = request.getCookies();for (Cookie cookie : cookies) {System.out.println(cookie.getName()+":"+cookie.getValue());}return "转发";}/*** http://localhost:8080/request3* 重定向测试 redirect* @RequestAttribute(value = "name") 绑定指定的值* @return*/@GetMapping("/request3")public String request3(HttpServletRequest request){System.out.println("进入重定向请求1");request.setAttribute("name","中文");return "redirect:request4";}@GetMapping("/request4")@ResponseBodypublic String request4(String name,String aa){System.out.println("进入重定向请求2");System.out.println("获取重定向的请求参数name:"+name);return "重定向";}
}
3.2:源码分析到-DispatcherServlet
我们以一下代码做分析,将请求参数封装到Person
/*** 请求参数转换实体* http://localhost:8080/person?id=1&name=张三&date=2023-03-04 14:30:38&cat.id=2&cat.name=波斯猫*/@GetMapping("/person")public Person get(Person person){//这一行输出代码打断点 debug运行System.out.println(person);return person;}
在源码分析之前,我想知道请求流程是怎么处理的,请求的参数是怎么绑定到我们指定的参数、或者pojo、或者map中的
我们先上一张SpringWeb的执行流程图,然后格局源码分析。
我们查看源码
1:调用DispatcherServlet的doService()方法,DispatcherServlet也是个Servlet,从继承关系层层调用到doService方法
2:doService()方法调用了doDispatch(request, response);这个方法是核心,我们查看代码分析
从源码结合上边的流程图可以详细的看到具体流程
//核心代码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 {//检查是不是Multipart 文件上传的请求processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// 确认当前的请求 从5个handlerMapping中匹配到合适//这里就是RequestHandlerMapping 因为使用了@RequestMapping注解//这5个handlerMapping 分别是Request、welcome、BeanNamesUrl、RouterFunction等handlerMappingmappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 根据前边的mappedHandler 找到HandlerAdapter//从4个中Adapter找到了RequestMappingHandlerAdapter 还是因为使用了@RequestMapping注解HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.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;}}//执行拦截器的Pre方法 在Controller前执行if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}//执行真正的controller方法 反射调用//因为反射知道了Controller方法的参数和类型 所以我们就可以把request的参数//通过反射绑定到Controller的方法参数中 这就是核心原理//执行流程 核心方法 重点五颗星 具体的跳转流程已经标志 不做代码截取//handle->(没哈意思,只是跳转)handleInternal->(没哈意思,只是跳转)invokeHandlerMethod->(加载参数解析器27个、返回值参数解析器15个)invokeAndHandle->(调用执行invokeForRequest方法,获取返回值)invokeForRequest->(调用执行Controller方法,获取返回值)执行绑定参数方法getMethodArgumentValues->(对方法的参数遍历 调用参数绑定方法) 返回绑定好的pojoresolveArgument(逐个参数进行绑定,调用指定的参数解析器) 绑定到Pojo 返回mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);//调用拦截器的后置方法PostHandle controller后执行执行mappedHandler.applyPostHandle(processedRequest, response, mv);}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);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {//调用拦截器的after方法 最终执行mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}
handlerMapping(5个)和handlerAdapters(4个)
参数解析器(27个,对应各种注解比如@PathVariable、 @RequestHeader、@RequestParam、@CookieValue等注解的解析器)
返回值解析器(15个,对应ModelAndView、model、@responseBody的注解的解析器)
4:响应返回值处理
主要分析响应的返回值怎么在DispatcherServlet的doDispatch()方法中的执行流程
4.1:代码实例
/*** http://localhost:8080/response/p1* * @ResponseBody 注解返回json*/@GetMapping("/response/p1")@ResponseBodypublic Person p1(){Person person=new Person();person.setId(1);person.setName("麻子");person.setDate(new Date());return person;}
4.2:源码分析到-DispatcherServlet
源码分析第一步:执行controller方法,得到返回值。
源码分析第二步:根据返回值找到15个返回值处理器,遍历根据注解@ResponseBody,找到合适的返回值处理器
源码分析第三步:根据返回值处理器,去他的方法里边处理返回值
源码分析第四步:选择消息转换器(转换器很多,这里是jackson的消息转换器,把pojo转换成json)
//第一步 :执行controller方法,得到返回值public void invokeAndHandle (ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object...providedArgs) throws Exception {//这里就是执行controller方法得到的返回值Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}} else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {//将返回值封装成为自己指定的类型 比如json 或者xml等this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);} catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;}}//第二步:根据返回值找到15个返回值处理器,遍历 根据注解@ResponseBody,找到合适的返回值处理器@Overridepublic void handleReturnValue (@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {//在很多的返回值(15个如下图)处理器中选择合适的 返回json使用的是//RequestResponseBodyMethodProcessor 处理返回json的处理器HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());}//指定的json返回值处理方法handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}//第三步:注解 @ResponseBody的方法 使用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.//消息转化器 处理成json的方法writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}//第四步:选择消息转换器(转换器很多,这里是jackson的消息转换器,把pojo转换成json)protected <T > void writeWithMessageConverters (@Nullable T value, MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {Object body;Class<?> valueType;Type targetType;if (value instanceof CharSequence) {body = value.toString();valueType = String.class;targetType = String.class;} else {body = value;//value就是返回值 比如pojo的User的值valueType = getReturnValueType(body, returnType);//返回值类型UsertargetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());}if (isResourceType(value, returnType)) {outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes");if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null &&outputMessage.getServletResponse().getStatus() == 200) {Resource resource = (Resource) value;try {List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value());body = HttpRange.toResourceRegions(httpRanges, resource);valueType = body.getClass();targetType = RESOURCE_REGION_LIST_TYPE;} catch (IllegalArgumentException ex) {outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());}}}MediaType selectedMediaType = null;MediaType contentType = outputMessage.getHeaders().getContentType();boolean isContentTypePreset = contentType != null && contentType.isConcrete();if (isContentTypePreset) {if (logger.isDebugEnabled()) {logger.debug("Found 'Content-Type:" + contentType + "' in response");}selectedMediaType = contentType;} else {HttpServletRequest request = inputMessage.getServletRequest();List<MediaType> acceptableTypes;try {//这里是request的accept的类型 表示浏览器接受的类型 9个 逗号分割// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,// image/avif,image/webp,image/apng,*/*;q=0.8,// application/signed-exchange;v=b3;q=0.7acceptableTypes = getAcceptableMediaTypes(request);} catch (HttpMediaTypeNotAcceptableException ex) {int series = outputMessage.getServletResponse().getStatus() / 100;if (body == null || series == 4 || series == 5) {if (logger.isDebugEnabled()) {logger.debug("Ignoring error response content (if any). " + ex);}return;}throw ex;}//这里就是服务器生产数据的类型//application.json等4个List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);if (body != null && producibleTypes.isEmpty()) {throw new HttpMessageNotWritableException("No converter found for return value of type: " + valueType);}List<MediaType> mediaTypesToUse = new ArrayList<>();//服务器产出类型和浏览器接受类型的匹配for (MediaType requestedType : acceptableTypes) {for (MediaType producibleType : producibleTypes) {if (requestedType.isCompatibleWith(producibleType)) {mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));}}}if (mediaTypesToUse.isEmpty()) {if (logger.isDebugEnabled()) {logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);}if (body != null) {throw new HttpMediaTypeNotAcceptableException(producibleTypes);}return;}MediaType.sortBySpecificityAndQuality(mediaTypesToUse);for (MediaType mediaType : mediaTypesToUse) {if (mediaType.isConcrete()) {selectedMediaType = mediaType;break;} else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;break;}}if (logger.isDebugEnabled()) {logger.debug("Using '" + selectedMediaType + "', given " +acceptableTypes + " and supported " + producibleTypes);}}if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();//这里就是消息转换器的类型messageConverters 遍历 有转json、xml、Model、modelView、view等好多个//找到转jackson的消息转换器for (HttpMessageConverter<?> converter : this.messageConverters) {GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ?((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(),inputMessage, outputMessage);if (body != null) {Object theBody = body;LogFormatUtils.traceDebug(logger, traceOn ->"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {genericConverter.write(body, targetType, selectedMediaType, outputMessage);} else {//这里就是真正的调用jacksonConverter消息转换器 将实体转换成json 放到Response中//依赖ObjectMapper mapper = new ObjectMapper();//String json = mapper.writeValueAsString(User);((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);}} else {if (logger.isDebugEnabled()) {logger.debug("Nothing to write: null body");}}return;}}}if (body != null) {Set<MediaType> producibleMediaTypes =(Set<MediaType>) inputMessage.getServletRequest().getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) {throw new HttpMessageNotWritableException("No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'");}throw new HttpMediaTypeNotAcceptableException(getSupportedMediaTypes(body.getClass()));}}}
响应的参数解析的返回值处理器如下15个
浏览器接受格式、服务器返回格式、交集
消息转换器10个(可以处理String、byte、json、Resource等)
相关文章:

SpringBoot_第五章(Web和原理分析)
目录 1:静态资源 1.1:静态资源访问 1.2:静态资源源码解析-到WebMvcAutoConfiguration 2:Rest请求绑定(设置put和delete) 2.1:代码实例 2.2:源码分析到-WebMvcAutoConfiguratio…...

4-2 Linux进程和内存概念
文章目录前言进程状态进程优先级内存模型进程内存关系前言 进程是一个其中运行着一个或多个线程的地址空间和这些线程所需要的系统资源。一般来说,Linux系统会在进程之间共享程序代码和系统函数库,所以在任何时刻内存中都只有代码的一份拷贝。 进程状态…...

【微信小程序】计算器案例
🏆今日学习目标:第二十一期——计算器案例 ✨个人主页:颜颜yan_的个人主页 ⏰预计时间:30分钟 🎉专栏系列:我的第一个微信小程序 计算器前言实现效果实现步骤wxmlwxssjs数字按钮事件处理函数计算按钮处理事…...

408 计算机基础复试笔记 —— 更新中
计算机组成原理 计算机系统概述 问题一、冯诺依曼机基本思想 存储程序:程序和数据都存储在同一个内存中,计算机可以根据指令集执行存储在内存中的程序。这使得程序具有高度灵活性和可重用性。指令流水线:将指令分成若干阶段,每…...
找出最大数-课后程序(Python程序开发案例教程-黑马程序员编著-第二章-课后作业)
实例6:找出最大数 “脑力大乱斗”休闲益智游戏的关卡中,有一个题目是找出最大数。本实例要求编写程序,实现从输入的任意三个数中找出最大数的功能。 实例分析 对于3个数比较大小,我们可以首先先对两个数的大小进行比较ÿ…...

Java——N叉树的层序遍历
题目链接 leetcode在线oj题——N叉树的层序遍历 题目描述 给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。 树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例&…...
【Kubernetes】第十八篇 - k8s 服务发现简介
一,前言 上一篇,介绍了阿里云 ECS 服务器重启后的环境修复; 本篇,介绍 k8s 服务发现; 二,服务发现简介 当 A服务依赖了 B服务,而 B服务的IP和端口未知(或相对不固定)&…...
Codeforces Round 856 (Div. 2) 最好ak的div2
最近几场的div2 E都是一个思路啊,代码大差不差的,感觉随便ak啊。 A. Prefix and Suffix Array 题意 给你前n−1n-1n−1个字符串前缀和后n−1n-1n−1个字符串后缀,判断原字符串是否是回文串 思路 相同长度的判断是否是对称的即可。 代码 B C…...
最新JVM技术: GraalVM,让你一文了解它的方方面面
1. 什么是GraalVM? GraalVM是一种开源的虚拟机平台,由Oracle公司开发。它支持多种编程语言,包括Java、JavaScript、Python、Ruby、R、C++等,旨在提高应用程序的性能和扩展性。 GraalVM通过提供即时编译器(Just-in-Time Compiler,JIT)和Ahead-of-Time(AOT)编译器来提…...
MySQL索引失效的场景
1.like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效。 2.数据库表数据量过小 如果表的数据量非常小,则MySQL可能不会使用索引,因为它认为全表扫描的代价更小。 3.or语句前后没有同时使用索引 …...

Java - 对象的比较
一、问题提出 前面讲了优先级队列,优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够进行比较,为了简单起见,我们只是插入了Integer类型, 那优先级队列中能否插入自定义类型对象呢…...

[算法]选择排序
目录 1、选择排序的实现 2、例子 3、代码实现 4、时间复杂度和空间复杂度 5、选择排序的缺点——不稳定性 1、选择排序的实现 选择排序就是每一轮选择最小的元素直接交换到左侧。这种排序的最大优势,就是省去了多余的元素交换。 2、例子 原始数组和选择排序的…...

dp模型——状态机模型C++详解
状态机定义状态机顾名思义跟状态有关系,但到底有什么关系呢。在实际解决的时候,通常把状态想成节点,状态的转换想成有向边的有向图,我们来举个例子。相信大家都玩过类似枪战的游戏(没玩过的也听说过吧)&…...

1.4 条件概率与乘法公式
1.4.1 条件概率在实际问题中,除了直接考虑某事件 B 发生的概率P(B)外,有时还会碰到这样的问题,就是“在事件A 已经发生的条件下,事件B 发生的概率”。一般情况下,后概率与前一概率不同,为了区别,我们常把后者称为条件概率,记为P(B…...

VITA/PYTHON/LUPA families
Image Sensor Group Top to Bottom Portfolio in Industrial Imaging Machine Vision • Factory automation and inspection • Robotic vision • Biometrics High-End Surveillance • Aerial Surveillance • Intelligent Traffic Systems (ITS) • Mapping Medical and Sc…...

ChatGPT概述:从模型训练到基本应用的介绍
ChatGPT概述:从模型训练到基本应用的介绍 目录 本文是对ChatGPT的由来、训练过程以及实际落地场景的解释,主要内容包括如下三个方面: 1、ChatGPT是什么 2、ChatGPT的原理 3、ChatGPT的思考 4、ChatGPT的应用 ChatGPT是什么 ChatGPT可能是近…...

C语言实现扫雷【详细讲解+全部源码】
扫雷的实现1. 配置运行环境2. 扫雷游戏的初步实现2.1 建立扫雷分布模块2.2 创建名为board的二维数组并进行棋盘初始化2.3 打印棋盘3. 接下来该讨论的事情3.1 布置雷3.2 排查雷3.3 统计坐标周围有几个雷4. 完整扫雷游戏的实现4.1 game.h4.2 game.c4.3 扫雷.c1. 配置运行环境 本游…...

Vue2.0开发之——购物车案例-Goods组件封装-商品名称和图片(46)
一 概述 循环渲染Goods组件为Goods组件封装title属性为Goods组件封装pic属性 二 循环渲染Goods组件 2.1 App.vue中导入Goods组件 import Goods from /components/Goods/Goods.vue2.2 App.vue中注册Goods组件 components: {Header,Goods}2.3 循环渲染每一个商品的信息 <…...

0201基础-组件-React
1 组件和模块 1.1 模块 对外提供特定功能的js程序,一般就是一个js文件 为什么拆分模块呢?随着业务逻辑增加,代码越来越多,越来越复杂。作用:复用js,简化js,提高js运行效率 1.2 模块化 当应用…...

论文笔记 | Conducting research in marketing with quasi-experiments
这篇论文是Journal of Marketing上的论文,讲了使用准实验来进行论文研究的一些事项。外生性识别的来源、几种准实验方法的注意点还有内生性的解决。 这篇论文对于准实验或者是平常论文的展开有一个非常友善的指导功能,可以阅读~ 摘要:本文旨…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...