SpringBoot_第五章(Web和原理分析)

news2024/12/26 12:07:49

目录

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;



	@Override
	protected 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 delete
			String 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

@Bean
    public 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")
    @ResponseBody
    public 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")
    @ResponseBody
    public 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")
    @ResponseBody
    public 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等handlerMapping
                mappedHandler = 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->(对方法的参数遍历 调用参数绑定方法) 返回绑定好的pojo
                                                           resolveArgument(逐个参数进行绑定,调用指定的参数解析器) 绑定到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 afterCompletion
                if (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")
    @ResponseBody
    public 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,找到合适的返回值处理器

        @Override
        public 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处理器
        @Override
        public 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);//返回值类型User
                targetType = 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.7

                    acceptableTypes = 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等) 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/390272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

穿越时空:当ChatGPT遇见stable-diffusion,你不敢相信的创意艺术之旅!

前言 欢迎来到一场创意的旅程&#xff0c;这里将聚焦于 ChatGPT 和 stable-diffusion 这两个令人激动的技术。在这篇文章中&#xff0c;我们将会探索这两种技术如何结合使用&#xff0c;为艺术创作带来全新的可能性。我们将探讨如何利用 ChatGPT 生成富有想象力的创意&#xf…

Mac Book pro(M1)使用总结

1、拿到电脑激活的时候&#xff0c;一定要记住账号密码及安全问题的答案。 2、显示隐藏文件夹&#xff1a; 3、显示.git或者gitignore等隐藏后缀的文件&#xff1a; 打开终端 defaults write com.apple.finder AppleShowAllFiles TRUE重启Finder在终端输入 killall Finder …

如何在虚拟机中安装ikuai软路由系统

首先访问ikuai官网下载固件固件下载-爱快 iKuai-商业场景网络解决方案提供商 (ikuai8.com) 根据需求下载 然后创建一个虚拟机&#xff0c;点击下一步 选择更下载的ISO映像文件&#xff0c;点击下一步 点击下一步 设置一下名称和储存位置&#xff0c;点击下一步 根据需求设置&a…

k8s集群调度

1、调度约束Kubernetes 是通过 List-Watch 的机制进行每个组件的协作&#xff0c;保持数据同步的&#xff0c;每个组件之间的设计实现了解耦。1.1 List-Watch 用户通过kubectl命令提交请求交给apiserver创建pod&#xff0c;然后apiserver将pod信息存放到etcd中&#xff0c;之后…

并发编程——ReentrantLock

如果有兴趣了解更多相关内容&#xff0c;欢迎来我的个人网站看看&#xff1a;耶瞳空间 一&#xff1a;基本介绍 从Java 5开始&#xff0c;引入了一个高级的处理并发的java.util.concurrent包&#xff0c;它提供了大量更高级的并发功能&#xff0c;能大大简化多线程程序的编写…

shell基础(5)算数计算:运算语法、自增自减

文章目录1. shell算数运算的特点2. 运算符一览3. 运算语法3.1 整形运算3.2. 小数运算 ing4. 自增自减4.1. a与a4.2. 自加1. shell算数运算的特点 Shell 和其它编程语言不同&#xff0c;Shell 不能直接进行算数运算&#xff0c;必须使用数学计算命令。Shell只支持整数运算&#…

C++017-C++冒泡排序与插入排序

文章目录C017-C冒泡排序与插入排序冒泡排序与插入排序目标冒泡排序排序规则冒泡排序优化插入排序题目描述在线练习&#xff1a;总结C017-C冒泡排序与插入排序 在线练习&#xff1a; http://noi.openjudge.cn/ https://www.luogu.com.cn/ 冒泡排序与插入排序 参考&#xff1a;…

详解基于 Celestia、Eclipse 构建的首个Layer3 链 Nautilus Chain

以流支付为主要概念的Zebec生态&#xff0c;正在推动流支付这种新兴的支付方式向更远的方向发展&#xff0c;该生态最初以Zebec Protocol的形态发展&#xff0c;并从初期的Solana进一步拓展至BNB Chian以及Near上。与此同时&#xff0c;Zebec生态也在积极的寻求从协议形态向公链…

【PyTorch】教程:torch.nn.Hardsigmoid

torch.nn.Hardsigmoid 原型 CLASS torch.nn.Hardsigmoid(inplaceFalse) 参数 inplace (bool) – 默认为 False 定义 Hardsigmoid(x){0if x≤−3,1if x≥3,x/61/2otherwise\text{Hardsigmoid}(x) \begin{cases} 0 & \text{if~} x \le -3, \\ 1 & \text{if~} x \ge 3…

PHP<=7.4.21 Development Server源码泄露漏洞

PHP<7.4.21 Development Server源码泄露漏洞php启动内置web服务器漏洞利用原理因为特殊的原因CTF荒废了一段时间&#xff0c;近期总算再次捡了起来&#xff0c;算是从头开始了吧。近期比赛刚好遇到了这个漏洞&#xff0c;看国内似乎还没有过多的论述&#xff0c;先总结一波。…

【自然语言处理】【大模型】大语言模型BLOOM推理工具测试

相关博客 【自然语言处理】【大模型】大语言模型BLOOM推理工具测试 【自然语言处理】【大模型】GLM-130B&#xff1a;一个开源双语预训练语言模型 【自然语言处理】【大模型】用于大型Transformer的8-bit矩阵乘法介绍 【自然语言处理】【大模型】BLOOM&#xff1a;一个176B参数…

RocksDB 架构

文章目录1、RocksDB 摘要1.1、RocksDB 特点1.2、基本接口1.3、编译2、LSM - Tree2.1、Memtable2.2、WAL2.3、SST2.4、BlockCache3、读写流程3.1、读取流程3.2、写入流程4、LSM-Tree 放大问题4.1、放大问题4.2、compactionRocksDB 是 Facebook 针对高性能磁盘开发开源的嵌入式持…

如何防止用户打开浏览器开发者工具?

大家好&#xff0c;我是前端西瓜哥。作为一名前端开发&#xff0c;在浏览一些网页时&#xff0c;有时会在意一些交互效果的实现&#xff0c;会打开开发者工具查看源码实现。 但有些网站做了防窥探处理&#xff0c;打开开发者工具后&#xff0c;会无法再正常进行网页的操作。 …

Jeston与Px4(四)

ROS控制PX4 上一节里我们已经将mavros和仿真gazebo搭建完毕&#xff0c;这一节将通过ros来实现对接PX4固件的目标 文章目录ROS控制PX41、搭建PX4开发固件环境1、搭建PX4开发固件环境 “永远不要使用sudo来修复权限问题&#xff0c;否则会带来更多的权限问题&#xff0c;需要重…

PMP项目管理引论介绍

目录1. 指南概述和目的1.1 项目管理标准1.2 道德与专业行为规范2 基本要素2.1 项目2.2 项目管理的重要性2.3 项目、项目集、项目组合以及运营管理之间的关系2.3.1 概述2.3.2. 项目组合与项目集管理2.3.3. 运营管理2.3.4. 组织级项目管理和战略2.3.5. 项目管理2.3.6. 运营管理与…

下载BSP并编译内核

前提&#xff1a;用到的开发板100ask_imx6ull 下载BSP 100ask_imx6ull 开发板的 BSP 都保存在 Git 站点上&#xff0c;通过 repo 命令进行统一管理。配置 repo git config --global user.email "user100ask.com" book100ask:~$ git config --global user.name &qu…

spring源码篇(3)——bean的加载和创建

spring-framework 版本&#xff1a;v5.3.19 文章目录bean的加载bean的创建总结getBean流程createBean流程doCreateBean流程bean的加载 beanFactory的genBean最常用的一个实现就是AbstractBeanFactory.getBean()。 以ApplicationContext为例&#xff0c;流程是: ApplicationCon…

01 C语言计算

C语言计算 1、变量 用途&#xff1a;需要存放输入的数据 定义格式&#xff1a;数据类型 变量名&#xff08;用于区分其他变量&#xff09; 变量名格式&#xff1a;只能由字母/下划线/数字构成&#xff0c;首位不能是数字&#xff1b;且变量名不能是标识符 **变量赋值和初始…

Python每日一练(20230305)

目录 1. 正则表达式匹配 ★★★ 2. 寻找旋转排序数组中的最小值 II ★★★ 3. 删除排序链表中的重复元素 II ★★ 1. 正则表达式匹配 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 . 和 * 的正则表达式匹配。 . 匹配任意单个字符* 匹配零个或多个…

格式化字符串之在栈上修改got表,执行system(“/bin/sh“)

题目自取&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1sZyC-d47cnrjQ0rmRNLbSg?pwdiung 提取码&#xff1a;iung 这是一题改got表的格式化字符串的例题 这里介绍下pwntools里的一个脚本 fmtstr_payload&#xff1a; 举个例子&#xff0c; payload fmtstr_payl…