SpringBoot 启动配置原理
启动配置原理
1 2 3 4 5
| public class SpringBootApplication { public static void main(String[] args) { SpringApplication.run(SpringBootApplication.class, args); } }
|
点进启动类的run
方法可以看到,在源码中先创建SpringApplication
对象,然后在执行它的run
方法。
1 2 3 4 5 6 7
| 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); }
|
创建SpringApplication对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
|
运行run方法
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
| public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); }
try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
|
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
| private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); applyInitializers(context); listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } 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()); } Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); }
|
1 2 3 4 5 6 7 8 9
| 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); } }
|
1 2 3 4
| private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] {SpringApplication.class, String[].class}; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
|
配置在META-INF/spring.factories
: ApplicationContextInitializer
、SpringApplicationRunListener
只需要放在ioc容器中 :ApplicationRunner
、CommandLineRunner
事件监听机制
ApplicationContextInitializer
1 2 3 4 5 6
| public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("ApplicationContextInitializer...initialize..."+applicationContext); } }
|
SpringApplicationRunListener
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
| public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
public HelloSpringApplicationRunListener(SpringApplication application, String[] args){
}
@Override public void starting() { System.out.println("SpringApplicationRunListener...starting..."); }
@Override public void environmentPrepared(ConfigurableEnvironment environment) { Object o = environment.getSystemProperties().get("os.name"); System.out.println("SpringApplicationRunListener...environmentPrepared.."+o); }
@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..."); } }
|
配置(META-INF/spring.factories
)\
表示换行,,
表示分隔
1 2 3
| org.springframework.context.ApplicationContextInitializer=\ com.springboot.listener.HelloApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=\ com.springboot.listener.HelloSpringApplicationRunListener
|
ApplicationRunner
1 2 3 4 5 6 7
| @Component public class HelloApplicationRunner implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("ApplicationRunner...run...."); } }
|
CommandLineRunner
1 2 3 4 5 6 7
| @Component public class HelloCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("CommandLineRunner...run..."+ Arrays.asList(args)); } }
|
1 2 3 4 5 6 7 8 9
| 输出顺序: SpringApplicationRunListener...starting... SpringApplicationRunListener...environmentPrepared... ApplicationContextInitializer...initialize... SpringApplicationRunListener...contextPrepared... SpringApplicationRunListener...contextLoaded... ApplicationRunner...run.... CommandLineRunner...run... SpringApplicationRunListener...started...
|
自定义starter
SpringBoot starter机制
SpringBoot
中的starter
是一种非常重要的机制,能够抛弃以前繁杂的配置,将其统一集成进starter
,应用者只需要在maven
中引入starter
依赖,SpringBoot
就能自动扫描到要加载的信息并启动相应的默认配置。
starter
让我们摆脱了各种依赖库的处理,需要配置各种信息的困扰。SpringBoot
会自动通过classpath
路径下的类发现需要的Bean
,并注册进IOC容器。
SpringBoot
提供了针对日常企业应用研发各种场景的spring-boot-starter
依赖模块。所有这些依赖模块都遵循着约定成俗的默认配置,并允许我们调整这些配置,即遵循“约定大于配置”的理念。
为什么要自定义starter
在我们的日常开发工作中,经常会有一些独立于业务之外的配置模块,我们经常将其放到一个特定的包下,然后如果另一个工程需要复用这块功能的时候,需要将代码硬拷贝到另一个工程,重新集成一遍,麻烦至极。
如果我们将这些可独立于业务代码之外的功配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoo
t为我们完成自动装配。
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency>
|
spring-boot-starter
中集成了spring
全家桶的许多依赖支持,如下:
命名规范
启动器(starter
),启动器模块是一个空 JAR
文件,仅提供辅助性依赖管理,这些依赖可能用于自动装配或者其他类库。
一个完整的Spring Boot Starter可能包含以下组件:
- autoconfigure模块:包含自动配置的代码
- starter模块:提供对autoconfigure模块的依赖,以及一些其它的依赖
如果你不需要区分这两个概念的话,也可以将自动配置代码模块与依赖管理模块合并成一个模块。
官方命名空间:
– 前缀:“spring-boot-starter-
”
– 模式:spring-boot-starter-模块名
– 举例:spring-boot-starter-web、spring-boot-starter-actuator、spring-boot-starter-jdbc
推荐自定义命名空间:
– 后缀:“-spring-boot-starter
”
– 模式:模块-spring-boot-starter
– 举例:mybatis-spring-boot-starter
如何编写
1 2 3 4 5 6 7
| @Configuration @ConditionalOnXXX @AutoConfigureAfter @Bean
@ConfigurationPropertie @EnableConfigurationProperties
|
1 2 3 4 5 6
| 自动配置类要能加载 将需要启动就加载的自动配置类,配置在META-INF/spring.factories org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
|
新建项目
新建一个工程名midkuro-spring-boot-starter
的maven
空项目,只做pom.xml
的依赖引入。
新建一个工程名midkuro-spring-boot-starter-autoconfigurer
的Spring-boot
项目
并修改其midkuro-spring-boot-starter-autoconfigurer
项目的pom.xml
文件,引入spring-boot-starter
依赖
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
| <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.midkuro.com</groupId> <artifactId>midkuro-spring-boot-starter-autoconfigurer</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> </parent>
<properties> <maven-jar-plugin.version>2.6</maven-jar-plugin.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies> </project>
|
然后修改midkuro-spring-boot-starter
的pom.xml
,引入相关依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <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.midkuro.com</groupId> <artifactId>midkuro-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging>
<name>midkuro-spring-boot-starter</name>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>
<dependencies> <dependency> <groupId>cn.midkuro.com</groupId> <artifactId>midkuro-spring-boot-starter-autoconfigurer</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> </project>
|
定义配置类
在midkuro-spring-boot-starter-autoconfigurer
工程中增加配置类
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
| package cn.midkuro.com.autoconfigurer;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "mtex.hello") public class HelloProperties { 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; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package cn.midkuro.com.autoconfigurer;
public class HelloService {
private HelloProperties properties;
public HelloProperties getProperties() { return properties; }
public void setProperties(HelloProperties properties) { this.properties = properties; }
public String sayHello(String name) { return properties.getPrefix() + "-" + name + "-" + properties.getSuffix(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package cn.midkuro.com.autoconfigurer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration @ConditionalOnWebApplication @EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
@Autowired private HelloProperties properties;
@Bean public HelloService helloService() { HelloService helloService = new HelloService(); helloService.setProperties(properties); return helloService; } }
|
配置spring.factories
想要xxxAutoConfiguration
配置默认生效,需要在META-INF/spring.factories
增加启动类相关配置
1 2 3
| org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ cn.midkuro.com.autoconfigurer.HelloServiceAutoConfiguration
|
\
斜杠符号表示换行,多个类以,
逗号分开,具体可以参考spring-boot-starter
的配置文件:
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
| org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
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,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ ...
|
测试
新建工程名midkuro-spring-boot-starter-test
的spring-boot
项目,引入打包好的自定义starter
依赖:
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
| <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.mastercom</groupId> <artifactId>mtex-spring-boot-starter-test</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> <relativePath/> </parent>
<properties> <maven-jar-plugin.version>2.6</maven-jar-plugin.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties>
<dependencies>
<dependency> <groupId>cn.midkuro.com</groupId> <artifactId>midkuro-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
</project>
|
新建测试Controller
:
1 2 3 4 5 6 7 8 9 10
| @RestController public class HelloController { @Autowired private HelloService helloService;
@GetMapping("/sayHello") public void sayHello(String name) { helloService.sayHello(name); } }
|
添加配置到application.properties
中:
1 2
| mtex.hello.prefix=hello mtex.hello.suffix=world
|
运行启动类SpringBootStarterTestApplication
:
1 2 3 4 5 6 7 8 9 10 11
| package cn.midkuro.com;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication public class SpringBootStarterTestApplication { public static void main(String[] args) { SpringApplication.run(SpringBootStarterTestApplication.class, args); } }
|