1. 从SpringBootApplication开始
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Start {
public static void main(String[] args) {
SpringApplication.run(Start.class, args);
}
}
通过main方法启动,进入,跟进去:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
发现是通过SpringApplication的run来启动的,创建容器的方法是createApplicationContext:
protected ConfigurableApplicationContext createApplicationContext() {
return this.applicationContextFactory.create(this.webApplicationType);
}
最骚操作的是ApplicationContextFactory这个类,自己拿了一个自己的实现类,还是lambda表达式,所以很有迷惑性,当时我一直以为我错过了什么Java的重要特性了。
ApplicationContextFactory DEFAULT = (webApplicationType) -> {
try {
switch (webApplicationType) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
}
catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
};
Spring容器就创建好了,后面基本就是进入Spring模式的核心refresh了,然后就开始扫描解析BeanDefinition等等操作。
2. @SpringBootApplication注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
@SpringBootConfiguration:其实就是@Configuration,会通过ConfigurationClassPostProcessor处理,会在Spring处理BeanFactoryPostProcessor阶段被处理 @ComponentScan:这个我们比较熟悉了扫描Bean的
SpringBoot中的新重点是@EnableAutoConfiguration:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
这个注解Import了AutoConfigurationImportSelector,AutoConfigurationImportSelector继承了ImportSelector,当被Import导入的时候就会执行它的selectImports。 关于Import注解的逻辑可以参考后面的Import相关的内容。
最终在AutoConfigurationImportSelector的getCandidateConfigurations方法中通过:
SpringFactoriesLoader#loadFactoryNames方法导入了配置文件META-INF/spring.factories配置的类。
spring.factories配置文件中配置了一些XXXAutoConfiguration类,这些类又会导入一些类,例如spring-boot依赖的spring-boot-autoconfigure的META-INF/spring.factories中配置了一个ServletWebServerFactoryAutoConfiguration类。
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
ObjectProvider<WebListenerRegistrar> webListenerRegistrars) {
return new ServletWebServerFactoryCustomizer(serverProperties,
webListenerRegistrars.orderedStream().collect(Collectors.toList()));
}
}
它又会通过@Import、@Bean注解导入一些它需要的类。
有2个地方需要注意:
- 有@Conditional注解表示满足条件才导入,例如@ConditionalOnClass(ServletRequest.class),表示有ServletRequest这个类才导入这个类
- 我们在配置文件中添加server.port=8080为什么生效,就是@EnableConfigurationProperties(ServerProperties.class)配置的
具体的内容,可以看看后面的相关内容。
spring.factories不一定都是AutoConfiguration类,例如:
在spring-boot中:
在spring-boot-autoconfigure中:
3. @Conditional
关于了解一下下面的2个东西。
首先,它会在下面3个地方被处理:
- ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod:处理@Bean
- ConfigurationClassParser.processConfigurationClass(ConfigurationClass):处理@Configuration
- ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClass, SourceClass):处理@ComponentScan
常见的@Conditional注解,及其作用:
Conditional注解
作用
@ConditionalOnJndi
在JNDI存在的条件下查找指定的位置
@ConditionalOnBean
当容器里有指定Bean的条件下
@ConditionalOnJava
基于JVM版本作为判断条件
@ConditionalOnClass
当类路径下有指定的类的条件下
@ConditionalOnProperty
指定的属性是否有指定的值
@ConditionalOnResource
类路径是否有指定的值
@ConditionalOnExpression
基于SpEL表达式为true的时候作为判断条件才去实例化
@ConditionalOnMissingBean
当容器里没有指定Bean的情况下
@ConditionalOnMissingClass
当容器里没有指定类的情况下
@ConditionalOnWebApplication
当前项目时Web项目的条件下
@ConditionalOnOnSingleCandidate
当指定Bean在容器中只有一个,或者有多个但是指定首选的Bean
@ConditionalOnNotWebApplication
当前项目不是Web项目的条件下
关于@Conditional,可以看一下Spring Conditional原理与实例这篇文章。
4. @Import
@Import注解的参数有3种:
普通类直接注入
实现ImportSelector接口的类
实现ImportBeanDefinitionRegistrar接口的类
public class ImportSelectorTest implements ImportSelector {
@Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{"vip.mycollege.User","vip.mycollege.Teacher"}; }
}
public class ImportBeanDefinitionRegistrarTest implements ImportBeanDefinitionRegistrar {
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { RootBeanDefinition beanDefinition = new RootBeanDefinition(Teacher.class); registry.registerBeanDefinition("teacher", beanDefinition); }
}
处理Import的类是ConfigurationClassPostProcessor,这是一个BeanFactoryPostProcessor。在Spring核心流程梳理中我们介绍了这个是在BeanDefinition都解析好之后,在AbstractApplicationContext#refresh方法中通过invokeBeanFactoryPostProcessors来处理。
其中:@Import注解的处理逻辑在:ConfigurationClassParser的processImports方法中。
for (SourceClass candidate : importCandidates) {
//处理ImportSelector
if (candidate.isAssignable(ImportSelector.class)) {
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
//处理ImportBeanDefinitionRegistrar
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
//处理其他类
else {
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
5. @EnableConfigurationProperties
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
}
EnableConfigurationPropertiesRegistrar又是一个ImportBeanDefinitionRegistrar。
主要注册了2个类:
- ConfigurationPropertiesBindingPostProcessor
- BoundConfigurationProperties
主要是ConfigurationPropertiesBindingPostProcessor是一个BeanPostProcessor,实现了postProcessBeforeInitialization,会在一个bean的属性值设置完之后,但是还没有执行init-method、 afterPropertiesSet等初始化之前被调用。
关于Spring Bean的生命周期可以看看:Spring Bean生命周期
具体处理逻辑是:
通过ConfigurationPropertiesBean的create方法找到@ConfigurationProperties注解并处理验证相关逻辑
通过ConfigurationPropertiesBindingPostProcessor自己的bind方法执行绑定操作
bind的逻辑是在ConfigurationPropertiesBinder的bind方法,它会去找到并解析@ConfigurationProperties注解
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties {
/** * Server HTTP port. */ private Integer port;
}
所以我们在配置文件中配置的server.port=8080会生效。
6. 自定义starter
了解了上面的原理之后,自定义starter就没啥难的了。
一般官方的artifactId是spring-boot-starter-web,这种spring-boot-starter-xxx。
我们自定义的artifactId会使用:druid-spring-boot-starter,这种xxx-spring-boot-starter
只需要定义一个XXXAutoConfigure:
@Configuration
@EnableConfigurationProperties({NBProperties.class})
@Import({NBConfiguration.class})
public class NBDataSourceAutoConfigure {
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
return new NBDataSourceWrapper();
}
}
通过@Import注解添加一下默认配置需要的类,通过@Bean注入一下默认需要的类实例。
然后在resources目录下创建一个META-INF目录,创建一个spring.factories文件,把自己的XXXAutoConfigure配置进去就可以了。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
vip.mycollege.autoconfig.DruidDataSourceAutoConfigure
如果你高兴,还可以加一个XXXProperties:
@ConfigurationProperties("wo.nb")
public class NBProperties {
private String realNB;