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) 


debug=true

#默认静态资源拦拦截所有请求
#spring.mvc.static-path-pattern=/**

#自定义静态资源拦截请求  用于拦截器过滤器 区分controller和静态访问路径
#http://localhost:8080/static/2.png 请求路径前边加static 才能访问这4个静态png
spring.mvc.static-path-pattern=/static/**

#设置自己的静态资源存放位置 该位置的才能访问 其他位置的访问不了
#http://localhost:8080/static/2.png 请求路径前边加static public下边的2.png 其他的报错
spring.web.resources.static-locations=classpath:/public


上边的下边的二选一配置即可




#自定义静态资源位置 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);
            }
        });
    }




//此处代码表示如果static资源路径不是/** 自己定义了静态资源路径 不能直接
//8080 端口直接访问静态资源

if (indexHtmlResource != null && "/**".equals(staticPathPattern)) {
			return new WelcomePage("forward:index.html", false);
		}
		if (templateAvailabilityProviders.getProvider("index", applicationContext) != null) {
			return new WelcomePage("index", true);
		}
		return UNRESOLVED;

源码分析我们知道在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 重写了request的mathod为put 会进入Servlet的put方法
//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 转达重定向Controlle {

    /**
     * 转发到其他的controller 设置请求域中的属性值
     * forward:/success
     */
    @GetMapping(value = "/forward")
    public String forward(HttpServletRequest request) {
        request.setAttribute("msg", "转发成功!");
        request.setAttribute("code", 200);
        return "forward:/success";
    }


    /**
     * 转发到其他的controller 获取请求域中的属性值
     *
     * @RequestAttribute(value = "msg") 获取请求域中的属性值
     */
    @GetMapping(value = "/success")
    @ResponseBody
    public Map<String, Object> success1(@RequestAttribute(value = "msg") String msg1,
                                        @RequestAttribute(value = "code") Integer code1,
                                        HttpServletRequest request) {
        String msg = (String) request.getAttribute("msg");
        Integer code = (Integer) request.getAttribute("code");
        Map map = new HashMap();
        map.put("msg", msg);
        map.put("code", code);
        map.put("msg1", msg1);
        map.put("code1", code1);

        return map;
    }


    /**
     * 重定向 其他的controller 设置请求域中的属性值
     * forward:/success
     */
    @GetMapping(value = "/redirect")
    public String redirect(HttpServletRequest request, RedirectAttributes redirectAttributes) {
        //redirectAttributes 传递参数
        redirectAttributes.addAttribute("msg1", "1-重定向成功");
        redirectAttributes.addAttribute("code1", "1-400");
        String msg="重定向成功";
        //直接传递参数
        return "redirect:/success2?name=test";
    }


    /**
     * 转发到其他的controller 获取请求域中的属性值
     *
     * @RequestAttribute(value = "msg") 获取请求域中的属性值
     */
    @GetMapping(value = "/success2")
    @ResponseBody
    public Map<String, Object> success2(@RequestParam(value = "name", required = false) String name,
                                        @RequestParam(value = "msg1", required = false) String msg1,
                                        @RequestParam(value = "code1", required = false) String code1,
                                        HttpServletRequest request) {

        Map map = new HashMap();
        map.put("name", name);
        map.put("msg1", msg1);
        map.put("code1", code1);
        return map;
    }


}

3.3:源码分析到-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:代码起源于servlet接口的service(ServletRequest req, ServletResponse res)

然后servlet接口的抽象实现类HttpServlet的

service(HttpServletRequest req, HttpServletResponse resp)方法里边根据方法名字get、post调用不同的实现类FrameworkServlet的doGet(HttpServletRequest req, HttpServletResponse resp)方法

然后调用到的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,找到合适的返回值处理器,这里的是RequestResponseBodyMethodProcessor返回值处理器

源码分析第三步:根据返回值处理器,去他的方法里边处理返回值

源码分析第四步:选择消息转换器(转换器很多,这里是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个,这里使用RequestResponseBodyMethodProcessor

浏览器接受格式、服务器返回格式、交集 

 

 消息转换器10个(可以处理String、byte、json、Resource等) 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值