springboot的出现可以说是广大java开发者们的福音,它极大的简化了配置,把我们从繁重的项目环境搭建、各种配置文件编写中解脱出来,让我们更专注于自己的业务逻辑。基于springboot项目,我们只需要把自己需要的组件通过maven pom的方式引入就行了,springboot会自动帮我们完成IOC注入。
但是我们都知道springboot默认的扫描路径都是以application启动主类作为根路径扫描的,所以只会帮我们把当前项目的主路径及其子包下的bean扫描出来(当然可以通过@ComponentScan去配置),所以一直很好奇我们引入的其他模块到底是怎么自动注入进来的,下面我们就来看看究竟是咋回事。
先让我们来看看application主类里都有啥:
@SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class);
}
}
其实关键就在这个@SpringBootApplication注解里面,打开看一下:
@SpringBootApplication=@SpringBootConfiguration(里面就是@Configuration注解)+@EnableAutoConfiguration(重要)+@ComponentScan(指定扫描路径用的)
其实@SpringBootApplication就是上面这三个注解的集合,第一个和第三个我们都知道是干啥用的,现在就来看看第二个@EnableAutoConfiguration:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
大家看到没,注意看第6行,原来是用了@Import注解,可能又有人有疑惑了,这个@Import又是干啥的呢?额......,这里简单说下大家可以去自行在了解下。@Import注解就是用来导入一个类到spring IOC中,跟我们平常用的@Component注解将类声明为一个bean是类似的。
好,看到这里我们知道此时会去初始化EnableAutoConfiguration.class这个类,让我们再进去这个类里面看看:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
private static final String[] NO_IMPORTS = new String[0];
private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
public AutoConfigurationImportSelector() {
}
/**
* 返回需要注入到IOC容器里的全类名数组
*/
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
/**
* 就是调用下面的这个方法
*/
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
/**
* 这一步会去扫描所有类路径下的META-INF/spring.factories文件,将其里面的configuration类捞出来
*/
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
因为这个类比较长嘛,我们就简单描述下这个类的执行过程:
- AutoConfigurationImportSelector类实现了ImportSelector这个接口,重写了它的selectImports()方法,这个方法会返回一个全类名的数组,里面的类会被加载到IOC中
- 重写的selectImports()方法里面会调用自己的内部getAutoConfigurationEntry()方法。这个方法会去扫描所有类路径下的META-INF/spring.factories文件,将其里面的configuration类捞出来
- 每个META-INF/spring.factories文件内配置的configuration类最终被加载到IOC中
其实看到这里,我们大概就能明白了。其实我们开发中引入的那些starter包里面几乎都会含有META-INF/spring.factories这样一个文件,像我们常用的mybatis-spring-boot-starter,它里面的spring.factories配置文件是这样的:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
然后就会去加载MybatisAutoConfiguration这个类到IOC中,同时这个类里面又声明了其他的bean,例如这里又初始化了sqlSessionFactory加入到IOC中
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
this.applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
@ConditionalOnMissingBean注解的意思就是当bean容器中不存在该类的时候就将其初始化放入bean容器中,跟其类似的还有很多个注解,例如:
- @ConditionalOnWebApplication:仅在web应用下生效。
- @ConditionalOnClass:需要指定的类(依赖)存在才生效。
- @ConditionalOnProperty:主配置文件中存在指定的属性才生效。
所以就通过这一系列的Conditional注解又把这个starter里面的其他bean完成了实例化放入到了IOC中,这样就能直接在我们的主项目中引用了。
最后我们再来总结下,springboot项目启动的时候会去扫描每个类路径下的META-INF/spring.factories文件,这个文件里面会对应一个AutoConfiguration类,然后会对这个类进行实例化,同时这个AutoConfiguration类里面会通过@ConditionalOn系列注解和@Bean注解搭配完成一系列功能类的初始化到IOC容器中。即完成了通过maven pom引入就完成了自动装配的功能。