关于程序员:SpringBoot自动装配原理

2次阅读

共计 20701 个字符,预计需要花费 52 分钟才能阅读完成。

引言

springboot 给咱们的开发带来了极大的便当,并通过启动器的形式不便咱们增加依赖而不必过多的关注配置,那么 springboot 是如何进行工作的?一起探索下。

个别咱们都会在 pom.xml 中继承 spring-boot-parent

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

或者在 <dependencyManagement> 标签里通过依赖 spring-boot-dependencies 来引入 springboot

 <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.16.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

咱们只有关注咱们须要哪些依赖,而不须要关注依赖的具体版本,因为下面两种形式(当然形式能够有其余的,只有正确引入 springboot 即可)都是通过 springboot 的父我的项目来进行版本的治理。比方咱们想开发一个 web 我的项目,就间接引入即可,而不须要标注版本:

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

主启动类

默认的主启动类

在 springboot 我的项目中都会有个启动类,而启动类都有一个 @SpringBootApplication 注解,咱们先探索下这个注解的作用。

//@SpringBootApplication 来标注一个主程序类
// 阐明这是一个 Spring Boot 利用
@SpringBootApplication
public class SpringbootApplication {public static void main(String[] args) {
     // 认为是启动了一个办法,没想到启动了一个服务
      SpringApplication.run(SpringbootApplication.class, args);
   }

}

@SpringBootApplication 注解实际上是 SpringBoot 提供的一个复合注解,咱们来看一看其源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {. . .}

看得很分明,其是一个合成体,但其中最重要的三个注解别离是:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

上面咱们一一来剖析这三个次要的注解:


@SpringBootConfiguration

这个注解比较简单,源码如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}

这阐明 @SpringBootConfiguration 也是来源于 @Configuration,二者性能都是将以后类标注为配置类,并将以后类里以 @Bean 注解标记的办法的实例注入到 srping 容器中,实例名即为办法名。

至于 @Configuration,我想在非 SpringBoot 时代大家应该不生疏吧,作用是配置 Spring 容器,也即 JavaConfig 模式的 Spring IoC 容器的配置类所应用。

@EnableAutoConfiguration(重点)

这个注解实现类 springboot 的 约定大于配置 。这个注解能够帮忙咱们 主动载入 应用程序所须要的所有 默认配置

源码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}

这个注解上有两个比拟非凡的注解 @AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage:主动配置包

源码如下:

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

@import:Spring 底层注解 @import,给容器中导入一个组件

AutoConfigurationPackages.Registrar.class 作用:将主启动类的所在包及包上面所有子包外面的所有组件扫描到 Spring 容器;

@Import({AutoConfigurationImportSelector.class}):给容器导入组件

AutoConfigurationImportSelector:主动配置导入选择器


@EnableAutoConfiguration 注解启用主动配置,其能够帮忙 SpringBoot 利用将所有符合条件的 @Configuration 配置都加载到以后 IoC 容器之中,能够简要用图形示意如下:

咱们对照源码,简略剖析一下这个流程:

  • @EnableAutoConfiguration 借助 AutoConfigurationImportSelector 的帮忙,而后者通过实现 selectImports() 办法来导出 Configuration

这里对 getAutoConfiguration 办法进行正文阐明:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
   // 查看主动拆卸开关
   if (!isEnabled(annotationMetadata)) {return EMPTY_ENTRY;}
   // 获取 EnableAutoConfiguration 中的参数,exclude()/excludeName()
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 获取须要主动拆卸的所有配置类,读取 META-INF/spring.factories
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // 去重,List 转 Set 再转 List
   configurations = removeDuplicates(configurations);
   // 从 EnableAutoConfiguration 的 exclude/excludeName 属性中获取排除项
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   // 查看须要排除的类是否在 configurations 中,不在报错
   checkExcludedClasses(configurations, exclusions);
   // 从 configurations 去除 exclusions
   configurations.removeAll(exclusions);
   // 对 configurations 进行过滤,剔除掉 @Conditional 条件不成立的配置类
   configurations = filter(configurations, autoConfigurationMetadata);
   // 把 AutoConfigurationImportEvent 绑定在所有 AutoConfigurationImportListener 子类实例上
   fireAutoConfigurationImportEvents(configurations, exclusions);
   // 返回 (configurations, exclusions) 组
   return new AutoConfigurationEntry(configurations, exclusions);
}
  • AutoConfigurationImportSelector 类的 selectImports() 办法外面通过调用 Spring Core 包里 SpringFactoriesLoader 类的 loadFactoryNames()办法(接上图)
  • 最终通过 SpringFactoriesLoader.loadFactoryNames() 读取了 ClassPath 上面的 META-INF/spring.factories 文件来获取所有导出类。

  • 这个文件在这个地位

而 spring.factories 文件里对于 EnableAutoConfiguration 的配置其实就是一个键值对构造,样子大略长上面这样:

说了这么多,如果从略微宏观一点的角度 概括总结 上述这一过程那就是:

从 ClassPath 下扫描所有的 META-INF/spring.factories 配置文件,并将 spring.factories 文件中的 EnableAutoConfiguration 对应的配置项通过反射机制实例化为对应标注了 @Configuration 的模式的 IoC 容器配置类,而后注入 IoC 容器

@ComponentScan

@ComponentScan 对应于 XML 配置模式中的 context:component-scan,用于将一些标注了特定注解的 bean 定义批量采集注册到 Spring 的 IoC 容器之中,这些特定的注解大抵包含:

  • @Controller
  • @Component
  • @Service
  • @Repository

等等

对于该注解,还能够通过 basePackages 属性来更细粒度的管制该注解的主动扫描范畴,比方:

@ComponentScan(basePackages = {"com.njit.controller","cn.njit.entity"})

疑难点

1.@AutoConfigurationPackage 和 @ComponentScan 区别(这里的能够了解须要验证下)

@AutoConfigurationPackage 默认 的状况下就是将:主配置类 (@SpringBootApplication) 的所在包及其子包里边的组件扫描到 Spring 容器中。比如说,你用了 Spring Data JPA,可能会在实体类上写 @Entity 注解。这个 @Entity 注解由 @AutoConfigurationPackage 扫描并加载,而咱们平时开发用的 @Controller/@Service/@Component/@Repository 这些注解是由 ComponentScan 来扫描并加载的。

  • 简略了解:这二者 扫描的对象是不一样 的。
  • 能够了解,前者是用来扫描 springboot 的主动拆卸,后者次要关怀咱们本人的代码中的 @service、@controller 等这些咱们本人申明的须要被主动注入的 bean
  • 文档中的话:
  • it will be used when scanning for code @Entity classes. It is generally recommended that you place EnableAutoConfiguration (if you're not using @SpringBootApplication) in a root package so that all sub-packages and classes can be searched.

2.spring.factories 中所有的配置类都会被加载?

SpringBoot 所有主动配置类都是在启动的时候进行扫描并加载,通过 spring.factories 能够找到主动配置类的门路,然而不是所有存在于 spring,factories 中的配置都进行加载,而是通过 @ConditionalOnClass 注解进行判断条件是否成立(只有导入相应的 stater,条件就能成立),如果条件成立则加载配置类,否则不加载该配置类。

主启动办法(main 办法)

咱们只有运行主启动类中的 main 办法,整个服务就被开启了. 这里咱们次要关注两个:SpringApplication以及它的 run 办法

SpringApplication的实例化

@SpringBootApplication
public class MySpringBootApplication {public static void main(String[] args) {SpringApplication.run(MySpringBootApplication.class, args);
    }
}
  • 首先咱们进入 run 办法
// 调用动态类,参数对应的就是 MySpringBootApplication.class 以及 main 办法中的 args
public static ConfigurableApplicationContext run(Class<?> primarySource,String... args) {return run(new Class<?>[] {primarySource}, args);
}
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {return (new SpringApplication(sources)).run(args);
}

它实际上会结构一个 SpringApplication 的实例,并把咱们的启动类MySpringBootApplication.class 作为参数传进去,而后运行它的 run 办法

  • 而后咱们进入 SpringApplication 结构器
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 把 MySpringBootApplication.class 设置为属性存储起来
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 设置利用类型是 Standard 还是 Web
    this.webApplicationType = deduceWebApplicationType();
    // 设置初始化器(Initializer), 最初会调用这些初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置监听器(Listener)
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();}

先将 HelloWorldMainApplication.class 存储在 this.primarySources 属性中

  • 设置利用类型 - 进入 deduceWebApplicationType()办法
private WebApplicationType deduceWebApplicationType() {if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {return WebApplicationType.REACTIVE;}
    for (String className : WEB_ENVIRONMENT_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}
    }
    return WebApplicationType.SERVLET;
}

// 相干常量
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.servlet.DispatcherServlet";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };

这里次要是通过类加载器判断 REACTIVE 相干的 Class 是否存在,如果不存在,则 web 环境即为 SERVLET 类型。这里设置好 web 环境类型,在前面会依据类型初始化对应环境。因为在 pom 中引入的 web 的启动器,它又依赖了spring-webmvc,spring-webmvc 中存在 DispatcherServlet 这个类,也就是咱们以前 SpringMvc 的外围 Servlet,通过类加载能加载 DispatcherServlet 这个类,那么咱们的利用类型天然就是 WebApplicationType.SERVLET。

  • 设置初始化器(Initializer)

// 设置初始化器(Initializer), 最初会调用这些初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

咱们先来看看getSpringFactoriesInstances(ApplicationContextInitializer.class)

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {return getSpringFactoriesInstances(type, new Class<?>[] {});
}

// 这里的入参 type 就是 ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // 应用 Set 保留 names 来防止反复元素
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 依据 names 来进行实例化
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    // 对实例进行排序
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

这外面首先会依据 入参 type 读取所有的 names(是一个 String 汇合),而后依据这个汇合来实现对应的实例化操作,这里和后面的 EnableAutoConfiguration 前面的是同一个办法(然而入参不同,这里只是读取取配置文件中 Key 为:org.springframework.context.ApplicationContextInitializer 的 value)。

// 入参就是 ApplicationContextInitializer.class
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {String factoryClassName = factoryClass.getName();

  try {
      // 从类门路的 META-INF/spring.factories 中加载所有默认的主动配置类
      Enumeration<URL> urls = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
      ArrayList result = new ArrayList();

      while(urls.hasMoreElements()) {URL url = (URL)urls.nextElement();
          Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
          // 获取 ApplicationContextInitializer.class 的所有值
          String factoryClassNames = properties.getProperty(factoryClassName);
          result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
      }

      return result;
  } catch (IOException var8) {throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
  }
}

接着就进入 createSpringFactoriesInstances 办法中, 开始上面的实例化操作

// parameterTypes: 上一步失去的 names 汇合
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
        Set<String> names) {List<T> instances = new ArrayList<T>(names.size());
    for (String name : names) {
        try {Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            // 确认被加载类是 ApplicationContextInitializer 的子类
            Assert.isAssignable(type, instanceClass);
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            // 反射实例化对象
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            // 退出 List 汇合中
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate" + type + ":" + name, ex);
        }
    }
    return instances;
}

确认被加载的类的确是 org.springframework.context.ApplicationContextInitializer 的子类,而后就是失去结构器进行初始化,最初放入到实例列表中.

因而,所谓的初始化器就是 org.springframework.context.ApplicationContextInitializer 的实现类,这个接口是这样定义的:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {void initialize(C applicationContext);

}

它在 Spring 上下文被刷新之前进行初始化的操作。典型地比方在 Web 利用中,注册 Property Sources 或者是激活 Profiles。Property Sources 比拟好了解,就是配置文件。Profiles 是 Spring 为了在不同环境下(如 DEV,TEST,PRODUCTION 等),加载不同的配置项而形象进去的一个实体。

  • 最初设置监听器(Listener)

上面开始设置监听器:

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 这里的入参 type 是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<String>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

能够发现,这个加载相应的类名,而后实现实例化的过程和下面在设置初始化器时一模一样,同样,还是以 spring-boot-autoconfigure 这个包中的 spring.factories 为例,看看相应的 Key-Value:

org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

这 10 个监听器会贯通 springBoot 整个生命周期。至此,对于 SpringApplication 实例的初始化过程就完结了。

总的来说做了这四件事:

1、推断利用的类型是一般的我的项目还是 Web 我的项目

2、查找并加载所有可用初始化器,设置到 initializers 属性中

3、找出所有的应用程序监听器,设置到 listeners 属性中

4、推断并设置 main 办法的定义类,找到运行的主类

SpringApplication.run 办法

run 办法一共有 8 个步骤,具体能够看上面参考链接。

spring boot 的配置文件

springboot 中反对两种格局的配置文件,而且配置文件的名字是固定的:application.propertiesapplication.yml,springboot 在主动拆卸的时候会给配置类注入默认的属性,如果咱们须要自定义属性,就间接在配置文件中定义就好,springboot 在主动拆卸的时候会优先应用咱们配置的属性,没有的话才会应用默认的属性。

给属性注入值

有时候咱们须要自定义一些组件,比方一些工具类或者咱们的 service 类中有须要自定义的属性,咱们能够通过上面这两种形式去注入属性。不过咱们更举荐应用后者的形式,更不便而且反对的能力更多。

通过 @value 注解

这种形式须要一个一个属性上写,如果比拟少的话能够,如果属性多的话就会比拟麻烦。这个 spring 的注解,能够通过表达式去获取,也能够设置如果没找到的默认值。

@Component
@Data
public class User {// 设置默认值(${value:defalutValue})@Value("${myuser.name:defalut}")
    private String name;

    @Value("${myuser.age:12}")
    private Integer age;

    // 反对简单的类型
    @Value("${myuser.age:12}")
    private List<String> perfect;

}

而后在配置文件中写配置(properties 和 yml 格局都能够)

myuser.name=hahaha
myuser.age=5
myuser.perfect="eat","sleep"

而后测试失常:

@SpringBootTest
@RunWith(SpringRunner.class)
public class AutoPropertiesTest {
    @Autowired
    private User user;

    @Test
    public void test(){System.out.println(user);
    }
}

--- 输入后果
 User(name=hahaha, age=5, perfect=["eat", "sleep"])

通过 @ConfigurationProperties 注解

只有在本人的类上增加一个注解,并指定前缀或者后缀等规定即可。(SpringBoot 主动拆卸中就是应用这种形式

@Component
@ConfigurationProperties(prefix = "myuser")
@Data
public class User {//    @Value("${myuser.name:defalut}")
    private String name;

//    @Value("${myuser.age:12}")
    private Integer age;

    // 反对简单的类型
//    @Value("${myuser.age:12}")
    private List<String> perfect;

}

这样测试,会发现后果和上次的是一样的:

User(name=hahaha, age=5, perfect=["eat", "sleep"])

主动配置总结

通过在配置文件中增加 debug=true 就能够察看到 spring boot 主动拆卸(哪些失效哪些没有)的后果:

# 匹配到的主动拆卸的配置
Positive matches:
-----------------
 CodecsAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.http.codec.CodecConfigurer'(OnClassCondition)

 CodecsAutoConfiguration.JacksonCodecConfiguration matched:
      - @ConditionalOnClass found required class 'com.fasterxml.jackson.databind.ObjectMapper' (OnClassCondition)
......
# 匹配不胜利的配置
Negative matches:
-----------------

   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.jms.ConnectionFactory' (OnClassCondition)

   AopAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'org.aspectj.lang.annotation.Aspect' (OnClassCondition) 
......

整体流程

spring boot 会默认给咱们加载大量的主动配置类,并通过 @ConditionalXXX 去判断是否注入组件,在注入是会引入 Properties 配置类(封装配置文件的相干属性,通过 ConfigurationProperties 注解),配置类中有默认值,咱们能够在配置文件中设置这些值去笼罩默认值,这样咱们只有配置咱们自定义的一些配置就好,其余的 springboot 就默认帮咱们注入好了,咱们只有应用即可。

扩大 springMVC

springboot 曾经帮咱们配置好了很多 springmvc 的配置,如果咱们还想增加一些本人的配置,springboot 曾经给咱们预留了计划。

在 spring boot1.0+,咱们能够应用 WebMvcConfigurerAdapter 来扩大 springMVC 的性能,其中自定义的拦截器并不会拦挡动态资源(js、css 等)。

在 Spring Boot2.0 版本中,WebMvcConfigurerAdapter 这个类被弃用了那么咱们扩大有两种办法,继承 WebMvcConfigurationSupport 或者 实现 WebMvcConfigurer 接口

注:如果配置类上应用@EnableWebMvc,则会接管默认的配置,如果是扩大则不须要加上这个注解

1. 继承 WebMvcConfigurationSupport(不举荐)

@Configuration
public class MyMvcConfig extends WebMvcConfigurationSupport {}

咱们能够看到外面有很多的办法能够重写:

继承 WebMvcConfigurationSupport 之后,能够应用这些 add…办法增加自定义的拦截器、试图解析器等等这些组件。如下:

@Configuration
public class MyMvcConfig extends WebMvcConfigurationSupport {

    @Override
    protected void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("login");
        registry.addViewController("/login.html").setViewName("login");
    }
}

但这里有个问题就是,当你继承了 WebMvcConfigurationSupport 并将之注册到容器中之后,Spring Boot 无关 MVC 的主动配置就不失效了。

能够看一下 Spring Boot 启动 WebMVC 主动配置的条件:

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

其中一个条件就是@ConditionalOnMissingBean(WebMvcConfigurationSupport.class),只有当容器中没有 WebMvcConfigurationSupport 这个类型的组件的时候,才会启动主动配置。

所以当咱们继承 WebMvcConfigurationSupport 之后,除非你本人对代码把控的相当的好,在继承类中重写了一系列无关 WebMVC 的配置,否则可能就会遇到动态资源拜访不到,返回数据不胜利这些一系列问题了。

2.实现 WebMvcConfigurer 接口(举荐)

咱们晓得,Spring Boot2.0 是基于 Java8 的,Java8 有个重大的扭转就是接口中能够有 default 办法,而 default 办法是不须要强制实现的。上述的 WebMvcConfigurerAdapter 类就是实现了 WebMvcConfigurer 这个接口,所以咱们不须要继承 WebMvcConfigurerAdapter 类,能够间接实现 WebMvcConfigurer 接口,用法与继承这个适配类是一样的:

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {


    @Override
    public void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/aiden").setViewName("success");
    }

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){WebMvcConfigurer wmc = new WebMvcConfigurer() {
           @Override
           public void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("login");
               registry.addViewController("/index.html").setViewName("login");
           }
       };
       return wmc;
    }

}

这两种办法都能够作为 WebMVC 的扩大,去自定义配置。区别就是继承 WebMvcConfigurationSupport 会使 Spring Boot 对于 WebMVC 的主动配置生效,须要本人去实现全副对于 WebMVC 的配置,而实现 WebMvcConfigurer 接口的话,Spring Boot 的主动配置不会生效,能够有抉择的实现对于 WebMVC 的配置。

自定义 starter

所谓的 Starter,其实就是一个一般的 Maven 我的项目,因而咱们自定义 Starter,须要首先创立一个一般的 Maven 我的项目,创立实现后,增加 Starter 的自动化配置类即可,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>2.1.8.RELEASE</version>
</dependency>

咱们首先创立一个 HelloProperties 类,用来承受 application.properties 中注入的值,如下:

@ConfigurationProperties(prefix = "javaboy")
public class HelloProperties {
    private static final String DEFAULT_NAME = "NJITZYD";
    private static final String DEFAULT_MSG = "自定义的默认值";
    private String name = DEFAULT_NAME;
    private String msg = DEFAULT_MSG;
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public String getMsg() {return msg;}
    public void setMsg(String msg) {this.msg = msg;}
}

这个配置类很好了解,将 application.properties 中配置的属性值间接注入到这个实例中,@ConfigurationProperties 类型平安的属性注入,行将 application.properties 文件中前缀为 javaboy 的属性注入到这个类对应的属性上,最初应用时候,application.properties 中的配置文件,大略如下:

javaboy.name=zhangsan
javaboy.msg=java

配置实现 HelloProperties 后,接下来咱们来定义一个 HelloService,而后定义一个简略的 say 办法,HelloService 的定义如下:

public class HelloService {
    private String msg;
    private String name;
    public String sayHello() {return name + "say" + msg + "!";}
    public String getMsg() {return msg;}
    public void setMsg(String msg) {this.msg = msg;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
}

接下来就是咱们的重轴戏,主动配置类的定义,用了很多他人定义的自定义类之后,咱们也来本人定义一个自定义类:

@Configuration
@EnableConfigurationProperties(HelloProperties.class)
@ConditionalOnClass(HelloService.class)
public class HelloServiceAutoConfiguration {
    @Autowired
    HelloProperties helloProperties;

    @Bean
    HelloService helloService() {HelloService helloService = new HelloService();
        helloService.setName(helloProperties.getName());
        helloService.setMsg(helloProperties.getMsg());
        return helloService;
    }
}

对于这一段主动配置,解释如下:

  • 首先 @Configuration 注解表明这是一个配置类。
  • @EnableConfigurationProperties 注解是使咱们之前配置的 @ConfigurationProperties 失效,让配置的属性胜利的进入 Bean 中。
  • @ConditionalOnClass 示意当我的项目以后 classpath 下存在 HelloService 时,前面的配置才失效。
  • 主动配置类中首先注入 HelloProperties,这个实例中含有咱们在 application.properties 中配置的相干数据。
  • 提供一个 HelloService 的实例,将 HelloProperties 中的值注入进去。

做完这一步之后,咱们的自动化配置类就算是实现了,接下来还须要一个 spring.factories 文件, 用来通知 springboot 吴主动注入咱们的 starter。

咱们首先在 Maven 我的项目的 resources 目录下创立一个名为 META-INF 的文件夹,而后在文件夹中创立一个名为 spring.factories 的文件,文件内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.njit.config.HelloServiceAutoConfiguration

到这里,代码曾经写完了,我么能够把自定义的 starte 打包到本地以及上传公司的私服中,这样就能够应用了。

测试

在另一个我的项目中援用咱们自定义的 starter:

<dependency>
    <groupId>com.njit</groupId>
    <artifactId>helllo-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

而后在配置文件中编写配置:

javaboy.name=zhangsan
javaboy.msg=java

配置实现后,不便起见,我这里间接在单元测试办法中注入 HelloSerivce 实例来应用,代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UsemystarterApplicationTests {

    @Autowired
    HelloService helloService;
    @Test
    public void contextLoads() {System.out.println(helloService.sayHello());
    }
}

能够看到控制台失常的应用到:

参考

springboot 主动注入以及 scan 和 autopage 之间的区别

主动拆卸原理

主动拆卸如何条件加载

主动加载在什么时候进行过滤的细节解答(很重要的参考)

springboot 中 main 办法启动流程(重要参考)

正文完
 0