SpringBoot 源码阅读:启动及自动装配

591 阅读7分钟

版本

  • 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.javainitialize() 方法

// 初始化 创建并初始化 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.javarun() 方法

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);
	}
}

代码引用

启动总结

  • 可以很直观的看出,在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相关的扩展点,编写也更加复杂。