背景
Spring基本属于目前离不开的开发框架了,Spring IOC更是如此,IOC主要的实现,控制反转,将所有Bean归于Spring容器中进行管理,通过使用CurrentHashMap来保证其线程安全性。使得开发对Bean的管理与使用变得及其方便、简洁。那么我们今天主要来盘一盘Spring中AnnotationConfigApplicationContext类启动流程。当面试提起这个问题时,分分钟拿下对手。
区别
首先我们在获取SpringBean时常用是ApplicationContext,反而BeanFactory用得少,那么先聊一聊BeanFactory与ApplicationContext的区别,我们根据其类图可以看出
BeanFactory是获取Bean对象,顾名思义,专注于Bean的获取,读取Bean的配置文档,管理Bean的加载与其实例化,维护Bean的生命周期。ApplicationContext则实现了BeanFactory,继承了其功能的情况下,实现了其它的加强方法。提供更为完整的框架功能,事件传递、国际化、项目内文件资源访问等功能。
实现
再来扒一扒AnnotationConfig 的实现,前面图中我们了解到AnnotationConfig是ApplicationContext的实现子类,包含了其相关方法。接下来着重通过执行流程图来进行分析其操作运行步骤。
1.首先其启动,最先是获取ApplicationContext。根据AnnotationConfig对象实现并获取
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
2.首先在构造函数中优先执行父类,会根据AnnotationConfig的父类GenericApplicationContext去创建默认的实体工厂,也就是DefaultListableBeanFactory,该Bean工厂主要负责后续的Bean注入以及实例化IOC的beanDefinitionMap存储对象。
// 父类构造函数初始化对象源码
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
3.再执行AnnotationConfig中自身的构造函数,创建实例化两个对象。一个读取对象AnnotationBeanDefinitionReader,后续会执行doRegisterBean(注册Bean)方法。一个扫描对象ClassPathBeanDefinitionScanner。
// 当前调用的构造函数源码
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}
// 创建扫描对象与读取对象的无参构造函数
public AnnotationConfigApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
4.走到AnnotationBeanDefinitionReader对象中的doRegisterBean方法中,方法详细如同中解析。会对Bean的相关注解进行判断处理。注册前使用BeanDefinitionHolder去包装Bean对象。也就是源码中最后的三行代码。
<T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
abd.setInstanceSupplier(instanceSupplier);
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
for (BeanDefinitionCustomizer customizer : definitionCustomizers) {
customizer.customize(abd);
}
// 使用BeanDefinitionHolder 包装对象
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
5.BeanDefinitionReaderUtils注册在第四步被包装了的Bean对象。
注册时按源码的执行逻辑是,根据bean的名称去调用注册Bean方法registerBeanDefinition,如果有别名则注册别名。该方法会去选择程序最开始创建的BeanFactory对象。在这里也就是第二步中GenericApplicationContext类创建的DefaultListableBeanFactory对象。
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
6.调用注册之后就会选择Bean工厂去完成该行为,这里通过AnnotationConfig父类创建的是DefaultListableBeanFactory,那么自然就会走入该类方法,然后将beanDefinition存放在IOC容器中,也就是beanDefinitionMap中去。
最后通过this.beanDefinitionMap.put存放Bean到Spring IOC容器中。
完成启动,之后即可以通过ApplicationContext上下文获取IOC容器中的对象,如下例代码可以打印出所有容器中Bean的ID,即注册时的Bean Name。
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
总结
以上即AnnotationConfig类的启动流程,阅读源码的一个心得是,首先大量的设计模式,单例、策略、模板等等,一定要对设计模式有所见解,否则很难读懂其含义。 其次就是可以通过Debug了解程序源码运行的走向,进行一个较为全面的了解。最后必然就是高效的学习+输出了。今日分享到此,下回见。