前言:
本文SpringBoot版本:2.5.12
SpringBoot
- 约定大于配置
- 减少繁琐的配置,开箱即用
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication
SpringBoot应用标注在某个类上,说明这个类是SpringBoot的主配置类,运行这个类的main方法启动SpringBoot应用
ps:项目中使用了@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})的写法。
exclude 排除此类的AutoConfig,即禁止 SpringBoot 自动注入数据源配置。
这是因为DataSourceAutoConfiguration.class 会自动查找 application.yml 或者 properties 文件里的 spring.datasource.* 相关属性并自动配置单数据源。
排查掉自动配置,通过@ConfigurationProperties("spring.datasource手动配置多个数据源。
我们继续查看@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 {}
其中前4个都是元注解,我们重点看后面的@EnableAutoConfiguration:
- @Target(ElementType.TYPE):接口作用域
- @Retention(RetentionPolicy.RUNTIME):生命周期,运行时有效
- @Documented:文档注释
- @Inherited:子类可以继承该注解
@SpringBootConfiguration:表示这是一个SpringBoot配置类,其实就是Configuration配置类。
@EnableAutoConfiguration
- SpringBoot的核心注解,开启自动配置功能
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
@AutoConfigurationPackage 自动配置包
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}
他的核心是AutoConfigurationPackages.Registrar。
@Import(AutoConfigurationPackages.Registrar.class)默认将主配置类中(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中。具体实现是如下代码:AutoConfigurationPackages
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
//默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
我们重点看这个registerBeanDefinitions方法。他最终会通过BeanDefinitionRegistry 调用registerBeanDefinition()方法
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
beanDefinition.addBasePackages(packageNames);
}
else {
registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
}
}
BeanDefinitionRegistry 是一个接口,他有三个实现类
- DefaultListableBeanFactory
- GenericApplicationContext
- SimpleBeanDefinitionRegistry
查看BeanDefinitionRegistry提供的方法,我们能知道他具有的主要能力有
- 以Map<String, BeanDefinition>的形式注册bean
- 根据beanName 删除和获取 beanDefiniation
- 得到持有的beanDefiniation的数目
- 根据beanName 判断是否包含beanDefiniation
public interface BeanDefinitionRegistry extends AliasRegistry {
// 向beanFactory中注册一个BeanDefinition
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;
// 根据beanName从beanFactory中移除一个已经注册的BeanDefinition
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
// 根据beanName从beanFactory中获取一个BeanDefinition
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
// 判断beanFactory中是否有beanName的BeanDefinition
boolean containsBeanDefinition(String beanName);
// 获取beanFactory中的所有BeanDefinition的beanName
String[] getBeanDefinitionNames();
// 获取beanFactory中的BeanDefinition的数量
int getBeanDefinitionCount();
// 判断beanFactory中的beanName是否被占用
boolean isBeanNameInUse(String beanName);
}
其中GenericApplicationContext我们可以发现其实他最终也是通过DefaultListableBeanFactory 调用的。
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
private final DefaultListableBeanFactory beanFactory;
}
那么DefaultListableBeanFactory和SimpleBeanDefinitionRegistry的主要区别是什么呢?
SimpleBeanDefinitionRegistry: 通过代码,我们可以知道SimpleBeanDefinitionRegistry是一个非常简单的实现方式,没有内置的beanFactory。实际上这个类是提供给我们测试使用的,开发情况下Spring不会使用这个类
DefaultListableBeanFactory : 这个类非常重要,我们重点看这个类。它是BeanDefinitionResgitry接口的基本实现,实际上很多beanFactory最后都是调用DefaultListableBeanFactory类的registryBeanFactory来注册beanDefinition。
分析完之后,我们查看最重要的registryBeanFactory()方法,也就是向beanFactory中注册BeanDefinition。
下面来看下这个方法到底做了哪些事情。其实也很简单。
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
...
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
// 保存所有的Bean名称
/** List of bean definition names, in registration order */
private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
// 注册Bean定义信息~~
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
...
BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName);
// 如果不为null,说明这个beanName对应的Bean定义信息已经存在了~~~~~
if (oldBeanDefinition != null) {
// 是否允许覆盖(默认是true 表示允许的)
if (!isAllowBeanDefinitionOverriding()) {
// 抛异常
}
// 若允许覆盖 那还得比较下role 如果新进来的这个Bean的role更大
// 比如老的是ROLE_APPLICATION(0) 新的是ROLE_INFRASTRUCTURE(2)
// 最终会执行到put,但是此处输出一个warn日志,告知你的bean被覆盖啦~~~~~~~(我们自己覆盖Spring框架内的bean显然就不需要warn提示了)
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// 仅仅输出一个logger.warn
}
// 最终会执行put,但是内容还不相同 那就提醒一个info信息吧
else if (!beanDefinition.equals(oldBeanDefinition)) {
// 输出一个info信息
}
else {
// 输出一个debug信息
}
// 最终添加进去 (哪怕已经存在了~)
// 从这里能看出Spring对日志输出的一个优秀处理,方便我们定位问题~~~
this.beanDefinitionMap.put(beanName, beanDefinition);
// 请注意:这里beanName并没有再add了,因为已经存在了 没必要了嘛
}
else {
// hasBeanCreationStarted:表示已经存在bean开始创建了(开始getBean()了吧~~~)
if (hasBeanCreationStarted()) {
// 注册过程需要synchronized,保证数据的一致性
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition); // 放进去
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
//
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
// Still in startup registration phase
// 表示仍然在启动 注册的状态~~~就很好处理了 put仅需,名字add进去
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
// 手动注册的BeanNames里面移除~~~ 因为有Bean定义信息了,所以现在不是手动直接注册的Bean单例~~~~
this.manualSingletonNames.remove(beanName);
}
// 这里的意思是:但凡你新增了一个新的Bean定义信息,之前已经冻结的就清空呗~~~
this.frozenBeanDefinitionNames = null;
}
// 最后异步很有意思:老的bean定义信息不为null(beanName已经存在了),或者这个beanName直接是一个单例Bean了~
if (oldBeanDefinition != null || containsSingleton(beanName)) {
// 做清理工作:
// clearMergedBeanDefinition(beanName)
// destroySingleton(beanName); 销毁这个单例Bean 因为有了该bean定义信息 最终还是会创建的
// Reset all bean definitions that have the given bean as parent (recursively). 处理该Bean定义的getParentName 有相同的也得做清楚 所以这里是个递归
resetBeanDefinition(beanName);
}
}
@Override
public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
// 移除整体上比较简单:beanDefinitionMap.remove
// beanDefinitionNames.remove
// resetBeanDefinition(beanName);
BeanDefinition bd = this.beanDefinitionMap.remove(beanName);
// 这里发现移除,若这个Bean定义本来就不存在,事抛异常,而不是返回null 需要注意~~~~
if (bd == null) {
throw new NoSuchBeanDefinitionException(beanName);
}
if (hasBeanCreationStarted()) {
synchronized (this.beanDefinitionMap) {
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames);
updatedDefinitions.remove(beanName);
this.beanDefinitionNames = updatedDefinitions;
}
} else {
this.beanDefinitionNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
resetBeanDefinition(beanName);
}
// 这个实现非常的简单,直接从map里拿
@Override
public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
BeanDefinition bd = this.beanDefinitionMap.get(beanName);
if (bd == null) {
throw new NoSuchBeanDefinitionException(beanName);
}
return bd;
}
@Override
public boolean containsBeanDefinition(String beanName) {
return this.beanDefinitionMap.containsKey(beanName);
}
@Override
public int getBeanDefinitionCount() {
return this.beanDefinitionMap.size();
}
@Override
public String[] getBeanDefinitionNames() {
String[] frozenNames = this.frozenBeanDefinitionNames;
if (frozenNames != null) {
return frozenNames.clone();
}
else {
return StringUtils.toStringArray(this.beanDefinitionNames);
}
}
// ==========这个方法非常有意思:它是间接的实现的===============
// 因为BeanDefinitionRegistry有这个方法,而它的父类AbstractBeanFactory也有这个方法,所以一步小心,就间接的实现了这个接口方法
public boolean isBeanNameInUse(String beanName) {
// 增加了一个hasDependentBean(beanName); 或者这个BeanName是依赖的Bean 也会让位被使用了
return isAlias(beanName) || containsLocalBean(beanName) || hasDependentBean(beanName);
}
}
通过代码分析,我们可以得到一个beanDefinitionMap。他是一个ConcurrentHashMap能保证线程安全,没错,这个就是我们常说的Spring的IOC容器。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);他的Key是String类型的,就是我们常说的Bean的名称,Value就是对象本身,还包含对象的属性:是否单例,是否懒加载等。所以封装成了BeanDefinition对象。BeanDefinition存储着真正的对象,以及对象的相关属性。
到这里,我们跟完了@AutoConfigurationPackage,知道了这里会注册Bean放入IOC容器中。
接着我们重新查看@EnableAutoConfiguration注解的@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector.class又是我们需要重点关注的一个地方,这个类就是SpringBoot能够自动装配的原因。
我们主要看selectImports()方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断SpringBoot是否开启自动配置
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 获取需要被引入的自动配置信息
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 判断是否开启自动配置
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取@EnableAutoConfiguration注解的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 从spring.factories文件中获取配置类的全限定名数组
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// 获取注解中exclude或excludeName排除的类集合
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查被排除类是否可以实例化,是否被自动配置所使用,否则抛出异常
checkExcludedClasses(configurations, exclusions);
// 去除被排除的类
configurations.removeAll(exclusions);
// 使用spring.factories配置文件中配置的过滤器对自动配置类进行过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 抛出事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
再继续深入源码,我们重点看getCandidateConfigurations(annotationMetadata, attributes)方法。
最终来到SpringFactoriesLoader的loadSpringFactories。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
//从类路径的META-INF/spring.factories中加载所有默认的自动配置类
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
//获取EnableAutoConfiguration指定的所有值,也就是EnableAutoConfiguration.class的值
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
通过这段代码,我们知道了SpringBoot启动的时候,会从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,并将这些值作为自动配置类导入到容器中,自动配置类就会生效,最后完成自动配置工作。
EnableAutoConfiguration默认在spring-boot-autoconfigure这个包中,如下图。
这里附上一张自动装配的流程图。
到现在,我们重点分析了 @EnableAutoConfiguration,这个注解的主要作用就是开启自动装配。
而这个注解我们又主要关注两点:
- @AutoConfigurationPackage:这个里面我们主要关注registerBeanDefinition,注册Bean信息,存入IOC容器中。
- @Import(AutoConfigurationImportSelector.class):加载配置文件。扫描读取配置文件META-INF/spring.factories。
总结:
通过上文的学习,我们已经对SpringBoot启动项上的注解做的事情有了详细的了解,我们主要分析了将Bean注册到IOC容器中的方法,以及SpringBoot的自动装配。接下来的章节,我们继续分析SpringBoot的启动过程到底是怎么样的。
巨人的肩膀