了解配置

自动配置分析

  • 我们以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    // 表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
    @Configuration

    // 启动指定类的ConfigurationProperties功能;
    // 进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
    // 并把HttpProperties加入到ioc容器中
    @EnableConfigurationProperties({HttpProperties.class})

    // Spring底层@Conditional注解
    // 根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
    // 这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
    @ConditionalOnWebApplication(
    type = Type.SERVLET
    )

    // 判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器;
    @ConditionalOnClass({CharacterEncodingFilter.class})

    // 判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
    // 如果不存在,判断也是成立的
    // 即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
    @ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
    )

    public class HttpEncodingAutoConfiguration {
    // 他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    // 只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
    this.properties = properties.getEncoding();
    }

    // 给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
    CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
    filter.setEncoding(this.properties.getCharset().name());
    filter.setForceRequestEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.REQUEST));
    filter.setForceResponseEncoding(this.properties.shouldForce(org.springframework.boot.autoconfigure.http.HttpProperties.Encoding.Type.RESPONSE));
    return filter;
    }
    //。。。。。。。
    }
  • 一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!

    • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;

    • 这些组件的属性是从对应的properties类中获取的@EnableConfigurationProperties({XXXXProperties.class})

    • 这些类里面的每一个属性又是和配置文件绑定的;

  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;

  • 配置文件能配置什么就可以参照某个功能对应的这个属性类,这就是自动装配的原理!

总结

  1. SpringBoot启动会加载大量的自动配置类

  2. 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

  3. 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

  4. 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

    • xxxxAutoConfigurartion:自动配置类, 给容器中添加组件。
    • xxxxProperties:封装配置文件中相关属性。

@Conditional注解

  • 了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@Conditional派生注解

  • 作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @ConditionalOnClass : classpath中存在该类时起效
    @ConditionalOnMissingClass : classpath中不存在该类时起效
    @ConditionalOnBean : DI容器中存在该类型Bean时起效
    @ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
    @ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
    @ConditionalOnExpression : SpEL表达式结果为true
    @ConditionalOnProperty : 参数设置或者值一致时起效
    @ConditionalOnResource : 指定的文件存在时起效
    @ConditionalOnJndi : 指定的JNDI存在时起效
    @ConditionalOnJava : 指定的Java版本存在时起效
    @ConditionalOnWebApplication : Web应用环境下起效
    @ConditionalOnNotWebApplication : 非Web应用环境下起效
  • @Conditional注解主要用在以下位置:

    • 可以放在注标识有@Component(包含@Configuration)的类上
    • 作为一个meta-annotation,组成自定义注解
    • 方法级别可以放在标识由@Bean的方法上
    • 如果一个@Configuration的类标记了@Conditional,所有标识了@Bean的方法和@Import注解导入的相关类将遵从这些条件。
    1
    2
    3
    4
    5
    6
    //@Conditional定义
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE, ElementType.METHOD)
    public @interface Conditional{
    Class<? extends Condition>[] value();
    }

示例

  • 实现Condtin接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    package top.fulsun.helloworld.condition;

    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;

    /**
    * @className: LinuxCondition
    * @Description: 实现了Condtin接口,重载的方法返回一个基于操作系统类型的布尔值。
    * @version: v1.8.0
    * @author: Fulsun
    * @date: 2020/3/27 22:14
    */
    public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
    return conditionContext.getEnvironment().getProperty("os.name").contains("Linux");
    }
    }

    //===================================

    package top.fulsun.helloworld.condition;

    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;

    /**
    * @className: WindowsCondition
    * @Description: 实现了Condtin接口,重载的方法返回一个基于操作系统类型的布尔值。
    * @version: v1.8.0
    * @author: Fulsun
    * @date: 2020/3/27 22:15
    */
    public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
    return conditionContext.getEnvironment().getProperty("os.name").contains("Windows");
    }
    }
  • 定义两个bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package top.fulsun.helloworld.condition;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Conditional;
    import org.springframework.context.annotation.Configuration;

    /**
    * @className: MyConfiguration
    * @Description: 定义两个bean, 一个符合条件另外一个不符合条件:
    * @version: v1.8.0
    * @author: Fulsun
    * @date: 2020/3/27 22:18
    */
    @Configuration
    public class MyConfiguration {
    @Bean(name = "emailerService")
    @Conditional(WindowsCondition.class)
    public void windowsEmailerService() {
    System.out.println("send windows email");
    }

    @Bean(name = "emailerService")
    @Conditional(LinuxCondition.class)
    public void linuxEmailerService() {
    System.out.println("send linux email");
    }
    }
  • 测试:启动主启动类,观察控制台的输出即可

查看生效配置类

  • 那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。我们怎么知道哪些自动配置类生效?

  • 我们可以通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

    1
    2
    #开启springboot的调试类
    debug=true
  • Positive matches:(自动配置类启用的:正匹配)

  • Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)

  • Unconditional classes: (没有条件的类)

MVC 自动配置原理

  • SpringBoot对我们的SpringMVC还做了哪些配置,我们如何扩展,如何定制?

  • 官网阅读地址:官方文档

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    Spring MVC Auto-configuration
    // Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
    Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

    // 自动配置在Spring默认设置的基础上添加了以下功能:
    The auto-configuration adds the following features on top of Spring’s defaults:

    // 包含视图解析器
    Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

    // 支持静态资源文件夹的路径,以及webjars
    Support for serving static resources, including support for WebJars

    // 自动注册了Converter:
    // 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
    // Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
    Automatic registration of Converter, GenericConverter, and Formatter beans.

    // HttpMessageConverters
    // SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
    Support for HttpMessageConverters (covered later in this document).

    // 定义错误代码生成规则的
    Automatic registration of MessageCodesResolver (covered later in this document).

    // 首页定制
    Static index.html support.

    // 图标定制
    Custom Favicon support (covered later in this document).

    // 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
    Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

    /*
    如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),
    则可以添加自己的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。
    如果希望提供 RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandler,ExceptionResolver的自定义实例,
    则可以声明WebMVCregistrationAdapter实例来提供此类组件。
    */
    If you want to keep Spring Boot MVC features and you want to add additional MVC configuration
    (interceptors, formatters, view controllers, and other features), you can add your own
    @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide
    custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or
    ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.

    // 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
    If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc.

spring-boot-starter-web

  • 首先打开Maven的本地仓库,找到对应Spring Boot的文件夹,打开spring-boot-start-web文件夹
    就可以看到一个名为 spring-boot-starter-web-2.0.7.RELEASE.pom 的文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    <?xml version="1.0" encoding="UTF-8"?>
    <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starters</artifactId>
    <version>2.0.7.RELEASE</version>
    </parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.0.7.RELEASE</version>
    <name>Spring Boot Web Starter</name>
    <description>Starter for building web, including RESTful, applications using Spring
    MVC. Uses Tomcat as the default embedded container</description>
    <url>https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-web</url>
    <organization>
    <name>Pivotal Software, Inc.</name>
    <url>https://spring.io</url>
    </organization>
    <licenses>
    <license>
    <name>Apache License, Version 2.0</name>
    <url>http://www.apache.org/licenses/LICENSE-2.0</url>
    </license>
    </licenses>
    <developers>
    <developer>
    <name>Pivotal</name>
    <email>info@pivotal.io</email>
    <organization>Pivotal Software, Inc.</organization>
    <organizationUrl>http://www.spring.io</organizationUrl>
    </developer>
    </developers>
    <scm>
    <connection>scm:git:git://github.com/spring-projects/spring-boot.git/spring-boot-starters/spring-boot-starter-web</connection>
    <developerConnection>scm:git:ssh://git@github.com/spring-projects/spring-boot.git/spring-boot-starters/spring-boot-starter-web</developerConnection>
    <url>http://github.com/spring-projects/spring-boot/spring-boot-starters/spring-boot-starter-web</url>
    </scm>
    <issueManagement>
    <system>Github</system>
    <url>https://github.com/spring-projects/spring-boot/issues</url>
    </issueManagement>
    <dependencies>
    <!-- Spring Boot 的依赖-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.0.7.RELEASE</version>
    <scope>compile</scope>
    </dependency>
    <!-- json 的依赖-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.0.7.RELEASE</version>
    <scope>compile</scope>
    </dependency>
    <!-- tomcat 的依赖-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.0.7.RELEASE</version>
    <scope>compile</scope>
    </dependency>
    <!-- hibernate 的依赖-->
    <dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.13.Final</version>
    <scope>compile</scope>
    </dependency>
    <!-- Spring Web MVC 依赖-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.0.11.RELEASE</version>
    <scope>compile</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.0.11.RELEASE</version>
    <scope>compile</scope>
    </dependency>
    </dependencies>
    </project>
  1. 通过代码中的中文注释可以看出,当加入spring-boot-starter-web后,它会通过Maven将对应的资源加载到我们的工程中,这样便能够形成依赖。但是这样还不足以运行 Spring MVC项目,要运行它还需要对Spring MVC进行配置,让它能够生产Spring MVC所需的对象,才能启用 Spring MVC,所以还需要进一步探讨。
  2. 为了探讨Spring MVC在Spring Boot自动配置的问题,首先在本地下载的Maven仓库的目录 spring-boot-autoconfigure 中找到 spring-boot-autoconfigure-2.0.7.RELEASE-sources.jar 的包。它是一个 源码包,把它解压缩出来,打开它目录下的子目录org\ppringframework\boot\autoconfigure\web\servlet后,我们就可以看到许多配置类,这里可以看到存在很多的类,其中DispatcherServletAutoConfiguration就是一个对 DispatcherServlet 进行自动配置的类。

DispatcherServletAutoConfiguration

  • 找到DispatcherServletAutoConfiguration

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    //配置文件
    @Configuration
    //配置条件满足类 DefaultDispatcherServletCondition 的验证
    @Conditional(DefaultDispatcherServletCondition.class)
    //如果存在ServletRegistration类则进行配置
    @ConditionalOnClass(ServletRegistration.class)
    //如果存在对应的属性配置(Spring MVC 的是spring.mvc.*)则启用配置
    @EnableConfigurationProperties(WebMvcProperties.class)
    protected static class DispatcherServletConfiguration {

    private final WebMvcProperties webMvcProperties;

    public DispatcherServletConfiguration(WebMvcProperties webMvcProperties) {
    this.webMvcProperties = webMvcProperties;
    }

    @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
    DispatcherServlet dispatcherServlet = new DispatcherServlet();
    dispatcherServlet.setDispatchOptionsRequest(
    this.webMvcProperties.isDispatchOptionsRequest());
    dispatcherServlet.setDispatchTraceRequest(
    this.webMvcProperties.isDispatchTraceRequest());
    dispatcherServlet.setThrowExceptionIfNoHandlerFound(
    this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
    return dispatcherServlet;
    }

    @Bean
    //如果存在类定义则配置
    @ConditionalOnBean(MultipartResolver.class)
    // 判断如果不存在 bean 名称为 DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME,则配置 Bean
    @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
    // Detect if the user has created a MultipartResolver but named it incorrectly
    return resolver;
    }

    }
  • 注意上述代码中@EnableConfigurationProperties(WebMvcProperties.class)的注释,这些中文注释是我加入的,为的是更好地说明 Spring Boot 的自动配置功能。

  • 通过上面的代码, 可以看到 Spring Boot 内部己经自动 为我们做了很多关于 DispatcherServlet 的配置,其中的 @EnableConfigurationProperties 还能够在读取配置内容的情况下自动生成 Spring MVC 所需的类,到这里应该明白为什么几乎在没有任何配置下就能用 Spring Boot 启 动 Spring MVC 项目

  • 这些都是 Spring Boot 通过Maven 依赖找到对应的jar包和嵌入的服务器,然后使用默认自动配置类来创建默认的开发环境。但是有时候,我们需要对这些默认的环境进行修改以适应个性化的要求,这些在Spring Boot 中也是非常简单的,正如@EnableConfigurationProperties 注解那样,它允许读入配置文件的内容来自定义自动初始化所需内。

视图解析器

  • ContentNegotiatingViewResolver :视图解析器

  • SpringBoot自动配置了ViewResolver,就是我们之前学习的SpringMVC的视图解析器;

    • 即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
    1
    2
    3
    4
    5
    // 实现了 ViewResolver 接口的类,我们称为视图解析器
    public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
    implements ViewResolver, Ordered, InitializingBean {
    //....
    }
    1
    2
    3
    4
    5
    6
    public interface ViewResolver {
    @Nullable
    View resolveViewName(String viewName, Locale locale) throws Exception;

    }

  • 查看这里的源码:我们找到 WebMvcAutoConfiguration , 然后搜索ContentNegotiatingViewResolver。找到如下方法!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Bean
    @ConditionalOnBean(ViewResolver.class)
    @ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
    public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
    ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
    resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
    // ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
    resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return resolver;
    }
  • 我们可以点进ContentNegotiatingViewResolver类看看!找到对应的解析视图的代码;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Nullable // 注解说明:@Nullable 即参数可为null
    public View resolveViewName(String viewName, Locale locale) throws Exception {
    RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
    List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
    if (requestedMediaTypes != null) {
    // 获取候选的视图对象
    List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
    // 选择一个最适合的视图对象,然后把这个对象返回
    View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
    if (bestView != null) {
    return bestView;
    }
    }
    // .....
    }
    • 我们继续点进去看,他是怎么获得候选的视图的呢?

    • getCandidateViews中看到他是把所有的视图解析器拿来,进行for循环,挨个解析!

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
      throws Exception {

      List<View> candidateViews = new ArrayList<>();
      if (this.viewResolvers != null) {
      Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
      //遍历所有的视图解析器
      for (ViewResolver viewResolver : this.viewResolvers) {
      //封装到对象
      View view = viewResolver.resolveViewName(viewName, locale);
      if (view != null) {
      //添加到候选的视图
      candidateViews.add(view);
      }
      for (MediaType requestedMediaType : requestedMediaTypes) {
      List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
      for (String extension : extensions) {
      String viewNameWithExtension = viewName + '.' + extension;
      view = viewResolver.resolveViewName(viewNameWithExtension, locale);
      if (view != null) {
      candidateViews.add(view);
      }
      }
      }
      }
      }
      if (!CollectionUtils.isEmpty(this.defaultViews)) {
      candidateViews.addAll(this.defaultViews);
      }
      //返回候选的视图
      return candidateViews;
      }
    • 所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的 ,再研究下他的组合逻辑,看到有个属性viewResolvers,看看它是在哪里进行赋值的!

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      @Override
      protected void initServletContext(ServletContext servletContext) {
      // 这里它是从beanFactory工具中获取容器中的所有视图解析器
      // ViewRescolver.class 把所有的视图解析器来组合的
      Collection<ViewResolver> matchingBeans =
      BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
      if (this.viewResolvers == null) {
      this.viewResolvers = new ArrayList<>(matchingBeans.size());
      for (ViewResolver viewResolver : matchingBeans) {
      if (this != viewResolver) {
      this.viewResolvers.add(viewResolver);
      }
      }
      }
      else {
      for (int i = 0; i < this.viewResolvers.size(); i++) {
      ViewResolver vr = this.viewResolvers.get(i);
      if (matchingBeans.contains(vr)) {
      continue;
      }
      String name = vr.getClass().getName() + i;
      obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
      }

      }
      AnnotationAwareOrderComparator.sort(this.viewResolvers);
      this.cnmFactoryBean.setServletContext(servletContext);
      }

定义视图解析器

  • 既然它是在容器中去找视图解析器,我们是否可以猜想,我们就可以去实现一个视图解析器了呢?

  • 我们可以自己给容器中去添加一个视图解析器;这个类就会帮我们自动的将它组合进来;

  1. 我们在我们的主程序中去写一个视图解析器来试试;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    package top.fulsun.config;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.View;
    import org.springframework.web.servlet.ViewResolver;

    import java.util.Locale;

    /**
    * @className: MyViewResolver
    * @Description: 实现WebMvcConfigurer接口
    * @version: v1.8.0
    * @author: Fulsun
    * @date: 2020/4/5 1:58 下午
    */
    @Configuration //配置类
    public class MyWebMvcConfig implements WebMvcConfigurer {
    @Bean //放到bean中
    public ViewResolver myViewResolver(){
    return new MyViewResolver();
    }

    //我们写一个静态内部类,视图解析器就需要实现ViewResolver接口
    private static class MyViewResolver implements ViewResolver{
    @Override
    public View resolveViewName(String s, Locale locale) throws Exception {
    return null;
    }
    }
    }

  2. 怎么看我们自己写的视图解析器有没有起作用呢?

    • 我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下,因为所有的请求都会走到这个方法中

  3. 我们启动我们的项目,然后随便访问一个页面,看一下Debug信息;

    • 找到this,找到视图解析器,我们看到我们自己定义的就在这里了;

    • 所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了!

转换器和格式化器

  • WebMvcAutoConfiguration中找到格式化转换器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Bean
    @Override
    public FormattingConversionService mvcConversionService() {
    // 拿到配置文件中的格式化规则
    WebConversionService conversionService =
    new WebConversionService(this.mvcProperties.getDateFormat());
    addFormatters(conversionService);
    return conversionService;
    }
  • 点击去:

    1
    2
    3
    4
    5
    6
    7
    8
    public String getDateFormat() {
    return this.dateFormat;
    }

    /**
    * Date format to use. For instance, `dd/MM/yyyy`. 默认的
    */
    private String dateFormat;
  • 我们可以根据自动配置类prefix,在yaml中进行自动配置dateFormat

    • 如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:

      1
      2
      3
      spring:
      mvc:
      date-format: dd-MM-yyyy

修改SpringBoot的默认配置

  • 这么多的自动配置,原理都是一样的.
    • SpringBoot的底层,大量用到了这些设计细节思想,所以,没事需要多阅读源码!得出结论;
    • SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
    • 如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!

扩展使用SpringMVC

  • 编写一个@Configuration注解类,并且类型要为WebMvcConfigurer,还不能标注@EnableWebMvc注解;我们去自己写一个;我们新建一个包叫config,写一个类MyMvcConfig;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //应为类型要求为WebMvcConfigurer,所以我们实现其接口
    //可以使用自定义类扩展MVC的功能
    @Configuration
    public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    // 浏览器发送/2 , 就会跳转到test页面;
    registry.addViewController("/t2").setViewName("test");
    }
    }
  • 我们去浏览器访问一下:

    • 确实也跳转过来了!所以说,我们要扩展SpringMVC,官方就推荐我们这么去使用,既保SpringBoot留所有的自动配置,也能用我们扩展的配置!

扩展MVC的原理

  1. WebMvcAutoConfiguration 是 SpringMVC的自动配置类,里面有一个类WebMvcAutoConfigurationAdapter

  2. 这个类上有一个注解,在做其他自动配置时会导入:@Import(EnableWebMvcConfiguration.class)

  3. 我们点进EnableWebMvcConfiguration这个类看一下,它继承了一个类:DelegatingWebMvcConfiguration

  4. 这个父类中有这样一段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
    // 从容器中获取所有的webmvcConfigurer
    @Autowired(required = false)
    public void setConfigurers(List<WebMvcConfigurer> configurers) {
    if (!CollectionUtils.isEmpty(configurers)) {
    this.configurers.addWebMvcConfigurers(configurers);
    }
    }
    }
  5. 我们可以在这个类中去寻找addViewControllers当做参考,发现它调用了一个

    1
    2
    3
    4
    @Override
    protected void addViewControllers(ViewControllerRegistry registry) {
    this.configurers.addViewControllers(registry);
    }
  6. 我们点进去看一下

    1
    2
    3
    4
    5
    6
    7
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    for (WebMvcConfigurer delegate : this.delegates) {
    // 将所有的WebMvcConfigurer相关配置来一起调用!包括我们自己配置的和Spring给我们配置的
    delegate.addViewControllers(registry);
    }
    }
  • 所以得出结论:所有的WebMvcConfiguration都会被作用,不止Spring自己的配置类,我们自己的配置类当然也会被调用;

全面接管SpringMVC

  • 官方文档:If you want to take complete control of Spring MVCyou can add your own @Configuration annotated with @EnableWebMvc.

  • 全面接管即:SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己去配置!

  • 只需在我们的配置类中要加一个@EnableWebMvc

  • 我们看下如果我们全面接管了SpringMVC了,我们之前SpringBoot给我们配置的静态资源映射一定会无效,我们可以去测试一下;

  • 不加注解之前,访问首页:

  • 给配置类加上注解:@EnableWebMvc

  • 我们发现所有的SpringMVC自动配置都失效了!回归到了最初的样子;我们开发中,不推荐使用全面接管SpringMVC

失效原因

  • 为什么加了一个注解,自动配置就失效了!我们看下源码:
  1. 这里发现它是导入了一个类,我们可以继续进去

    1
    2
    3
    @Import(DelegatingWebMvcConfiguration.class)
    public @interface EnableWebMvc {
    }
  2. 它继承了一个父类 WebMvcConfigurationSupport

    1
    2
    3
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    // ......
    }
  3. 我们来回顾一下Webmvc自动配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @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 {

    }
  4. 总结一句话:@EnableWebMvcWebMvcConfigurationSupport组件导入进来了;所以自动配置类就不生效了.

掌握并理解原理,即可以不变应万变!就可以不用背配置文件啦。