0. 自动装配原理

0.0 pom.xml

  • 依赖一个父项目,主要管理项目的资源过滤和插件

    <parent>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-parent</artifactId>
    	<version>2.6.2</version>
    	<relativePath/> <!-- lookup parent from repository -->
    </parent>
    
  • 进去还有一个父依赖,才是真正管理 SpringBoot 应用里面所有依赖版本的地方, SpringBoot 的版本管理中心

     <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.6.2</version>
      </parent>
    

0.1 底层注解

  1. @Configuration,他还是一个 @Component 的别名,也是代替Spring xml 配置文件用的。 proxyBeanMethods()属性表明是不是代理对象调用方法,用于保持 Bean (组件)单实例,总是从容器中获取,Config 类本身是 Spring 增强了的代理对象,实例化 Config 调用方法获取组件,还是从容器里面拿。如果没有组件相互依赖,设置为 false,SpringBoot 就会跳过检查容器中是否有这个组件,启动会变快配置。

    • 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断,配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式。
    • @Configuration 和 @Component 区别:前者默认 CGLIB 代理
    • 以方法名作为组件的 id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
  2. @Import不仅能导入其他配置类,也能导入某个组件的 class,也能注册组件

  3. @Conditional条件装配,满足条件才注入。

    | @Conditional扩展注解 | 作用(判断是否满足当前指定条件) | | ——————————- | ———————————————— | | @ConditionalOnJava | 系统的java版本是否符合要求 | | @ConditionalOnBean | 容器中存在指定Bean; | | @ConditionalOnMissingBean | 容器中不存在指定Bean; | | @ConditionalOnExpression | 满足SpEL表达式指定 | | @ConditionalOnClass | 系统中有指定的类 | | @ConditionalOnMissingClass | 系统中没有指定的类 | | @ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean | | @ConditionalOnProperty | 系统中指定的属性是否有指定的值 | | @ConditionalOnResource | 类路径下是否存在指定资源文件 | | @ConditionalOnWebApplication | 当前是web环境 | | @ConditionalOnNotWebApplication | 当前不是web环境 | | @ConditionalOnJndi | JNDI存在指定项 |

    image-20220108174308636

  4. @ImportResource导入以前 Spring 的 xml 配置文件

  5. 配置绑定。使用 Java 读取到 properties 文件中的内容,并且把它封装到 JavaBean 中,和 Bean 的属性绑定。

    1. @ConfigurationProperties("my"),指定 properties 中的前缀,后面就是 Bean 属性。比如properties 中有 my.id,就会和 Bean 中的 id 属性绑定。需要加@Component注入为组件才能用这个注解
    2. 不加@Component,配置类上加@EnableConfigurationProperties(xx.class),指定类开启属性配置,也会自动注册到容器。第三方包加不了注解,可以用这种方法

0.1 启动器

  • 就是 SpringBoot 的启动场景。会导入所有模块运行需要的包。SpringBoot 将所有的功能场景都抽取出来,做成一个个的starter (启动器)。只需要在项目中引入这些 starter。所有相关的依赖都会导入进来

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    

0.2 主程序

  • 主启动类

    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
    • 无需以前的包扫描配置
    • 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.xxx”)或者@ComponentScan 指定扫描路径
    //@SpringBootApplication 来标注一个主程序类 , 说明这是一个Spring Boot应用
    @SpringBootApplication
    public class SpringbootApplication {
        public static void main(String[] args) {
            //以为是启动了一个方法,没想到启动了一个服务
            SpringApplication.run(SpringbootApplication.class, args)	
        }
    }
    
  • 进入这个注解还有几个主要关键注解

    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(
    excludeFilters = {@Filter(
    	type = FilterType.CUSTOM,
    	classes = {TypeExcludeFilter.class}
    ), @Filter(
    	type = FilterType.CUSTOM,
    	classes = {AutoConfigurationExcludeFilter.class}
    )}
    )
    
    1. @ComponentScan是 xml 配置转为 Java config 的 一个注解,用于自动扫描 Bean,对于 Bean 需要有 @Component 注解。这样一个 Bean 就被注册了

    2. @SpringBootConfiguration SpringBoot的配置类 ,标注在某个类上 , 表示这是一个提供给 SpringBoot的配置类。是Spring@Configuration的一个代替,这个可以自动被找到。这个注解也有

      @Configuration
      @Indexed
      public @interface SpringBootConfiguration {
      	@AliasFor(annotation = Configuration.class)
      	boolean proxyBeanMethods() default true;
      }
      
      @Component
      public @interface Configuration {
          @AliasFor(
              annotation = Component.class
          )
          boolean proxyBeanMethods() default true;
      }
      
    3. @EnableAutoConfiguration开启自动配置功能,SpringBoot可以自动帮我们配置。第一个注册我们写的,启动类包下组件,第二个注册 SpringBoot 帮我们自动配置的组件

      @AutoConfigurationPackage
      @Import(AutoConfigurationImportSelector.class)
      public @interface EnableAutoConfiguration {
      
      	Class<?>[] exclude() default {};
      	String[] excludeName() default {};
      
      }
      
      • @AutoConfigurationPackage主要是@import了个类,自动包注册。Registrar.class 作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到 Spring 容器,批量注册组件。元信息是获取了SpringBootLearningApplication的信息,因为注解标注在主启动类。再获取这个包名,把包里的内容转为数组,再注册进去。相当于把包下的组件批量注册进去

        @Import(AutoConfigurationPackages.Registrar.class)
        
        /**
        	 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
        	 * configuration.
        	 */
        static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        
            @Override
            public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
            }
        
            @Override
            public Set<Object> determineImports(AnnotationMetadata metadata) {
                return Collections.singleton(new PackageImports(metadata));
            }
        

        image-20220108164204481

      • @Import(AutoConfigurationImportSelector.class)getAutoConfigurationEntry(annotationMetadata)给容器导入组件,查看getAutoConfigurationEntry方法能看到一共有 137 个组件需要导入。文件里面写死了spring-boot一启动就要给容器中加载的所有配置类xxtAutoConfiguration。虽然启动时加载了这么多,但是是按需开启生效的,只有配置类中@Conditional注解满足条件,自动配置类才生效(使用了条件装配),是否生效流程是固定的,先判断组件或 class 有没有,后判断需不需要,最后在判断到底用谁的。

        1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
        2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
        3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
        4、从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件。spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories,里面就是自动配置类
        
        public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            }
            AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
        

        image-20220108170209690

      • 修改默认配置

        @Bean
        @ConditionalOnBean(MultipartResolver.class)
         //容器中有这个类型组件
        @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolv
        public MultipartResolver multipartResolver(MultipartResolver resolver) {
        //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
        //SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
        // Detect if the user has created a MultipartResolver but named it incorrectly
        	return resolver;
        }
        
  • 总结:

    • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration,都在springboot-autoconfigure的jar包中
    • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
    • 生效的配置类就会给容器中装配很多组件
    • 只要容器中有这些组件,相当于这些功能就有了
    • 定制化配置
      • 用户直接自己@Bean ww替换底层的组件
      • 用户去看这个组件是获取的配置文件什么值就去修改。 xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 —-> application.properties 所以在 application.properties 就能修改所有东西
  • 面试官问你你可以这样说,springboot 是通过 main 方法下的 SpringApplication.run 方法启动的,启动的时候他会调用 refshContext 方法,先刷新容器,然后根据解析注解或者解析配置文件的形式祖册 bean, 而它是通过启动类的 SpringBootApplication 注解进行开始解析的,他会根据 EnableAutoConfiguration 开启自动化配置,里面有个核心方法 ImportSelect 选择性的导入,根据 loadFanctoryNames 根据 classpash 路径以 MATA-INF/spring.factorces 下面以什么什么 EnableAutoConfiguration 开头的 key 去加载里面所有对应的自动化配置,他并不是把这一百二十多个自动化配置全部导入,在他每个自动化配置里面都有条件判断注解,先判断是否引入相互的 jar 包,再判断容器是否有 bean 再进行注入到 bean 容器

0.3 主启动类运行

主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;

SpringApplication这个类主要做了以下四件事情: 1、推断应用的类型是普通的项目还是Web项目 2、查找并加载所有可用初始化器 , 设置到initializers属性中 3、找出所有的应用程序监听器,设置到listeners属性中 4、推断并设置main方法的定义类,找到运行的主类

Run:

image-20220109094527033

1. 配置环境

yaml

yml 只是扩展别名

  1. key: value;kv之间有空格
  2. 大小写敏感
  3. 使用缩进表示层级关系
  4. 缩进不允许使用tab,只允许空格
  5. 缩进的空格数不重要,只要相同层级的元素左对齐即可
  6. ‘#‘表示注释
  7. 字符串无需加引号,如果要加,‘‘与"“表示字符串内容 会被 转义/不转义

1.0 数据类型

字面量:单个的、不可再分的值。date、boolean、string、number、null

 k: v

对象:键值对的集合。map、hash、set、object

行内写法: k: {k1:v1,k2:v2,k3:v3}
#或
k:
  k1: v1
  k2: v2
  k3: v3

数组:一组按次序排列的值。array、list、queue

行内写法: k: [v1,v2,v3]
#或者
k: 
 - v1
 - v2
 - v3

1.1 多环境配置

springboot 启动会扫描以下位置的 application.properties 或者 application.yml 文件作为 Spring boot的默认配置文件

项目内优先级:

优先级1:项目路径下的config文件夹配置文件 优先级2:项目路径下配置文件 优先级3:资源路径下的config文件夹配置文件 优先级4:资源路径下配置文件

**文件级优先级:**配置文件查找位置 (1) classpath 根路径 (2) classpath 根路径下config目录 (3) jar包当前目录 (4) jar包当前目录的config目录 (5) /config子目录的直接子目录

加载顺序:

  1. 当前jar包内部的application.properties和application.yml
  2. 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
  3. 引用的外部jar包的application.properties和application.yml
  4. 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml

指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

更优先:命令行激活java -jar xxx.jar --spring.profiles.active=prod --person.name=haha 文档一共 14 种配置方法,有更优先的

#选择要激活那个环境块
spring:
  profiles:
	active: prod

选择需要激活的环境:

#比如在配置文件中指定使用dev环境,我们可以通过设置不同的端口号进行测试;
#我们启动SpringBoot,就可以看到已经切换到dev下的配置了;
spring.profiles.active=dev

1.1.1 @Profile

条件装配功能:@Profile("test")注解加在类上或方法上,只有注解值对应的环境才生效。不同环境就会有不同的效果

profile分组

spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
使用:--spring.profiles.active=production 激活

2. Web

2.0 Spring MVC Auto-configuration

Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

The auto-configuration adds the following features on top of Spring’s defaults:

  • Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
  • Support for serving static resources, including support for WebJars (covered later in this document).
  • Automatic registration of Converter, GenericConverter, and Formatter beans.
  • Support for HttpMessageConverters (covered later in this document).
  • Automatic registration of MessageCodesResolver (covered later in this document).
  • Static index.html support.
  • Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.

If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.

2. 1 静态资源访问

  • 只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources

  • 访问 : 当前项目根路径/ + 静态资源名

  • 原理: 静态映射/**

    • 请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
  • 改变默认的静态资源访问路径和资源存放目录

    spring:
     mvc:
       static-path-pattern: /res/**
     resources:
       static-locations: [classpath:/haha/]
    

2.1.1 静态资源配置源码

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,生效
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
  • 构造器
  • 配置类只有一个有参构造器 ,有参构造器所有参数的值都会从容器中取得
//有参构造器所有参数的值都会从容器中确定
//WebProperties webProperties;获取和spring.web绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....
public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			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) {
        //判断是否 yaml 禁用了静态资源
        if (!this.resourceProperties.isAddMappings()) {
            logger.debug("Default resource handling disabled");
            return;
        }
        //webjar的目录
        addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        //静态资源目录,默认 /**,会在 mvcProperties 类中读取 spring.mvc 的属性,也可以自己在yaml 配置
        addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
            //得到静态资源具体位置,找 spring.web 的属性。是个字符串数组,并且有默认值,这就是四个默认静态资源位置的来源
            //private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" };
    registration.addResourceLocations(this.resourceProperties.getStaticLocations());
            if (this.servletContext != null) {
                ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION);
                registration.addResourceLocations(resource);
            }
        });
    }
    
  • 禁用所有静态资源(规则)

    spring:
      resources:
        add-mappings: false   禁用所有静态资源规则
    

SP doDispatch 源码

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 包装原始请求
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    // 是不是文件上传请求
    boolean multipartRequestParsed = false;
	
    // 有没有异步
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            // 第一个有用的功能,检查是不是文件上传,如果是进行转化,上传
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            // Determine handler for the current request.
            // 找到哪个 handler(也就是 controller的方法) 处理这个请求。可以找到哪个 controller 里的哪个方法。方法代码见第二个代码块
            mappedHandler = getHandler(processedRequest);
            
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            // 找当前请求的适配器
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            // 判断是不是 get 方法
            String method = request.getMethod();
            boolean isGet = HttpMethod.GET.matches(method);
            // 判断是 get 或者 head。head 不是服务器真正处理的
            if (isGet || HttpMethod.HEAD.matches(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            //执行拦截器的 preHandler,全部为 true 就继续执行目标方法,不然就 return 出去了
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            //真正执行目标方法。用得到的适配器,传入 handler 和 请求响应
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            
            // 执行拦截器的 post
            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) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

2.2 请求处理源码分析

2.2.1 请求映射

  • Restful: /user GET- 获取查询用户、DELETE- 删除用户、 PUT- 修改用户、 POST- 保存新增用户

patch 的使用: patch 是 2010 后成为的正式 http 方法,详见 RFC5789,它是对 put 的补充,在没有 patch 之前,我们都是用 put 进行更新操作,这时候我们的接口中通常会有一个逻辑规则,如:如果对象的的一个字符属性为 NULL,那么就是不更新该属性(字段)值,如果对象的字符属性是 “”,那么就更新该属性(字段)的值,通过这种方式来避免全部覆盖的操作。现在有了 patch 就解决了这种判断,在 put 接口中不管属性是不是 null,都进行更新,在 patch 接口中就对非 null 的进行更新;

  • 不前后分离,表单提交只有 get 和 post 两种方式。需要在前端加个隐藏域,也就是提交时候多点对象,把一个属性和一个值隐藏的传到后端。后端得到隐藏域的值,才是真实方法。后端拦截请求,获取隐藏域的值,也就是真正想要的请求方法,然后用包装模式重写 getMethod,返回传入的值,而不是原本的请求方式。然后再去调用正常处理请求的方法

Rest 原理(表单提交要使用 REST 的时候) ●表单提交会带上_method=PUT ●请求过来被 HiddenHttpMethodFilter 拦截 ○请求是否正常,并且是 POST ■获取到_method 的值。 ■兼容以下请求;PUT.DELETE.PATCH 原生 request(post),包装模式 requesWrapper 重写了 getMethod 方法,返回的是 传入的值。 过滤器链放行的时候用 wrapper。以后的方法调用 getMethod 是调用 requesWrapper 的。

2.2.2 请求映射原理(是咋找到控制层的方法处理这个请求)

请求都会先来 Spring 的DispatchServlet,是处理请求的开始。本质还是个 Servlet,就得有 doGet 和 doPost。子类HttpServletBean并没有实现,FrameworkServlet实现的 doGet 和 doPost 都调用他里面的processRequest(request, response);而这个方法设置一堆属性后又调用了doService(request, response);。这是个抽象方法,具体实现在DispatcherServlet

所以请求最终开始处理是在DispatcherServlet 中的 doService。doService 又在设置一堆属性后开始调用doDispatch,这里才是真正有用的方法。每个请求到来都要调用这个方法,并且这个方法由HandlerAdapters或处理程序本身来决定哪些方法可以接受。分析 SpringMVC 都要从org.springframework.web.servlet.DispatcherServlet 的 doDispatch 方法开始

image-20220109164715879


2.2.2 寻找handler

为了找到哪个controller 的方法,getHandler 会找handlerMappings,默认有五个**,所有的请求映射都在 HandlerMapping 中。**

请求一进来,遍历找这五个handlerMapping ,看谁能处理这个请求。从里面找有没有当前request中请求路径对应的 handler,有就返回这个 handler

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) {
      for (HandlerMapping mapping : this.handlerMappings) {
         HandlerExecutionChain handler = mapping.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}

五个handlerMapping

image-20220109175117812

SpringBoot 自动配置了默认的 RequestMappingHandlerMapping。

RequestMappingHandlerMapping:保存了所有 @RequestMapping 注解和 handler 的映射规则。是在SpringBoot 启动时 MVC 自动扫描所有 controller,并把注解信息保存到这个 HandlerMapping 里。并把每个

url 路径保存在了 mappingRegistry的一个 mapregistry,在 map 的值中记录了 mapping 地址,和处理这个 mapping 的方法。这样就能知道哪个类的哪个方法能处理哪个请求。

image-20220109175509668

image-20220109175946119

  • 也能自定义 HandlerMapping

找到之后会返回一个HandlerExecutionChain类型的handler,里面属性包含了 handlerimage-20220110132559145

2.3 参数处理和方法执行

2.3.1 找适配器,用于执行方法

拿着 handler 去找适配器。适配器去执行方法,因为需要用反射执行和获取参数

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

之前找到 handler,知道哪个控制层方法处理请求,Spirng 可以底层反射调用这个方法,但是还要知道方法的一堆参数,就把这个过程封装在了HandlerAdapter 里。现在适配器 adapter 就相当于一个反射工具,后面会用适配器调用 handler 方法。

  1. HandlerMapping 中找到能处理请求的 Handler(Controller.method())
  2. 为当前 Handler 找一个适配器 HandlerAdapter
    • HandlerAdapter 是个接口,里面俩方法,一个是supports看支持哪种 handler,一个是 handler 方法进行处理

    • 找 adapter 使用getHandlerAdapter方法,这个方法会找类中的属性handlerAdapters,他由initHandlerAdapters 方法初始化,里面存放了 4 个HandlerAdapter,每个完成不同的功能。然后遍历这四个看支不支持当前 handler 的,然后返回这个 adapter,就得到了适配器。一般都是找到RequestMappingHandlerAdapter来处理。

      protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
      		if (this.handlerAdapters != null) {
      			for (HandlerAdapter adapter : this.handlerAdapters) {
      				if (adapter.supports(handler)) {
      					return adapter;
      				}
      			}
      		}
      	}
      

      image-20220110124014553

      • 第 0 个是支持方法上标注 @RequestMapping
      • 第 1 个是函数式的
    • 如何知道适配器是否支持当前的handler:当前 handler 被封装为HandlerMethod,这个类里就包含了 hanlder 要执行的方法,也就是自己写在 controller 的方法,和对应的 Bean。然后调用 adapter 的 support 方法看是否支持,先判断 handler 是不是 HandlerMethod 类型,并且适配器支持这个类型。

      public final boolean supports(Object handler) {
          return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
      }
      
      protected boolean supportsInternal(HandlerMethod handlerMethod) {
          return true;
      }
      

2.3.2 执行目标方法和找方法参数

适配器执行目标方法并确定方法参数的每一个值。

//在 DispatcherServlet -- doDispatch
//找到了适配器,用适配器去执行目标方法。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  1. 步入这个调用会来到适配器的类,真正执行都在RequestMappingHandlerAdapter里,执行完会返回ModelAndView。 之后去执行目标方法
//执行目标方法
mav = invokeHandlerMethod(request, response, handlerMethod); 
  1. 去获取方法参数,要使用适配器里的参数解析器argumentResolvers,参数解析器一共有 27 个。 目标方法的每个参数值是什么是由这个决定的。用哪个注解,就用哪个解析器解析。 SpringMVC 目标方法能写多少种参数类型。取决于参数解析器。 这里把参数解析器和返回值处理器都 set 到invocableMethod
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
    //set 参数解析器
    invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// set 返回值处理器,看能写啥类型返回值
if (this.returnValueHandlers != null) {
    invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}

参数解析器:可以看到支持什么样子的参数传入 image-20220110131225966

返回值处理器:可以看到返回值类型

image-20220110134127265

  • 参数解析器实现了一个接口HandlerMethodArgumentResolver。接口第一个方法看当前解析器是否支持解析这种参数,因为会传一个参数的详细信息。支持就调用 resolveArgument image-20220110133323779
  1. 还配置了保存视图的

    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    modelFactory.initModel(webRequest, mavContainer, invocableMethod);
    mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
    
  2. 配置了参数和返回值等等等后,用invocableMethod执行目标方法和处理返回值的方法

    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    

    进来之后先执行请求,这个invokeForRequest就会来到自己写的 controller 的方法。真正执行目标方法的在这里

    //在 ServletInvocableHandlerMethod
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    

    调用内部方法,这个invokeForRequest方法第一步是获取所有参数值。真正确定参数值在这里。获取参数之后执行目标方法

    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
                                   Object... providedArgs) throws Exception {
        // 得到参数
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        //执行方法
        return doInvoke(args);
    }
    

2.3.3 确定方法每个参数的解析器

具体如何确定参数值就在这个方法里

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// 得到所有参数声明,参数的详细信息,注解、参数位置啥的都有
		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

    	//有几个参数就有几个 Object,参数具体内容就存这里
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
            // 获取第一个参数的详细信息
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            // 给参数赋值,确定参数 0 的值,解析了才有
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
            // 先判断解析器支不支持当前参数类型,步入会看到遍历所有解析看支不支持
            // supportsParameter 会返回 getArgumentResolver 方法
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
                // 用解析器,真赋值
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			catch (Exception ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}

image-20220110140638268


2.3.3.1 判断所有参数解析器那个支持解析这个参数

supportsParameter 就是看参数有没有对应注解,之后可能看参数类型。找到后返回对应参数解析器,supportsParameter 判断解析器不为空返回 true。放到 HashMap 当缓存,之后再找这个参数的解析器可以直接找到

private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
                    // 放入缓存
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}

2.3.4 解析参数值,正式给参数赋值

用解析器的 resolveArgument 解析参数名字和值。这个 resolvers 是 HandlerMethodArgumentResolverComposite 和下面的抽象类都实现了接口 HandlerMethodArgumentResolver

args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);

这个方法先获取当前参数的解析器,然后调用解析器的 resolveArgument 方法

// 当前的 resolveArgument 是 HandlerMethodArgumentResolverComposite 的
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
	NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    	// 之前调用是否有解析器时对应解析器放到了缓存,直接从缓存拿
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

这个 resolveArgument 是每个具体解析器继承了抽象类 AbstractNamedValueMethodArgumentResolver有的,每个解析器都一样。

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    	//得到参数 name,就是注解写的 name,还有 requite 等参数的值
		NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    	//得到参数类型
		MethodParameter nestedParameter = parameter.nestedIfOptional();

    	//获取参数 name,和 namedValueInfo 得到的差不多
		Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
		if (resolvedName == null) {
			throw new IllegalArgumentException(
					"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
		}
		
    	// 每个解析器都实现了不同的 resolveName,这个 resolveName 是抽象方法。
    	// 会得到真正的值
		Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
		if (arg == null) {
			if (namedValueInfo.defaultValue != null) {
				arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
			}
			else if (namedValueInfo.required && !nestedParameter.isOptional()) {
				handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
			}
			arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
		}
		else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
			arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
		}

		handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

		return arg;
	}

回到 getMethodArgumentValues中的循环继续获取第二个参数

解析这个参数就是调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可

获取参数的过程从invokeForRequest开始,通过 getMethodArgumentValues 获取全部参数。

getMethodArgumentValues循环用解析器去得到每个参数放入 args 数组。循环中调用内部的一个参数解析器,不同与真正的解析器,这个主要用于找到每个参数对应的解析器放入缓存,真正解析参数也通过他得到真正的解析器再去真正解析。

真正的解析器会调用他继承的抽象类resolveArgument去解析参数,并先得到参数 name,参数类型。我们在注解给予的信息,传入每个解析器都实现方法resolveName获取真正的值


从一个请求进入开始。doServlet 主要工作:先得到通过 handlerMappings 得到请求对应 handler(写的 controller 的方法就能得到,里面的五个 HandlerMapping 保存了每个请求映射和对应的 handlerMethod)。其次通过得到的 handler 去找对应适配器,遍历四个适配器的 support 方法看支持哪种 handler。

之后准备执行,调用适配器 handler 方法执行 handlerMehod。在invokeHandlerMethod 方法中给invocableMethod set 一堆配置后,调用 invokeAndHandle会处理方法和方法返回值,调用 invokeForRequest,方法获取 handlerMethod 的参数,到写的 controller 中反射执行方法,栈帧如下

image-20220110204404794

有了参数值,用反射执行方法,调用顺序是如下,最后就到反射 API 了

//DispatcherServlet.java
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 

//AbstractHandlerMethodAdapter.java
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
 throws Exception {
 return handleInternal(request, response, (HandlerMethod) handler);
}

//
mav = invokeHandlerMethod(request, response, handlerMethod); 

// RequestMappingHandlerAdapter.java
invocableMethod.invokeAndHandle(webRequest, mavContainer);

//ServletInvocableHandlerMethod.java
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

//InvocableHandlerMethod.java
doInvoke(args)

2.3.5 自定义参数类型解析

ServletModelAttributeMethodProcessor 这个解析器来解析,supportsParameter判断这个参数是不是简单类型,不是就能用这个解析器。自定义不是简单类型,能用这个解析

他的父类 ModelAttributeMethodProcessor中有方法构建了 pojo 实体类,主要步骤:

// 构建了一个值全为空的对象
attribute = createAttribute(name, parameter, binderFactory, webRequest);
// web 数据绑定器,将请求参数的值绑定到指定的 JavaBean 中
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
// 把请求数据绑定到对象,反射从 request 获取每个参数值,再反射找 bean 的属性,再把值封装到属性中
bindRequestParameters(binder, webRequest);

binder 里有 target 就是 attribute,空的实体类。还有 conversionService,里面有转换器 converters 用于转换类型,因为 http 传入的都为本文。会使用转换器把请求数据转换类型后,再用反射把值绑定到对象中。

public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
		Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		if (ann != null) {
			mavContainer.setBinding(name, ann.binding());
		}

		Object attribute = null;
		BindingResult bindingResult = null;

		if (mavContainer.containsAttribute(name)) {
			attribute = mavContainer.getModel().get(name);
		}
		else {
			// Create attribute instance
			try {
                // 构建了一个值全为空的对象
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
					attribute = Optional.empty();
				}
				else {
					attribute = ex.getTarget();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
            // web 数据绑定器,将请求参数的值绑定到指定的 JavaBean 中
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
                    // 把请求数据绑定到对象
					bindRequestParameters(binder, webRequest);
				}
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

binder 绑定过程

bindRequestParameters 会先把 binder 转换类型再调用绑定

protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
    ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
    Assert.state(servletRequest != null, "No ServletRequest");
    ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
    servletBinder.bind(servletRequest);
}

绑定过程:

public void bind(WebRequest request) {
    	// mpvs 先获取原生 request 所有 kv 对。相当于拿到请求传过来的所有参数
    MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
    if (request instanceof NativeWebRequest) {
		...
    }
    doBind(mpvs);
}

mpvs 的内容:

image-20220111185611141

GenericConversionService在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer) byte – > file

自定义转换器

可能会自定义提交数据,比如用逗号分割不同类型数据。自定义需要对 Spring MVC 进行定制,就需要在配置类添加个 MVC 配置,扩展功能

public void addFormatters(FormatterRegistry registry) {
    registry.addConverter((Converter<String, Pet>) s -> {
        if (StringUtils.hasLength(s)) {
            Pet pet = new Pet();
            String[] split = s.split(",");
            pet.setAge(Integer.parseInt(split[0]));
            pet.setName(split[1]);
            return pet;
        }
        return null;
    });
}

2.4 执行后处理返回值

2.4.1 返回视图

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); 方法派发处理结果

renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);合并数据到请求域

2.5 请求参数和基本注解

2.5. 1注解

@PathVariable:路径变量,还可以是Map<String, String>会把所有 name 和值放进去

@GetMapping("/user/{id}/{name}")
    public Map<String, Object> annotation(@PathVariable("id") int id,
                                          @PathVariable("name") String nn,
                                          Map<String, String> pathMap) 

@RequestHeader:获取请求头,浏览器信息啥的, Map<String, String>, MultiValueMap<String, String>, HttpHeaders可以拿到所有

@RequestHeader("User-Agent") String userAgent
@RequestHeader HttpHeaders header

@RequestParam:获取请求参数,一个属性多个值用这个。Map<String, String> 拿所有,hashMap,key 重复肯定拿不到俩,比如 inters 里面只有一个。url 需要lastParameter?age=3&inters=play&inters=music

@RequestParam("age") int age,
@RequestParam("inters") List<String> inters,
@RequestParam Map<String, String> requestParams

@CookieValue:获取 cookie 值,也可以声明 Cookie 类型拿所有。Cookie 类就像个实体类,有 get set 没有其他

@CookieValue("JSESSIONID") String JSESSIONID,
@CookieValue("JSESSIONID") Cookie cookie

image-20220109223816962

@RequestBody:获取请求体。Post 方法才有请求体,可以拿到表单全部数据

@PostMapping("/user")
public Map<String, Object> annotationPost(@RequestBody String body)

@RequestAttribute:获取 request 域属性,一般用于页面转发时取出请求域的数据

@Controller //转发不能用 rest
public class RequestController {
    
    @GetMapping("/goto")
    public String gotoPage(HttpServletRequest request) {
        request.setAttribute("msg", "success");
        request.setAttribute("code", "200");
        return "forward:/success"; //转发
    }
    
    @ResponseBody // 不做视图解析
    @GetMapping("/success")
    public Map success(@RequestAttribute("msg") String msg,
                       @RequestAttribute("code") Integer code,
                       HttpServletRequest request) {
        Object msg1 = request.getAttribute("msg");
        Map<String, Object> map = new HashMap<>();
        
        map.put("msg", msg);
        map.put("msg", msg1);
        return map;
    }
}

@MatrixVariable:矩阵变量,要绑定在路径变量中

以前带参数需要/cars/{psth}?xxx==xxx&...叫做 queryString 查询字符串,需要 @RequestParam 获取参数。

矩阵变量写在 path 中/car/{path;xx=xx;xx=xx,xx,xx,xx} 用分号的叫矩阵变量,逗号带多个参数

实际使用场景是:开始时禁用了 cookie,session 里内容怎么使用session.set(a, b) -> jsessionid -> cookie -> 每次发请求时携带。每次请求会带上 cookie,从 cookie 的 jsessionid 找服务器里的 session 对象,用 get 方法得到属性值。禁用了 cookie 就嘛也找不到了,这时可以用矩阵变量方式带 jsessionid,用路径(url)重写的方式解决。使用矩阵变量 /xx;jsessionid=xxxxxxx把 jsessionid 带过来。url 分号后面都是矩阵变量的东西,要和路径一起来看,是个整体,分号前面是访问路径,后面是矩阵变量。

可以用 Map<String, String> or MultiValueMap<String, String> 获取所有

SpringBoot默认禁用矩阵变量的功能

mvc 的自动配置类里有 configurePathMatch,里面的 urlPathHelper 默认禁用了分号,忽略了分号后面的内容。路径处理都是 urlPathHelper 来解析的。这个类里的 removeSemicolonContent 属性就是支持矩阵变量的。

所以需要修改 mvc 自动配置类的内容,有两种方式自定义 mvc 的类值类,一个是直接注册个 bean,一个是实现接口,接口都有默认方法,不需要全部修改

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        // 不移除分号后面内容
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}
@Configuration
public class WebConfig {

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除分号后面内容
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }
}

因为矩阵变量是用在路径变量里的,所以 controller 里的 mapping 注解也要写成路径变量的方式

@GetMapping("/user2/{path}") // 路径变量!
    public Map<String, Object> matrix(@MatrixVariable("id") int id,
                                      @MatrixVariable("name") List<String> name,
                                      @PathVariable("path") String path,
                                      @MatrixVariable MultiValueMap<String, String> valueMap) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("path", path);
        map.put("id", id);
        map.put("name", name);
        map.put("valueMap", valueMap);
        return map;
    }

如果不同路径有相同的变量,需要在注解指定路径@MatrixVariable(value = "id", pathVar = "user")

2.5.2 Servlet Api

WebRequest 、ServletRequest、 MultipartRequest、 HttpSession、 javax.servlet.http.PushBuilder、 Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

ServletRequestMethodArgumentResolver里支持的参数

public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    return (WebRequest.class.isAssignableFrom(paramType) ||
            ServletRequest.class.isAssignableFrom(paramType) ||
            MultipartRequest.class.isAssignableFrom(paramType) ||
            HttpSession.class.isAssignableFrom(paramType) ||
            (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
            (Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
            InputStream.class.isAssignableFrom(paramType) ||
            Reader.class.isAssignableFrom(paramType) ||
            HttpMethod.class == paramType ||
            Locale.class == paramType ||
            TimeZone.class == paramType ||
            ZoneId.class == paramType);
}

2.5.3 复杂类型参数

非 @RestController 返回视图用

Map、Model(map 、model 里面的数据会被放在 request 的请求域 ,相当于 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)ServletResponse (response)、SessionStatus 、UriComponentsBuilder、 ServletUriComponentsBuilder

Map<String,Object> map, Model model, HttpServletRequest request 都是可以给reques 域中放数据,数据都可用 request.getAttribute();取出

Map 和 modle 会放在请求域中

Map、Model 类型的参数,会返回 mavContainer.getModel() ---> BindingAwareModelMap 他是 Model 也是 Map,Map、Model内容都存在同一个对象中 都是调用 mavContainer.getModel (); 获取到值的

装入请求域是在处理返回值时完成的

需要视图的:目标方法执行完成将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。

2.5. 4自定义(实体类)

ServletModelAttributeMethodProcessor 来解析实体类参数

3. 数据响应和内容协商

主要是返回值处理

3.1 响应 Json

不用@RestCOntroller 的话

@Controller + @RespondBody 也返回 Json

//当前项目
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
// web 引入了 json
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.6.2</version>
    <scope>compile</scope>
</dependency>
//json 用的 jackson
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.1</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
    <version>2.13.1</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.13.1</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>
    <version>2.13.1</version>
    <scope>compile</scope>
</dependency>
</dependencies>

3.2 返回值解析器

invocableMethod 对象里面 set 了返回值解析器

image-20220110134127265

3.3 执行方法后返回

Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

invokeForRequest 执行完成会得到返回值

主要处理返回值在handleReturnValue,参数还带了返回值类型

this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
		// 执行完毕的返回值
		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 {
            // 处理返回值
			this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
		}
		catch (Exception ex) {
			if (logger.isTraceEnabled()) {
				logger.trace(formatErrorForReturnValue(returnValue), ex);
			}
			throw ex;
		}
	}

3.4 准备处理返回值

this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

遍历 HandlerMethodReturnValueHandlers找到一个支持这个返回值的并执行

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

和处理参数、类型 convert设计一样,有个 Composite,去遍历真正的处理方法,看那个处理方法能真正处理,然后在调用这个 Composite 去得到真正的处理类,然后调用真正的处理类执行处理。这个 Composite 的真正的处理类都实现了同样的接口,真正的处理类通用的方法会写在抽象类或父类。真的处理类一般也都会实现两个方法,是否支持,和真正处理的方法

总流程:

  1. 返回值处理器判断是否支持这种类型返回值 supportsReturnType
  2. 返回值处理器调用 handleReturnValue 进行处理
  3. RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
    1. 利用 MessageConverters 进行处理 将数据写为json
      1. 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
      2. 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
      3. SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        1. 得到MappingJackson2HttpMessageConverter可以将对象写为json
        2. 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

3.5 选择返回值处理器

因为返回 Json 会自然找到 RequestResponseBodyMethodProcessor

private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    	//先看是不是异步
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    	// 开始遍历处理器
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
				continue;
			}
            // 判断处理器支不支持这种返回类型
            // 这个方法就是判断返回值类型和处理器支持类型一样不一样
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}

处理其判断支持的类比较方法

public boolean supportsReturnType(MethodParameter returnType) {
	return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}

public boolean supportsReturnType(MethodParameter returnType) {
	return Model.class.isAssignableFrom(returnType.getParameterType());
}

// 看有没有对应注解
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||	returnType.hasMethodAnnotation(ResponseBody.class));
}

从这里也可以知道允许的返回值类型

ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;

3.6 开始处理返回值

handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);

先设置属性,包装原生请求响应

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.
    	// 使用消息转换器进行写出操作
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

交给 writeWithMessageConverters处理返回值,使用消息转换器进行写出操作,将数据写为 Json

流程:

  1. 先判断返回值类型
  2. 内容协商。浏览器接收内容,和服务器响应内容类型,找共同的
  3. 消息转换,找哪个能把返回值写出
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;
        valueType = getReturnValueType(body, returnType);
        targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
    }

    // 是不是资源类型(流数据)
    if (isResourceType(value, returnType)) {
		//流的处理方法
        ...
    }

    // 媒体类型,涉及内容协商,就是浏览器发请求时,请求头的 Accpet 里写的可以接收的内容类型
    //(请求头的 q=0.9 什么的表示权重,不写就是 1)
    // 相当于告诉服务器,浏览器的处理能力
    MediaType selectedMediaType = null;
    // 判断当前响应头中是否已经有确定的媒体类型。
    // 看响应头有没有媒体类型,因为可能前面处理有设置
    MediaType contentType = outputMessage.getHeaders().getContentType();
    // 有响应头就设置媒体类型为响应头的内容
    boolean isContentTypePreset = contentType != null && contentType.isConcrete();
    if (isContentTypePreset) {
        logger..
        selectedMediaType = contentType;
    }
    else {
        //调用原生请求,得到请求头的 Accpet 内容
        HttpServletRequest request = inputMessage.getServletRequest();
        List<MediaType> acceptableTypes;
        try {
            acceptableTypes = getAcceptableMediaTypes(request);
        }
        catch (HttpMediaTypeNotAcceptableException ex) {  异常处理和日志...   }
        
        // 服务器能响应的数据类型。因为 ResponseBody,都是 Json
        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()) {  异常处理和日志...   }

        MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

        //刚才找到能兼容的类型,写入 selectedMediaType
        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()) {  日志...   }
    }

    if (selectedMediaType != null) {
        selectedMediaType = selectedMediaType.removeQualityValue();
        // 遍历所有消息转换器,看哪个能转换成响应类型。这里是 Json
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            //converter 不是 GenericHttpMessageConverter,这个就为 null,不然强转
            GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                    (GenericHttpMessageConverter<?>) converter : null);
            // 看能不能支持写操作
            //方法具体:看支不支持返回值类性,看能不能写媒体类型
            if (genericConverter != null ?
                    ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
                    converter.canWrite(valueType, selectedMediaType)) {
                // body 就是返回值对象
                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 {
                        ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                    }
                }
                else {
                    if (logger.isDebugEnabled()) {  日志...   }
                }
                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()));
    }
}

3.6.1 媒体类型

媒体类型,涉及内容协商,就是浏览器发请求时,请求头的 Accpet 里写的可以接收的内容类型。请求头的 q=0.9 什么的表示权重,不写就是 1 相当于告诉服务器,浏览器的处理能力

3.6.2 消息转换器

实现了接口 HttpMessageConverter

image-20220111210029519

前两个方法接收媒体类型参数,看能不能以改类型读写

消息转换器就是看是否支持将此 Class 类型的对象,转为MediaType类型的数据。 例:Person对象转为JSON。或者 JSON转为Person

RequestResponseBodyMethodProcessor,既是argumentResolver也是returnValueHandler。

能处理返回值也能处理参数,就是处理 Json 的读写

通过使用HttpMessageConverter读写请求或响应的正文,处理用@RequestBody注释的方法参数,并处理来自用@ResponseBody注释的方法的返回值。

默认消息转换器

image-20220111212750356

0 - 只支持Byte类型的 1 - String 2 - String 3 - Resource 4 - ResourceRegion 5 - DOMSource.class \ SAXSource.class \ StAXSource.class \StreamSource.class \Source.class 6 - MultiValueMap 7 - true //都支持 8 - true

消息转换器 canWrite 方法

看该converters支不支持将该类型(返回值类型)的数据写出去(转为指定的media类型)

也就是比较类相同与否,看支不支持返回值类型。看能不能写媒体类型

public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
	return supports(clazz) && canWrite(mediaType);
}

public boolean supports(Class<?> clazz) {
	return byte[].class == clazz;
}

Jackson 的没有 supports,直接看 canWrite

public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
    	// 调用 canWrite 看能不能支持这个媒体类型
		if (!canWrite(mediaType)) {
			return false;
		}
		if (mediaType != null && mediaType.getCharset() != null) {
			Charset charset = mediaType.getCharset();
			if (!ENCODINGS.containsKey(charset.name())) {
				return false;
			}
		}
		ObjectMapper objectMapper = selectObjectMapper(clazz, mediaType);
		if (objectMapper == null) {
			return false;
		}
		AtomicReference<Throwable> causeRef = new AtomicReference<>();
    	// objectMapper 是 Jackson 底层组件
		if (objectMapper.canSerialize(clazz, causeRef)) {
			return true;
		}
		logWarningIfNecessary(clazz, causeRef.get());
		return false;
	}

protected boolean canWrite(@Nullable MediaType mediaType) {
		if (mediaType == null || MediaType.ALL.equalsTypeAndSubtype(mediaType)) {
			return true;
		}
    	// 看这个消息转换器支持的媒体类型,和我们需要的媒体类型是否匹配
		for (MediaType supportedMediaType : getSupportedMediaTypes()) {
			if (supportedMediaType.isCompatibleWith(mediaType)) {
				return true;
			}
		}
		return false;
	}

消息转换器写入

public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    // 添加响应头 Content-Type application/json
    final HttpHeaders headers = outputMessage.getHeaders();
    addDefaultHeaders(headers, t, contentType);

    if (outputMessage instanceof StreamingHttpOutputMessage) {
        StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) outputMessage;
        streamingOutputMessage.setBody(outputStream -> writeInternal(t, type, new HttpOutputMessage() {
            @Override
            public OutputStream getBody() {
                return outputStream;
            }
            @Override
            public HttpHeaders getHeaders() {
                return headers;
            }
        }));
    }
    else {
        // t 返回对象、返回类型、响应。写入响应,进入转换 Json 的过程
        writeInternal(t, type, outputMessage);
        //写给响应,响应得到 Json
        outputMessage.getBody().flush();
    }
}

MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)

3.7 内容协商原理

虽然代码都是返回同样类型,但是后端可以根据客户端 http 请求头 accpet 里接受的内容类型不同,去给不同的客户端返回不同的类型

1、判断当前响应头中是否已经有确定的媒体类型。MediaType 2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】

  • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
  • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型

3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person) 4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。 5、客户端需要【application/xml】。服务端能力【10种、json、xml】 6、进行内容协商的最佳匹配媒体类型 7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。

MediaType selectedMediaType = null;
// 判断当前响应头中是否已经有确定的媒体类型。
// 看响应头有没有媒体类型,因为可能前面处理有设置
MediaType contentType = outputMessage.getHeaders().getContentType();
// 有响应头就设置媒体类型为响应头的内容
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
if (isContentTypePreset) {
    logger..
        selectedMediaType = contentType;
}
else {
    //调用原生请求,得到请求头的 Accpet 内容
    HttpServletRequest request = inputMessage.getServletRequest();
    List<MediaType> acceptableTypes;
    try {
        //获取客户端(PostMan、浏览器)支持接收的内容类型(获取客户端Accept请求头字段)
        acceptableTypes = getAcceptableMediaTypes(request);
    }
    catch (HttpMediaTypeNotAcceptableException ex) {  异常处理和日志...   }

    // 服务器能响应的数据类型。因为 ResponseBody,都是 Json
    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()) {  异常处理和日志...   }

    //按权重排序
    MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

    //刚才找到能兼容的类型,写入 selectedMediaType
    // selectedMediaType 只有一个媒体类型
    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()) {  日志...   }
}

if (selectedMediaType != null) {
    selectedMediaType = selectedMediaType.removeQualityValue();
    // 遍历所有消息转换器,看哪个能转换成指定的响应媒体类型。这里是 Json
    for (HttpMessageConverter<?> converter : this.messageConverters) {
        //converter 不是 GenericHttpMessageConverter,这个就为 null,不然强转
        GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
        // 看能不能支持写操作
        //方法具体:看支不支持返回值类性,看能不能写媒体类型
        if (genericConverter != null ?
            ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
            converter.canWrite(valueType, selectedMediaType)) {
            // body 就是返回值对象
            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);
                }
  • getAcceptableMediaTypes 开始调用内容协商管理器,解析媒体类型。

     acceptableTypes = getAcceptableMediaTypes(request);
    
    // getAcceptableMediaTypes 直接返回
    return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
    

    具体内容就是获取request.getHeaderValues获取请求头数据,然后转为 List 返回,就得到了客户端能接受的媒体类型

    public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
        // strategy 一般就俩,参数和请求头
        for (ContentNegotiationStrategy strategy : this.strategies) {
            List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
            if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
                continue;
            }
            return mediaTypes;
        }
        return MEDIA_TYPE_ALL_LIST;
    }
    
  • 获取服务端能产生的媒体类型,会先遍历消息转换器,得到支持处理返回值对象的消息转换器,并把消息转换器能处理的媒体类型放入 List

    protected List<MediaType> getProducibleMediaTypes(
    			HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
        Set<MediaType> mediaTypes =
            (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
        if (!CollectionUtils.isEmpty(mediaTypes)) {
            return new ArrayList<>(mediaTypes);
        }
        List<MediaType> result = new ArrayList<>();
        for (HttpMessageConverter<?> converter : this.messageConverters) {
            if (converter instanceof GenericHttpMessageConverter && targetType != null) {
                if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
                    result.addAll(converter.getSupportedMediaTypes(valueClass));
                }
            }
            else if (converter.canWrite(valueClass, null)) {
                result.addAll(converter.getSupportedMediaTypes(valueClass));
            }
        }
        return (result.isEmpty() ? Collections.singletonList(MediaType.ALL) : result);
    }
    

3.8 自定义内容协商

3.8.1 开启浏览器参数内容协商

为了方便内容协商,开启基于请求参数的内容协商功能。让请求带个 format 参数,底层内容协商策略除了请求头内容协商策略还会多个参数内容协商策略,参数策略优先

spring:
  contentnegotiation:
    favor-parameter: true #开启请求参数内容协商模式

3.8.2 自定义消息转换器

实现多协议数据兼容。json、xml、x-guigu 0、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理 1、Processor 处理方法返回值。通过 MessageConverter 处理 2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写) 3、内容协商找到最终的 messageConverter

场景:浏览器请求返回 xml,ajax 请求返回 Json,app 请求返回自定义协议数据。内容协商一个请求处理多种数据。

xml 和 json 根据请求头就能处理,自定义数据的请求头也是自定义。就需要添加自定义的转换器,服务器会统计所有转换器支持的内容类型,进行内容协商

自定义

SpringMVC的什么功能。都在一个入口配置,给容器中添加一个 WebMvcConfigurer

自定义转换器:

public class PetMessageConverter implements HttpMessageConverter<Pet> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return clazz.isAssignableFrom(Pet.class);
    }

    /**
     * 服务器会统计所有转换器支持的内容类型
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return MediaType.parseMediaTypes("application/x=pet");
    }

    @Override
    public Pet read(Class<? extends Pet> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    public void write(Pet pet, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        //自定义数据写出
        String data = pet.toString();
        //写出数据
        OutputStream body = outputMessage.getBody();
        body.write(data.getBytes());
    }

}

加入 MvcConfig

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
	converters.add(new PetMessageConverter());
}

系统默认转换器何时配置

导入了jackson处理xml的包,xml的converter就会自动进来

WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);

if(jackson2XmlPresent)   {
    Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
    if (this.applicationContext != null) {
        builder.applicationContext(this.applicationContext);
    }
    messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}

参数自定义内容协商

自定义协商策略,就要自定义内容协商管理器,参数类型协商策略默认只有 xml 和 Json

这个参数内容是在内容协商时,浏览器给到服务端,服务端得到浏览器能接受的媒体类型,在accpetableTypes这个 List 里

public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    // 自定义参数和媒体类型要相对应,也就是参数支持的媒体类                
    // 自定义参数和媒体类型要相对应,也就是参数支持的媒体类型
    Map<String, MediaType> map = new HashMap<>();
    map.put("json", MediaType.APPLICATION_JSON);
    map.put("xml", MediaType.APPLICATION_XML);
    map.put("pet", MediaType.parseMediaType("application/x-pet"));
    // 参数内容协商策略, 指定参数对应媒体类型,会覆盖以前的
    ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(map);
    // 改变原 format 的默认名
    strategy.setParameterName("ff");
    // 放入内容协商管理器
    configurer.strategies(Arrays.asList(strategy));
    // 只增加一个
    configurer.mediaType("gg", MediaType.parseMediaType("application/x-pet"));
}

4. 拦截器

实现接口HandlerInterceptor。三个位置,pre 方法执行之前,post 方法执行后,after 请求处理完成页面渲染之后。拦截器逻辑写好,配置拦截请求,并把配置放入容器,实现 MvcConfigurer 的 addInterceptors,并指定拦截规则

原理

1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及得到handler的所有拦截器】 2、先顺序执行所有拦截器的 preHandle方法 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle 2、如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的 afterCompletion; 3、如果任何一个拦截器返回false。直接跳出不执行目标方法 4、所有拦截器都返回True。执行目标方法 5、倒序执行所有拦截器的postHandle方法。 6、前面的步骤有任何异常都会直接倒序触发 afterCompletion 7、页面成功渲染完成以后,也会倒序触发 afterCompletion

4.1 拦截器源码分析

在 doDispitcher 中找 handler 时,还会找到 interceptor 的 List

//执行拦截器的 preHandler,为 true 就继续执行目标方法,不然就 return 出去了
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
	return;
}
// Actually invoke the handler.
//真正执行目标方法。用得到的适配器,传入 handler 和 请求响应
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

// 执行拦截器的 post
mappedHandler.applyPostHandle(processedRequest, response, mv);

执行目标方法过程中有任何异常都会执行 after

//执行目标方法过程中有任何异常都会执行 after    
catch (Exception ex) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err));

遍历 interceptorList 执行 preHandler,就来到了写的拦截器方法中

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //顺序执行拦截器
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        if (!interceptor.preHandle(request, response, this.handler)) {
            //如果为 false,倒序执行已经执行的拦截器的 after
            triggerAfterCompletion(request, response, null);
            return false;
        }
        //如果拦截器执行为 true,执行下一个拦截器
        this.interceptorIndex = i;
    }
    return true;
}

handler 执行完成,倒序执行 post

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
			throws Exception {
    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

doDispatcher 最后 finally 也会倒序执行 after

finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
        // Instead of postHandle and afterCompletion
        if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
        }
    }
    else {
        // Clean up any resources used by a multipart request.
        if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
        }
    }
}

5. 异常处理

5.1 错误处理

  • 默认情况下, /error处理所有错误的映射,有错就跳这里
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
  • 要对其进行自定义,添加 View 解析为 error
  • 要完全替换默认行为,可以实现 ErrorController 替换其内容。并注册该类型的Bean定义,或添加 ErrorAttributes类型的组件以使用现有机制但替
  • 静态资源或模板引擎的error/目录下 4xx,5xx页面会被自动解析

5.2 异常处理自动装配原理

  • ErrorMvcAutoConfiguration 自动配置异常处理规则
    • 容器中的组件:类型:DefaultErrorAttributes -> beanId:errorAttributes
      • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
      • DefaultErrorAttributes: 定义错误页面中可以包含哪些数据。
    • 容器中的组件:类型:BasicErrorController –> id:basicErrorController(json+白页 适配响应)
      • 处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
      • 容器中有组件 View->id是error;(响应默认错误页)
      • 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
    • 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
      • 如果发生错误,会以HTTP的状态码作为视图页地址(viewName),找到真正的页面
      • error/404、5xx.html

5.3 异常处理流程

  1. 执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException
  2. 进入视图解析流程(页面渲染?) processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  3. mv = processHandlerException处理handler发生的异常,处理完成返回ModelAndView;
    1. 遍历所有的 handlerExceptionResolvers,看谁能处理当前异常,如果返回不为空就结束 。都实现了接口:HandlerExceptionResolver 处理器异常解析器。接口只有一个方法就,resolveException会返回一个 ModelAndView。
    2. 系统默认的异常解析器。第二个中的三个默认不支持异常处理,第一个只会保存异常信息image-20220114074654348
      1. 第一个 DefaultErrorAttributes 先处理异常。把异常信息保存到request域,并且返回 null
      2. 第二个找 @ExceptionHandler 的方法(能找到是因为启动是分析每个注解有提前缓存)
      3. 第三个标注解 @ResponseStatus 会直接响应一个异常状态码
      4. 默认没有任何人能处理异常,所以异常一定会被抛出
        1. 如果processDispatchResult中没有任何人能处理,最终底层就会发送/error请求。会被底层的BasicErrorController处理
        2. 解析错误视图,遍历所有的 ErrorViewResolver 看谁能解析。
        3. 默认的 DefaultErrorViewResolver 。先获取精确的 HttpStatus 的值,作用是把响应状态码作为错误页的地址,error/500.html
        4. 模板引擎最终响应这个页面 error/500.html

**捕获异常后处理开始:**异常会保存在 dispatchException

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

dispatchException就是产生的异常

5.4 定制错误处理逻辑

  • 自定义错误页:error/404.html error/5xx.html,有精确的错误状态码页面就匹配精确,没有就找 4xx.html,如果都没有就触发白页

  • @ControllerAdvice + @ExceptionHandler处理全局异常,底层是 ExceptionHandlerExceptionResolver 支持的。一般开发也用这种方式

    @Slf4j
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler({ArithmeticException.class, NullPointerException.class}) //处理指定类型异常
        public String handlerArithException(Exception ex) {
            log.error("exception: ", ex);
            //返回视图
            return "/";
        }
    }
    
  • @ResponseStatus+自定义异常 底层是 ResponseStatusExceptionResolver,把responsestatus注解的信息拿到,直接底层调用 response.sendError(statusCode, resolvedReason);就到了 tomcat 发送的 /error 请求。

    //自定义后,可以在方法中抛出此异常
    @ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "custom test")
    public class CustomRuntimeException extends RuntimeException {
        public CustomRuntimeException(String msg) {
            super(msg);
        }
    }
    
  • Spring底层的异常,如参数类型转换异常,DefaultHandlerExceptionResolver 处理框架底层的异常。

    • response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
  • 自定义实现 HandlerExceptionResolver 处理异常,可以调高 order 作为默认的全局异常处理规则

    @Order(value = Ordered.HIGHEST_PRECEDENCE)
    @Component
    public class CustomHandlerExceptionResolver implements HandlerExceptionResolver {
        @Override
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    
            try {
                response.sendError(512, "ERRRRROR");
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            return null;
        }
    }
    
  • ErrorViewResolver 实现自定义处理异常;

    • response.sendError 。error请求就会转给controller
    • 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
    • basicErrorController 要去的页面地址是 ErrorViewResolver

6. Web原生组件注入

  • 三大组件分别是 Servlet、Filter、Listener

6.1 注解注入

自定义之后需要启动类加扫描@ServletComponentScan指定原生三大组件所在位置

6.1.1 servlet

不会经过 Spring 的拦截器

@WebServlet(urlPatterns = "/test/servlet") //能处理的请求路径
public class TestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        resp.getWriter().write("666");
    }
}

6.1.2 Filter

@WebFilter(urlPatterns = {"/css/*", "/image/*"}) //拦截的路径,* 是 servlet 写法。** 是 Spring 写法
public class TestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("test filter working");
        //直接放行
        filterChain.doFilter(servletRequest, servletResponse);
    }

    /**
     * 初始化会调用此方法
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("test filter init");
    }

    @Override
    public void destroy() {
        log.info("test filter died");
    }
}

6.1.3 Listener

@WebListener
public class TestServletContentListener implements ServletContextListener {

    /**
     * 监听项目初始化
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("Test Listener: project init");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("Test Listener: project died");
    }
}

6.2 RegisterBean 注入

不用自定义类的注解和启动器注解,建一个 JavaBean config 类,使用 Bean 注入自定义的 Servlet

@Configuration
public class RegisterConfig {

    @Bean
    public ServletRegistrationBean testServletBean() {
        TestServlet servlet = new TestServlet();
        return new ServletRegistrationBean(servlet, "/bean/servlet");
    }

    @Bean
    public FilterRegistrationBean testFilterBean() {
        TestFilter filter = new TestFilter();
        // 拦截自定义 servlet 的路径
        // return new FilterRegistrationBean(filter, testServletBean());  
        
        // 自定义路径
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setUrlPatterns(Arrays.asList("/bean/filter", "/bean/filter2"));
        return bean;
    }
    
    @Bean
    public ServletListenerRegistrationBean testListenerBean() {
        TestServletContentListener listener = new TestServletContentListener();
        return new ServletListenerRegistrationBean(listener);
        
    }
}

6.3 源码分析

不会被拦截器拦截是因为当前 Spring 有两个 servlet,自定义和 DispatcherServlet,原生的 servlet 是 tomcat 处理

  1. SpringBoot 提供的 DispatcherServlet 是在自动配置类中被注入的DispatcherServletAutoConfiguration,配置属性都放在spring.mvc中。
  2. dispatcherServletRegistration把 DispatcherServlet 放入 servletContext
  3. 默认映射路径是/。多个 servlet 处理同一个请求,精确优先原则

6.4 嵌入式 Servlet 容器

默认不用配置 web 服务器,SpringBoot 直接用了 tomcat、Jetty、Undertow。

用了一个特殊的容器创建服务器,ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器

原理

  • SpringBoot应用启动发现当前是Web应用。pom 中 web 场景包导入了 tomcat
  • web 应用会创建一个 web 版的 ioc 容器 ServletWebServerApplicationContext
  • ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory (Servlet 的 web 服务器工厂—> Servlet 的web服务器)
  • SpringBoot 底层默认有很多的 WebServer 工厂TomcatServletWebServerFactory , JettyServletWebServerFactory , or UndertowServletWebServerFactory
  • 底层直接会有一个自动配置类ServletWebServerFactoryAutoConfiguration
  • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
  • ServletWebServerFactoryConfiguration 根据动态判断系统中到底导入了那个 Web 服务器的包。(默认是 web-starter 导入 tomcat 包),容器中就有 TomcatServletWebServerFactory
  • TomcatServletWebServerFactory 创建出 Tomcat 服务器并启动;TomcatWebServer 的构造器中就调用了初始化方法initialize—this.tomcat.start();
  • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)

切换 web 服务器只需要修改包依赖,在 pom.xml 中 web-starter 排除 tomcat,加入其他服务器的 starter 就行了

6.5 定制Servlet容器

  • 实现WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>
    • 把配置文件的值和 ServletWebServerFactory 进 行 绑 定
  • 修改配置文件 server.xxx
  • 直接自定义 ConfigurableServletWebServerFactory

xxxxxCustomizer定制化器,可以改变xxxx的默认规则

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
	server.setPort(9000);
}

7. 定制化原理

一般套路:场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties – 绑定配置文件项

导入包,修改配置属性就能自定义一些东西

定制化的常见方式

  • 修改配置文件

  • xxxxxCustomizer

  • 编写自定义的配置类 xxxConfiguration+ @Bean替换、增加容器中默认组件,视图解析器

  • Web应用:编写一个配置类实现 WebMvcConfigurer 即可定制化web功能,或者@Bean给容器中再扩展一些组件

  • @EnableWebMvc + WebMvcConfigurer —— @Bean 可以全面接管SpringMVC,所有规则全部自己重新配置,实现定制和扩展功能 EnableWebMvc 注解加上后,Spring 提供的视图解析器、静态资源等等自动配置会全部失效

    原理: 1、WebMvcAutoConfiguration 默认的 SpringMVC 的自动配置功能类会自动帮我们配置好一堆东西。静态资源、欢迎页….. 2、一旦使用@EnableWebMvc、会 @Import(DelegatingWebMvcConfiguration.class) 3、DelegatingWebMvcConfiguration 的作用是只保证 SpringMVC 最基本的使用

    • 把所有系统中的 WebMvcConfigurer 拿过来。所有功能的定制都是这些 WebMvcConfigurer 合起来一起生效
    • 自动配置了一些非常底层的组件。RequestMappingHandlerMapping、这些组件依赖的组件都是从容器中获取
    • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport

    4、WebMvcAutoConfiguration 里面的配置要能生效 必须@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效。

8. SpringBoot 原理

原理四部分:Spring 原理、MVC 原理、自动配置、SpringBoot 原理

8.1 SpringBoot 启动过程

进入启动类的 run 方法:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

8.2 创建 SpringApplication,保存关键组件信息在里面

  • 构造器中保存了一些信息

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        //判断应用类型
    	this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
        //初始化器
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //找监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //在栈中找主程序
        this.mainApplicationClass = deduceMainApplicationClass();
    }
    
  • 使用 ClassUtils 判断应用类型

    // 判断 web 应用类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    	static WebApplicationType deduceFromClasspath() {
    		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
    				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
    			return WebApplicationType.REACTIVE;
    		}
    		for (String className : SERVLET_INDICATOR_CLASSES) {
    			if (!ClassUtils.isPresent(className, null)) {
    				return WebApplicationType.NONE;
    			}
    		}
    		return WebApplicationType.SERVLET;
    	}
    
  • bootstrappers获取初始启动引导器(List<Bootstrapper>),是去spring.factories文件中找的 org.springframework.boot.Bootstrapper

  • 找 初始化器ApplicationContextInitializer,也是在 spring.factories 里找 ApplicationContextInitializer

    • List<ApplicationContextInitializer> initializersimage-20220118164753948
  • 找 ApplicationListener ;应用监听器。去 spring.factories 找 ApplicationListener

    • List<ApplicationListener> listeners

8.3 run 运行

	public ConfigurableApplicationContext run(String... args) {
        // 记时
		long startTime = System.nanoTime();
        // 创建上下文
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
        // 让当前应用进入headless模式,自力更生模式,不依赖别人,没有 I/O 时的系统配置
		configureHeadlessProperty();
        // 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】
		SpringApplicationRunListeners listeners = getRunListeners(args);
        // 遍历监听器执行 starting 方法
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
            // 保存命令行参数
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备环境
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
            // 创建 IOC 容器
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
            // 准备ApplicationContext IOC容器的基本信息 
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            // 刷新容器,经典初始化容器
			refreshContext(context);
            // 刷新后
			afterRefresh(context, applicationArguments);
            // 停止记时
			Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
			}
			listeners.started(context, timeTakenToStartup);
            // 调用所有 runners
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) { }
		return context;
	}

创建上下文,监听器初始化

  • 记录应用的启动时间

    long startTime = System.nanoTime();
    
  • 创建引导上下文(Context环境) createBootstrapContext()

    • 获取到创建时,所有之前的 bootstrappers 对每个执行 intitialize() 来完成对引导启动器上下文环境设置

      private DefaultBootstrapContext createBootstrapContext() {
          DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
          this.bootstrapRegistryInitializers.forEach((initializer) -> initializer.initialize(bootstrapContext));
          return bootstrapContext;
      }
      
  • 让当前应用进入headless模式。java.awt.headless

  • 获取所有 RunListener(运行监听器)【为了方便所有 Listener 进行事件感知】

    • getSpringFactoriesInstances 去 spring.factories 找 SpringApplicationRunListener.
  • 遍历 SpringApplicationRunListener 调用 starting 方法,监听器能监听项目运行

    • 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。

保存参数,准备环境

  • 保存命令行参数,ApplicationArguments

  • 准备环境 prepareEnvironment(),创建并配置。都是一些系统属性、yaml Profiles、servlet 配置参数等等

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    		// Create and configure the environment
        	// 环境不为空就直接返回,不然看 web 类型是 serlvet 还是 响应式
    		ConfigurableEnvironment environment = getOrCreateEnvironment();
    		// 配置环境信息对象
        	configureEnvironment(environment, applicationArguments.getSourceArgs());
        	// 绑定配置信息
    		ConfigurationPropertySources.attach(environment);
        	// 监听器执行 environmentPrepared
    		listeners.environmentPrepared(bootstrapContext, environment);
    		DefaultPropertiesPropertySource.moveToEnd(environment);
        	// 绑定一些属性
    		bindToSpringApplication(environment);
    		if (!this.isCustomEnvironment) {
    			environment = convertEnvironment(environment);
    		}
    		ConfigurationPropertySources.attach(environment);
    		return environment;
    	}
    

    image-20220118163203793

    • 返回或者创建基础环境信息对象。StandardServletEnvironment
    • 配置环境信息对象。
      • 读取所有的配置源的配置属性值。
    • 绑定环境信息
    • 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成

IOC 容器创建,准备容器所有基本信息

  • 创建 IOC 容器createApplicationContext()

    • 先根据项目类型(Servlet)创建容器

      protected ConfigurableApplicationContext createApplicationContext() {
      	return this.applicationContextFactory.create(this.webApplicationType);
      }
      
    • 当前会创建 AnnotationConfigServletWebServerApplicationContext

  • 准备ApplicationContext IOC容器的基本信息 prepareContext()

    private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments, Banner printedBanner) {
        // 保存环境
        context.setEnvironment(environment);
        // 后置处理
        postProcessApplicationContext(context);
        // 遍历初始化器,对 IOC 扩展功能
        applyInitializers(context);
        // 监听器调用
        listeners.contextPrepared(context);
        bootstrapContext.close(context);
    
        // Add boot specific singleton beans
        // 容器 Bean 单实例
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 注册组件
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
            ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
            if (beanFactory instanceof DefaultListableBeanFactory) {
                ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
            }
        }
        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }
    
    • 保存环境信息
    • IOC容器的后置处理流程。
    • 应用初始化器 applyInitializers;
      • 遍历所有的 ApplicationContextInitializer。调用 initialize.。来对ioc容器进行初始化扩展功能

        protected void applyInitializers(ConfigurableApplicationContext context) {
            for (ApplicationContextInitializer initializer : getInitializers()) {
                Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
                Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
                initializer.initialize(context);
            }
        }
        
      • 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared

    • 所有的监听器调用 contextLoaded。通知所有的监听器 contextLoaded;

刷新 IOC 容器,初始化容器,会初始化所有单实例组件

  • 刷新IOC容器。refreshContext
    • 创建容器中的所有组件(Spring 注解、Spring 源码内容)
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            // 初始化所有单实例组件
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
            contextRefresh.end();
        }
    }
}

容器刷新后

  • 容器刷新完成后工作? afterRefresh
  • 所有监听器调用 listeners.started(context); 通知所有的监听器 started
  • 调用所有runners,callRunners()
    • 获取容器中的 ApplicationRunner
    • 获取容器中的 CommandLineRunner
    • 合并所有runner并且按照@Order进行排序
    • 遍历所有的runner。调用 run 方法
  • 如果以上有异常,
    • 调用Listener 的 failed
  • 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
  • running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed

8.4 自定义事件监听组件

核心监听组件 :ApplicationContextInitializer、ApplicationListener、SpringApplicationRunListener、ApplicationRunner、CommandLineRunner。前三个都是在 spring.factories 找到的,后两个在容器中

可以自定义Listener,监听 Spring 整个启动过程。还需要 META-INF 中加入 spring.factories。

注意前两个包在自动配置类的,后一个在 SpringBoot 的

org.springframework.context.ApplicationContextInitializer=\
  com.cancel.listener.MyApplicationContextInitializer

org.springframework.context.ApplicationListener=\
  com.cancel.listener.MyApplicationListener

org.springframework.boot.SpringApplicationRunListener=\
  com.cancel.listener.MySpringApplicationRunListener

runner 想在启动时刻做一些事情就需要定义上面的组件,使用场景可以在官方示例 demo,测试一些东西,在应用一启动做一些一次性的事情,应用一启动就会调用这个

Contents