版本
- spring-projects/spring-boot/1.5.x
- GitHub
SpringBoot 启动过程
源码
启动:
//经典的启动方式,加上 @SpringBootApplication 注解,使用 run() 方法启动
@SpringBootApplication
public class MediationEsApplicationTemp {
public static void main(String[] args) {
SpringApplication.run(MediationEsApplicationTemp.class, args);
}
}
SpringApplication.java的initialize()方法
// 初始化 创建并初始化 SpringApplication
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
// 这里是SpringApplication 对象的初始化
@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
// 这里是将MediationEsApplicationTemp.class放入了集合
// 后面会用到这个sources,来解析启动类
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
// 检查是否是web应用,后面初始化环境会用到这个参数
this.webEnvironment = deduceWebEnvironment();
// 这里是加载META-INF/spring.fatcories 里ApplicationContextInitializer 对应的class,通过beanfactory 初始化创建对象
// 加载方式参考`SpringApplication.getSpringFactoriesInstances()`
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 同上,这里是加载 ApplicationListener 对象的class
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 这里是获取你的栈信息,也就是你的启动调用链路,然后从中找到名字为"main"的方法,记录下main方法对应的className。
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringApplication.java的run()方法
public ConfigurableApplicationContext run(String... args) {
// 启动一个秒表计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 操作上下文容器(context)的工具,例如设置上下文 ID,设置父应用上下文,添加监听器,刷新容器,关闭,判断是否活跃等方法
ConfigurableApplicationContext context = null;
// 故障分析对象,拦截启动时异常,将异常转换成更加易读的信息
FailureAnalyzers analyzers = null;
// 使用headless模式,意思是服务器不需要显示设备,例如:显示器、键盘、鼠标,让程序在这种状态下工作。
configureHeadlessProperty();
// 获取META-INF/spirng.factories 里的 SpringApplicationRunListener 对象,然后包装成SpringApplicationRunListeners 返回
// 这里用到了SpringEvent 的机制
// 这里其实就是获取EventPublish,用于群发事件
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发送Spring启动事件ApplicationStartedEvent,监听该事件的方法会收到消息
listeners.starting();
try {
// 解析传入的args参数,放到ApplicationArguments 对象里
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 初始化环境,会根据前面的webEnvironment 来判断初始化的是web 环境对象,还是普通对象
// 这里的核心还是将我们传入的args 参数解析后放入environment 对象中
// 最后会发送一个ApplicationEnvironmentPreparedEvent 事件给监听他的对象
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 打印banner
Banner printedBanner = printBanner(environment);
// 创建上下文容器,会根据前面的webEnvironment 来判断初始化的是web 环境对象,还是普通对象
context = createApplicationContext();
// 从META-INF/spring.factories 加载FailureAnalyzer 相关对象,然后填充到FailureAnalyzers里
analyzers = new FailureAnalyzers(context);
// 对spring context做初始化操作,初始化他的各项参数,
// 对spring context做初始化操作时,会调用Spring.factories 中ApplicationContextInitializer 的实现类,调用他们的initialize() 方法做初始化
// 在这一步创建了BeanDefinitionLoader 用户读取spring 的配置,转化为IOC容器内部数据
// 在最后,他会群发ApplicationPreparedEvent
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 调用Spring 的applicationContext.refresh(); 对Spring 容器刷新
refreshContext(context);
// 调用ApplicationRunner、CommandLineRunner 接口的实现方法run(), 做后续操作
afterRefresh(context, applicationArguments);
// 群发SpringApplicationEvent 事件
listeners.finished(context, null);
// 秒表停止计时
stopWatch.stop();
// 打印springboot 启动耗时日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
代码引用
- SpringBoot 启动类:SpringApplication
- SpringBoot 加载
META-INF/spring.factories:SpringApplicationgetSpringFactoriesInstances()方法- Spring 加载
META-INF/spring.factories:SpringFactoriesLoaderloadFactoryNames()方法- 事件处理的核心代码:EventPublishingRunListener
- 故障分析:FailureAnalysis
启动总结
- 可以很直观的看出,在spring启动时用到了很多
spring.factories配置,很多Spring event。在run()方法执行的每个阶段都有event 被触发,例如启动时触发ApplicationStartedEvent, 初始化environment时触发ApplicationEnvironmentPreparedEvent,初始化Spring Context触发ApplicationPreparedEvent, 启动结尾时触发SpringApplicationEvent等等。refreshContext(context)是一个很重要的方法,前面的许多操作都是为了它而准备的,在这一步,会调用的applicationContext.refresh()刷新启动spring容器。 我们可以在源码里看到大量的装箱、拆箱代码,熟悉这些装箱拆箱对象,有助于理解Spring 的启动过程。
SpringBoot 自动装配
源码
启动
@SpringBootApplication
public class HelloWebSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWebSecurityApplication.class, args);
}
}
在Spring Context 的
refresh()方法中,会触发自动装配
// org.springframework.context.support.AbstractApplicationContext#refresh
@Override
public void refresh() throws BeansException, IllegalStateException {
//从这里开始会触发自动装配
registerBeanPostProcessors(beanFactory);
}
@SpringBootApplication注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
// 自动装配注解
@EnableAutoConfiguration
// 扫描该类所在包下面的配置, 相当于<context:component-scan>。
// excludeFilters 根据一定规则过滤类,FilterType.CUSTOM 表示自定义过滤规则
@ComponentScan(
excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// 使用EnableAutoConfiguration 的exclude 属性
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
Class<?>[] exclude() default {};
// 使用EnableAutoConfiguration 的excludeName 属性
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
String[] excludeName() default {};
// 使用ComponentScan 的basePackages 属性
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
// 使用ComponentScan 的basePackageClasses 属性
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
@EnableAutoConfiguration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
// EnableAutoConfiguration 注解的解释器
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// 用于检查环境变量里面是否开启了自动配置
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
// 根据class 排除某项自动配置
Class<?>[] exclude() default {};
// 根据className 排除某项自动配置
String[] excludeName() default {};
}
EnableAutoConfigurationImportSelector@EnableAutoConfiguration的解释器
@Deprecated
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
//检查环境设置里,是否开启了自动配置(EnableAutoConfiguration)
@Override
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
}
EnableAutoConfigurationImportSelector解释器的父类AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
// 在这里将所有包下面的 META-INF/spring.factories 的配置扫描出来
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
//加载元数据
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//加载注解属性,exclude、excludeName
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//扫描META-INF/Spring.factories 的自动配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
//删除重复配置
configurations = removeDuplicates(configurations);
//排序
configurations = sort(configurations, autoConfigurationMetadata);
//获取exclude 配置的数据
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//检查配置exclude的class是否存在,是否可加载,并移除无效的配置
checkExcludedClasses(configurations, exclusions);
//移除exclude的类
configurations.removeAll(exclusions);
//调用spring.factories 里的OnClassCondition 对配置类筛选校验
configurations = filter(configurations, autoConfigurationMetadata);
// 加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类
// 触发fireAutoConfigurationImportEvents事件
// 当fireAutoConfigurationImportEvents事件被触发时,打印出已经注册到spring上下文中的@Configuration注解的类,打印出被阻止注册到spring
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}
总结
- 可以看出,Springboot中自动装配的核心处理是
AutoConfigurationImportSelector类的selectImports()方法,而他是通过扫描所有jar包下MATE-INFO/spring.factories 文件来获取自动装配配置的selectImports()方法会根据exclude相关参数移除某些自动配置类,前面的许多操作步骤都是将exclude 参数优雅的带给selectImports()方法。- 自动装配会在spring 容器启动时触发。
编写自己的starter
例如:写一个基于Jedis 的Redis Starter
要做的只有两件事:
- 暴露Jedis 配置,并能在starter中读取它。
- 根据配置创建Jedis 对象或工具,提供给调用者使用。
配置Jedis 属性
//编写属性配置
@ConfigurationProperties("zkn.redis")
public class RedisProperties {
private String host;
private Integer port = 6379;
private Integer database = 0;
private String password;
private Integer timeout = 2000;
private Integer maxActive = 8;
private Integer maxTotal = 8;
// 省略Get\Set
}
//编写配置文件自动提示,在META-INF 下面加入spring-configuration-metadata.json,内容如下:
{
"hints": [],
"groups": [
{
"name": "zkn.redis",
"type": "cn.zkn.spring.boot.config.redis.property.RedisProperties",
"sourceType": "cn.zkn.spring.boot.config.redis.property.RedisProperties"
}
],
"properties": [
{
"sourceType": "cn.zkn.spring.boot.config.redis.property.RedisProperties",
"name": "zkn.redis.host",
"type": "java.lang.String"
},
{
"sourceType": "cn.zkn.spring.boot.config.redis.property.RedisProperties",
"name": "zkn.redis.port",
"type": "java.lang.Integer"
},
{
"sourceType": "cn.zkn.spring.boot.config.redis.property.RedisProperties",
"name": "zkn.redis.database",
"type": "java.lang.Integer"
}
//其他属性略...
]
}
暴露自己的redis服务或工具
//编写自动配置类并读取配置、初始化JedisPool,并通过@Bean 注解暴露自己的服务
@Configuration
@ConditionalOnClass(RedisService.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
@Autowired
private RedisProperties redisProperties;
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "zkn.redis", value = "enabled", havingValue = "true")
public RedisService multiRedisConfigBeanPostProcessor() {
JedisPool jedisPool = configJedisPool(redisProperties);
return new RedisService(jedisPool);
}
/**
* 配置Jedis连接池
*
* @param redisProperties redis配置信息
* @return Jedis连接池
*/
private JedisPool configJedisPool(RedisProperties redisProperties) {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(redisProperties.getMaxTotal());
jedisPoolConfig.setMaxIdle(redisProperties.getMaxActive());
jedisPoolConfig.setBlockWhenExhausted(true);
jedisPoolConfig.setTestOnCreate(true);
jedisPoolConfig.setTestOnBorrow(true);
jedisPoolConfig.setTestOnReturn(true);
jedisPoolConfig.setTestWhileIdle(true);
return new JedisPool(jedisPoolConfig,
redisProperties.getHost(),
redisProperties.getPort(),
redisProperties.getTimeout(),
redisProperties.getPassword(),
redisProperties.getDatabase());
}
}
//自己的Service服务
public class RedisService {
private Jedis jedis ;
public RedisService(JedisPool jedisPool) {
jedis = jedisPool.getResource();
}
public Jedis getJedis() {
return jedis;
}
//这里写自己的实现
}
//配置RedisAutoConfiguration地址,在META-INF 下面加入spring.factories,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.zkn.spring.boot.config.redis.RedisAutoConfiguration
总结
starter本身的编写很简单,扩展性也很好,但对于复杂的设计,例如多个Redis 实例的使用,则需要用到Spring相关的扩展点,编写也更加复杂。