六、Spring Boot 框架之启动配置原理

799 阅读12分钟

文章中涉及到的代码仓库地址:demo-springBoot

SpringBoot的启动流程:

① 通过程序主入口进入;

② 创建SpringApplication对象;

③ 调用SpringApplication对象中的run方法启动SpringBoot;

(一)创建SpringApplication对象

可以在主配置类上打断点,然后单步调试查看SpringBoot启动的运行过程

① 在主配置类上打断点

image-20201106112624684

② 通过Debug的方式启动应用

image-20201106112450710

③ 使用调试工具进入到创建SpringApplication对象的方法内部

image-20201106112954605

获取到的 initializers 的值,此处获取到的值会存储起来,后面的run方法会调用。

image-20201106102535111

获取到的 listenters 的值,此处获取到的值会存储起来,后面的run方法会调用。

image-20201106103218857

SpringApplication类的构成

public class SpringApplication {

   	......

    // run方法通过调用此方法创建SpringApplication对象
    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }

    // 真正创建Application对象的构造方法
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        // 加载保存主配置类
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 判断当前应用是否是一个web应用
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 在类路径下查找META-INF/spring.factories配置所有的ApplicationContextInitializer,然后保存
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        // 在类路径下查找META-INF/spring.factories配置所有的ApplicationListener
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 在多个配置类中找到包含main方法的主程序类
        this.mainApplicationClass = deduceMainApplicationClass();
    }

	......

}

SpringApplication创建过程:

① 判断当前的应用类型;

② 加载并保存所有的ApplicationContextInitializer(META-INF/spring.factoties);

③ 加载并保存所有的ApplicationLisenter(META-INF/spring.factories);

④ 从多个配置类中得到包含main方法的主程序类;

(二)run方法的运行机制

作用:运行Spring 应用,创建并刷新IoC容器

SpringApplication类中 run 方法的构成

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 1)调用getRunListeners方法获取SpringApplicationRunListeners
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 回调所有的SpringApplicationRunListener对象的starting方法
    listeners.starting();
    try {
        // 将参数进行封装
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 2)调用prepareEnvironment方法准备IoC容器运行所需的环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 在控制台打印banner图标
        Banner printedBanner = printBanner(environment);
        // 3)调用createApplicationContext方法创建IoC容器,并决定IoC容器的类型
        context = createApplicationContext();
        // 异常报告,出现异常时打印的报告
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                                                         new Class[] { ConfigurableApplicationContext.class }, context);
        // 4)调用prepareContext方法准备上下文环境,将environment(IoC环境配置信息)保存到IoC中
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 5)调用refreshContext方法刷新容器,即初始化IoC容器。如果是web应用的话还会创建嵌入式的Servlet容器
        refreshContext(context);
         // 调用afterRefresh方法,这是个空方法,可能为SpringBoot后期扩展使用
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 回调所有的SpringApplicationRunListener对象的started方法
        listeners.started(context);
        // 调用callRunners方法进行事件回调
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 发生异常时调用SpringApplicationRunListener对象的failed方法
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 回调所有SpringApplicationRunListener对象的running方法,IoC容器创建完成
        listeners.running(context);
    }
    catch (Throwable ex) {
        // 发生异常时调用SpringApplicationRunListener对象的failed方法
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    // 至此整个SpringBoot应用启动完成并返回IoC容器
    return context;
}

// 通过我们书写的主配置类调用的run方法,实际上返回的是下面的run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}

// 进入到此方法中,创建对象然后调用真正的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
}

1)SpringApplication类中的 getRunListeners 方法

// 在类路径META-INF/spring.factories中获取监听器SpringApplicationRunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    // 获取监听器SpringApplicationRunListeners
    return new SpringApplicationRunListeners(logger,
                                             getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

2)SpringApplication类中的 prepareEnvironment 方法,用于准备IoC容器运行所需的环境

// 准备当前IoC容器运行所需的环境
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
                                                   ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    // 回调所有SpringApplicationRunListener对象的environmentPrepared方法;此时环境准备完成
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    // 如果容器是web容器则进行转化
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                                                                                               deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    // 返回创建好的环境
    return environment;
}

3)SpringApplication类中的 createApplicationContext 方法,用于创建IoC容器

// 此方法用于创建IoC容器
protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            // 根据项目的类型创建不同的IoC容器
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
        }
    }
    // 利用反射创建IoC容器
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

4)SpringApplication类中的 prepareContext 方法用于准备上下文环境

// 此方法用于准备上下文环境,将前准备好的信息添加到IoC容器中
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
                            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    // 调用applyInitializers方法获取所有ApplicationContextInitializer对象并调用initialize方法
    applyInitializers(context);
    // 回调所有的SpringApplicationRunListener对象的contextPrepared()方法
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    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]));
    // prepareContext运行完成以后回调所有的SpringApplicationRunListener的contextLoaded方法
    listeners.contextLoaded(context);
}

4-1)SpringApplication类中的 applyInitializers 方法,获取所有 ApplicationContextInitializer 对象并调用 initialize 方法

// 此方法是回调之前保存的所有的ApplicationContextInitializer的initialize方法。
// ApplicationContextInitializer对象是SpringBoot在启动时获取到并保存的
@SuppressWarnings({ "rawtypes", "unchecked" })
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);
    }
}

5)SpringApplication类中 refreshContext 方法用于刷新容器,即初始化IoC容器,是创建、扫描、加载所有组建的地方

private void refreshContext(ConfigurableApplicationContext context) {
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
    // 调用刷新方法,刷新IoC容器
    refresh((ApplicationContext) context);
}

6)SpringApplication类中 callRunners 方法,从IoC容器中获取所有的 ApplicationRunnerCommandLineRunner 进行回调

private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List<Object> runners = new ArrayList<>();
    // 在IoC容器中获取ApplicationRunner
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    // 在IoC容器中获取CommandLineRunner
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    AnnotationAwareOrderComparator.sort(runners);
    for (Object runner : new LinkedHashSet<>(runners)) {
        // 先回调ApplicationRunner
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        // 再回调CommandLineRunner
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

SpringApplication类中run方法的运行过程:

① 回调所有SpringApplicationRunListener对象的 starting 方法;

② 将参数封装到 ApplicationArguments 对象中;

③ 准备IoC容器运行所需的环境,回调所有SpringApplicationRunListener对象的 environmentPrepare 方法;

④ 向控制台输出banner信息(SpringBoot的logo);

⑤ 创建IoC容器对象;

  • org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext (web环境容器)
  • org.springframework.context.annotation.AnnotationConfigApplicationContext(默认的环境容器)

⑥ 准备上下文环境;此时回调SpringApplicationRunListener对象的 contextPrepared 方法和 contextLoaded 方法

⑦ 刷新容器,即初始化IoC容器,是创建、扫描、加载所有组建的地方;

⑧ 回调所有的SpringApplicationRunListener对象的 started 方法;

⑨ 调用SpringApplication对象的 callRunner 方法;此时回调 ApplicationRunnerCommandLineRunnerrun 方法;

⑩ 回调所有SpringApplicationRunListener对象的 running 方法;

至此整个SpringBoot应用启动完成并返回IoC容器;

run方法的核心:

  • 各种监听机制的使用,我们需要在什么时候干预,在对应的时机调用回调即可
  • refreshContext 方法:容器的刷新,即扫描、创建、加载所有组建的地方。我们配置加载到IoC容器中的所有组件都是在此处加载的。

(三)事件监听机制

通过前面对SpringBoot应用启动和run方法运行过程的分析我们可以注意到,SpringBoot的启动和IoC容器的创建依赖于监听器的回调,在合适的时机调用相应的监听器即可达到干预SpringBoot应用启动的目的。

SpringBoot中几个重要的事件回调:

  • 配置在META-INF/spring.factories中
    • ApplicationContextInitializer
    • SpringApplicationRunListener
  • 配置在ioc容器中的
    • ApplicationRunner
    • CommandLineRunner

1. 配置在META-INF/spring.factories中的监听器

1.1 ApplicationContextInitializer接口:

作用:用于初始化Spring的回调接口

ApplicationContextInitializer接口的构成:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

    /**
	 * Initialize the given application context.
	 * @param applicationContext the application to configure
	 */
    void initialize(C applicationContext);

}

ApplicationContextInitializer接口继承关系图:

image-20201107112205204

我们可以实现此接口查看 ApplicationContextInitializer 监听器的回调时机

public class TestApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("ApplicationContextInitializer对象的initialize方法运行了......容器类型为:" + applicationContext);
    }
}

1.2 SpringApplicationRunListener接口

作用:SpringApplication对象 run 方法运行时监听器

SpringApplicationRunListener接口的构成:

public interface SpringApplicationRunListener {

    /**
	 * Called immediately when the run method has first started. Can be used for very
	 * early initialization.
	 */
    default void starting() {
    }

    /**
	 * Called once the environment has been prepared, but before the
	 * {@link ApplicationContext} has been created.
	 * @param environment the environment
	 */
    default void environmentPrepared(ConfigurableEnvironment environment) {
    }

    /**
	 * Called once the {@link ApplicationContext} has been created and prepared, but
	 * before sources have been loaded.
	 * @param context the application context
	 */
    default void contextPrepared(ConfigurableApplicationContext context) {
    }

    /**
	 * Called once the application context has been loaded but before it has been
	 * refreshed.
	 * @param context the application context
	 */
    default void contextLoaded(ConfigurableApplicationContext context) {
    }

    /**
	 * The context has been refreshed and the application has started but
	 * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
	 * ApplicationRunners} have not been called.
	 * @param context the application context.
	 * @since 2.0.0
	 */
    default void started(ConfigurableApplicationContext context) {
    }

    /**
	 * Called immediately before the run method finishes, when the application context has
	 * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
	 * {@link ApplicationRunner ApplicationRunners} have been called.
	 * @param context the application context.
	 * @since 2.0.0
	 */
    default void running(ConfigurableApplicationContext context) {
    }

    /**
	 * Called when a failure occurs when running the application.
	 * @param context the application context or {@code null} if a failure occurred before
	 * the context was created
	 * @param exception the failure
	 * @since 2.0.0
	 */
    default void failed(ConfigurableApplicationContext context, Throwable exception) {
    }

}

SpringApplicationRunListener接口的继承关系图:

image-20201107112708113

我们可以实现此接口查看 ApplicationContextInitializer 监听器的回调时机

public class TestSpringApplicationRunListener implements SpringApplicationRunListener {

    // 此构造器必须书写,否则会报错
    public TestSpringApplicationRunListener(SpringApplication springApplication, String[] args) {
    }

    @Override
    public void starting() {
        System.out.println("SpringApplicationRunListener类中的starting方法被调用......");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        Object osName = environment.getSystemProperties().get("os.name");
        System.out.println("SpringApplicationRunListener类中的environmentPrepared方法被调用......" + osName);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener类中的contextPrepared方法被调用......");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener类中的contextLoaded方法被调用......");
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener类中的started方法被调用......");
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        System.out.println("SpringApplicationRunListener类中的running方法被调用......");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("SpringApplicationRunListener类中的failed方法被调用......");
    }
}

我们需要在类路径下创建 META-INF/spring.factories 文件配置我们自定义的监听器

org.springframework.context.ApplicationContextInitializer=\
  cn.bruce.springboot06_theory.listener.TestApplicationContextInitializer

org.springframework.boot.SpringApplicationRunListener=\
  cn.bruce.springboot06_theory.listener.TestSpringApplicationRunListener

2. 配置在ioc容器中的监听器

2.1 ApplicationRunner

作用:

ApplicationRunner接口的构成:

@FunctionalInterface
public interface ApplicationRunner {

    /**
	 * Callback used to run the bean.
	 * @param args incoming application arguments
	 * @throws Exception on error
	 */
    void run(ApplicationArguments args) throws Exception;

}

ApplicationRunner接口继承关系图:

image-20201107113045951

我们可以实现 ApplicationRunner 接口查看他的调用时机

// 注入到容器中
@Component
public class TestApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner对象的run方法执行了......");
    }
}

2.2 CommandLineRunner

作用:可以用于在Spring应用IoC容器

CommandLineRunner接口的构成:

@FunctionalInterface
public interface CommandLineRunner {

    /**
	 * Callback used to run the bean.
	 * @param args incoming main method arguments
	 * @throws Exception on error
	 */
    void run(String... args) throws Exception;

}

实现 CommandLineRunner 接口查看调用时机

// 添加到容器中
@Component
public class TestCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner对象的run方法执行了......参数:"+ Arrays.asList(args));
    }
}

3. SpringBoot应用启动后监听器回调时机

实现以上接口后控制台的输出内容

SpringApplicationRunListener类中的starting方法被调用......
SpringApplicationRunListener类中的environmentPrepared方法被调用......Windows 10

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.5.RELEASE)

ApplicationContextInitializer对象的initialize方法运行了......容器类型为:org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2ef3eef9, started on Thu Jan 01 08:00:00 CST 1970
SpringApplicationRunListener类中的contextPrepared方法被调用......
2020-11-07 11:43:47.376  INFO 18792 --- [           main] c.b.s.Springboot06TheoryApplication      : Starting Springboot06TheoryApplication on YOGA-S740 with PID 18792 (E:\workspace\workspace_idea03\demo-springBoot\springboot06_theory\target\classes started by Bruce in E:\workspace\workspace_idea03\demo-springBoot)
2020-11-07 11:43:47.389  INFO 18792 --- [           main] c.b.s.Springboot06TheoryApplication      : No active profile set, falling back to default profiles: default
SpringApplicationRunListener类中的contextLoaded方法被调用......
2020-11-07 11:43:48.165  INFO 18792 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-11-07 11:43:48.171  INFO 18792 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-11-07 11:43:48.171  INFO 18792 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.39]
2020-11-07 11:43:48.230  INFO 18792 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-11-07 11:43:48.231  INFO 18792 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 785 ms
2020-11-07 11:43:48.345  INFO 18792 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-11-07 11:43:48.470  INFO 18792 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-11-07 11:43:48.477  INFO 18792 --- [           main] c.b.s.Springboot06TheoryApplication      : Started Springboot06TheoryApplication in 1.378 seconds (JVM running for 2.064)
SpringApplicationRunListener类中的started方法被调用......
ApplicationRunner对象的run方法执行了......
CommandLineRunner对象的run方法执行了......参数:[]
SpringApplicationRunListener类中的running方法被调用......

通过控制台输出的信息,我们能看到SpringBoot应用启动的过程和回调监听器的时机:

① 创建 SpringApplication 对象;

1) 判断当前的应用类型;

2) 加载并保存所有的ApplicationContextInitializer(META-INF/spring.factoties);

3) 加载并保存所有的ApplicationLisenter(META-INF/spring.factories);

4) 从多个配置类中得到包含main方法的主程序类;

② 运行 SpringApplication 对象中的 run 方法

1) 回调所有SpringApplicationRunListener对象的 starting 方法;

2) 将参数封装到 ApplicationArguments 对象中;

3) 准备IoC容器运行所需的环境,回调所有SpringApplicationRunListener对象的 environmentPrepare 方法;

4) 向控制台输出banner信息(SpringBoot的logo);

5) 创建IoC容器对象;

  • org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext (web环境容器)
  • org.springframework.context.annotation.AnnotationConfigApplicationContext(默认的环境容器)

6) 准备上下文环境,此时回调SpringApplicationRunListener对象的 contextPrepared 方法和 contextLoaded 方法

7) 刷新容器,即初始化IoC容器,是创建、扫描、加载所有组建的地方;

8) 回调所有的SpringApplicationRunListener对象的 started 方法;

9)调用SpringApplication对象的 callRunner 方法;此时回调 ApplicationRunnerCommandLineRunnerrun 方法;

10) 回调所有SpringApplicationRunListener对象的 running 方法;

至此整个SpringBoot应用启动完成并返回配置好的IoC容器;

(四)自定义starters

starter书写的基本原则:

① 确定应用场景所需的依赖;

② 编写自动配置,配置相关的场景;

③ 配置模式,引入Starters导入自动配置;

1. Starter编写的模式

image-20201107160318013

① 启动器模块是空的Jar文件,仅提供辅助性的依赖管理,这些依赖可能用于自动装配或其他类库;

② 专门书写一个自动配置模块 xxxAutoConfiguration

③ 启动器依赖自动配置;使用时仅需引入启动器(Starter)即可自动配置;

starter命名规范:

  • 官方的命名方式:前缀-模块名
    • 前缀:spring-boot-starter-
    • 模式:spring-boot-starter-模块名
    • 举例:spring-boot-starter-webspring-boot-starter-jdbc
  • 自定义命名方式:模块名-后缀
    • 后缀:-spring-boot-starter
    • 模式:模块-spring-boot-starter
    • 举例:mybatis-spring-boot-starter

2. 书写自动配置:

编写自动配置类

@configuration  // 指定此类是一个配置类
@ConditationOnxxx // 在指定的条件成立的情况下自动配置类生效
@AutoConfigureAfter // 指定自动配置类的顺序
@Bean //给容器中添加组件

@EnableConfigurationProperties  // 引入相关的xxxProperties配置类,使其生效并加入到容器中

编写配置属性类

@ConfigurationProperties // 书写相关的xxxProperties配置类绑定相关的配置

自动配置类的加载

将需要启动时加载的自动配置类,配置在META-INF/spring.factories中。如:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\

3. 自定义Starters并测试使用

3.1 书写启动器模块

新建模块 demo-spring-boot-starter,启动模块仅作为依赖导入。

书写 pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.bruce.starter</groupId>
    <artifactId>demo-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- 引入自动配置模块 -->
        <dependency>
            <groupId>cn.bruce.starter</groupId>
            <artifactId>demo-spring-boot-starter-autoconfigurer</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

3.2 书写自动配置模块

书写自动配置模块 demo-spring-boot-starter-configurer

① 修改 pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

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

	<groupId>cn.bruce.starter</groupId>
	<artifactId>demo-spring-boot-starter-autoconfigurer</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>demo-spring-boot-starter-configurer</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<!-- 引入spring-boot-starter;所有starter的基本配置 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
	</dependencies>

</project>

② 书写配置属性类

@ConfigurationProperties(prefix = "demo.test")
public class TestProperties {
    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

③ 书写服务类

public class TestService {
    TestProperties testProperties;

    public TestProperties getTestProperties() {
        return testProperties;
    }

    public void setTestProperties(TestProperties testProperties) {
        this.testProperties = testProperties;
    }

    public String testPrintString(String name) {
        return testProperties.getPrefix() + "-" + name + testProperties.getSuffix();
    }
}

④ 书写自动配置服务类

@Configuration
@ConditionalOnWebApplication // web应用生效
@EnableConfigurationProperties(TestProperties.class )
public class TestServiceAutoConfiguration {

    @Autowired
    TestProperties testProperties;

    @Bean
    public TestService testService() {
        TestService testService = new TestService();
        testService.setTestProperties(testProperties);
        return testService;
    }
}

⑤ 书写spring配置文件,配置自动导入

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  cn.bruce.starter.TestServiceAutoConfiguration

⑥ 通过Maven将启动器模块和自动配置模块打包成jar包

1)将 demo-spring-boot-starter-configurer 模块打包成jar包安装到仓库

image-20201110163856758

2)将 demo-spring-boot-starter 模块打包成jar包安装到仓库

image-20201110162943854

demo-spring-boot-starter 模块依赖 demo-spring-boot-starter-configurer 模块,因此需要先打包 demo-spring-boot-starter-configurer 模块

3.3 使用自定义starter

① 导入自定义的starter依赖

<!-- 引入自定义starter -->
<dependency>
    <groupId>cn.bruce.starter</groupId>
    <artifactId>demo-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

② 书写配置文件

demo.test.prefix=this is
demo.test.suffix=good

③ 书写controller调用

@RestController
public class TestController {

    @Autowired
    TestService testService;

    @GetMapping("/test")
    public String Test() {
        return testService.testPrintString("cat");
    }
}