核心注解:@SpringBootApplication
springboot的入口是一个application类新建的springboot工程自带一个application类,这个类上一般标注有@SpringBootApplication注解,相信@SpringBootApplication这个注解大家或多或少都接触过。下面是@SpringBootApplication注解的部分内容:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {...}
大概可以把 @SpringBootApplication看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。
那么来看看这几个注解什么作用:
@Configuration:
@Configuration允许在上下文中注册额外的 bean 或导入其他配置类,这也是我们可以在启动类(Application类)注入我们想要让IOC容器管理的类(@Bean的方式)的原因。
@Configuration 注解的类可以看作是能生产让Spring IoC容器管理的Bean实例的工厂。
@Bean 注解的方法返回的对象可以被注册到spring容器中。
@ComponentScan:
扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如上面片段所示,容器中将排除TypeExcludeFilter和AutoConfigurationExcludeFilter。
@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 {};
}
我们可以看到里面通过@Import({AutoConfigurationImportSelector.class})引入了AutoConfigurationImportSelector类,我们进入这个类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
//成员变量
/*调用链selectImports->AutoConfigurationEntry->getCandidateConfigurations*/
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
//!!!!!
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
// ...
//!!!!!
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// ...
}
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//!!!!!
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
// 其他方法
}
可以看到AutoConfigurationImportSelector类中存在调用链selectImports->AutoConfigurationEntry->getCandidateConfigurations,而getCandidateConfigurations方法中调用了SpringFactoriesLoader里的loadFactoryNames方法。
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
//!!!!!
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
Map<String, List<String>> result = new HashMap();
try {
//!!!!!
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
//...
}
在loadFactoryNames中loadFactoryNames紧接着会调用类中另一个叫做loadSpringFactories的方法,绕了一大圈后我们可以发现,loadFactoryNames这个静态方法可以读取所有的jar包中META-INF/spring.factories文件,而自动配置的类就在这个文件中进行配置。
在springboot的starter的jar包中就可以找到对应的spring.factories文件
内容如下:
同样的,如果你的项目使用了mybatis,那么我们可以在依赖库中找到mybatis的starter的jar包,然后找到spring.factories文件
这个文件里面的内容如下:
# Auto Configure
# pattrtn: key=value1,value2...
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
spring.factories文件是什么,有什么作用?
结合上述两个文件,spring.factories其实就是个键值对格式的配置文件。
作用:spring.factories用键值对的方式记录了所有需要加入容器的类。
springboot自动装配流程
springboot通过@Import({AutoConfigurationImportSelector.class})引入了AutoConfigurationImportSelector,EnableAutoConfigurationImportSelector的selectImports方法返回的类名,来自(所有 Spring Boot Starter的 )spring.factories文件内的配置信息。
这些key等于EnableAutoConfiguration的配置信息,由于spring boot应用启动时使用了EnableAutoConfiguration注解,所以EnableAutoConfiguration注解通过import注解将EnableAutoConfigurationImportSelector类实例化,并且将其selectImports方法返回的类名实例化后注册到spring容器。
注意:如果你想要实现自己的自动配置(自定义spring boot starter),就将你的类通过键值对的方式写在你的META-INF/spring.factories即可,value是你的自动配置类,key必须是org.springframework.boot.autoconfigure.EnableAutoConfiguration。
讲到这里,在额外延伸出一个问题:
spring.factories中这么多配置,每次启动都要全部加载么?
答案是:不会全部加载。
这里就不得不提到@ConditionalOnXXX 注解了,我们以mybatis为例子,下面仍然是其spring.factories文件
# Auto Configure
# pattrtn: key=value1,value2...
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
我们进入EnableAutoConfiguration里面随便一个类,比如MybatisLanguageDriverAutoConfiguration类
可以看到类被声明了一个@ConditionalOnClass({LanguageDriver.class})的注解,这个注解表示当前配置类只有在classpath中存在LanguageDriver类的情况下才会生效。这意味着如果项目依赖中不包含LanguageDriver的实现,那么此配置将被忽略。这一步进行了一遍筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。
Springboot 提供了其他的条件注解:
- @ConditionalOnBean:当容器里有指定 Bean 的条件下
- @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
- @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
- @ConditionalOnClass:当类路径下有指定类的条件下
- @ConditionalOnMissingClass:当类路径下没有指定类的条件下
- @ConditionalOnProperty:指定的属性是否有指定的值
- @ConditionalOnResource:类路径是否有指定的值
- @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
- @ConditionalOnJava:基于 Java 版本作为判断条件
- @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
- @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
- @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下
通过这些条件注解,在自定义starter时可以按需加载配置类。