所有文章

https://www.cnblogs.com/lay2017/p/11478237.html

prepareContext方法核心逻辑

上一篇文章中,我们通过createApplicationContext方法创建了一个ApplicationContext的实例对象。本文将阅读一下在ApplicationContext在refresh之前的prepareContext中做了哪些事情。

我们跟进prepareContext方法

private void prepareContext(
ConfigurableApplicationContext context,
ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments,
Banner printedBanner
) {
// 设置Environment
context.setEnvironment(environment);
// 后置处理
postProcessApplicationContext(context);
// 调用ApplicationContextInitializer接口的实现
applyInitializers(context);
// 发布ApplicationContext准备事件
listeners.contextPrepared(context); ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 注册args参数为单例bean
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
// 注册banner为单例bean
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
// 设置beanFactory中是否允许重复bean覆盖
((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
} // 加载main方法所在类
Set<Object> sources = getAllSources();
// 注册main方法所在类到beanFactory
load(context, sources.toArray(new Object[0]));
// 发布Context加载事件
listeners.contextLoaded(context);
}

我们看到prepareContext方法主要逻辑包含了三块内容

1)基本的初始化,如设置Environment,调用ApplicationContextInitializer接口的实现类

2)注册现有的对象为单例bean,如args、banner

3)加载main方法所在的主类

load方法加载主类

很显然,加载main方法的主类是我们关注的重点。我们先看看getAllSources方法返回

public Set<Object> getAllSources() {
Set<Object> allSources = new LinkedHashSet<>();
// 添加主类
if (!CollectionUtils.isEmpty(this.primarySources)) {
allSources.addAll(this.primarySources);
}
// 添加附加资源类
if (!CollectionUtils.isEmpty(this.sources)) {
allSources.addAll(this.sources);
}
return Collections.unmodifiableSet(allSources);
}

primarySources是在SpringApplication初始化的时候设置的,而sources默认是没有的。所在getAllSoures方法将返回main方法所在的主类。

下面,我们跟进load方法,阅读一下加载main所在主类的逻辑

protected void load(ApplicationContext context, Object[] sources) {
// 获取BeanDefinition加载器
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
// 加载资源
loader.load();
}

load方法中,先是获得了BeanDefinitionLoader,然后去加载资源。这里要注意!为什么是BeanDefinitionLoader呢?

首先,我们得知道Spring的bean资源来自各种地方,如xml、annotation等,那么这些bean在配置的时候也就是对bean进行定义,而这些定义映射到内存中的对象就是BeanDefinition的对象,Spring后续会根据BeanDefinition再获取具体的bean。

简单来说就是:配置 --> BeanDefinition --> Bean 这样一个逻辑

所以,后续我们会看到BeanDefinitionLoader会将主类加载成BeanDefinition,然后注册到ApplicationContext当中。

先跟进getBeanDefinitionRegistry方法,看看BeanDefinition会被注册到哪里去

private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
// 返回当前ApplicationContext
if (context instanceof BeanDefinitionRegistry) {
return (BeanDefinitionRegistry) context;
}
if (context instanceof AbstractApplicationContext) {
return (BeanDefinitionRegistry) ((AbstractApplicationContext) context).getBeanFactory();
}
throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}

我们注意,springboot的AnnotationConfigServletWebServerApplicationContext这个ApplicationContext的实现类,随着继承链路向上走是继承自GenericApplicationContext的,而GenericApplicationContext实现了BeanDefinitionRegistry。

所以,getBeanDefinitionRegistry将最终返回强转过的ApplicationContext。也就是说BeanDefinition将被注册到ApplicationContext里面。

回到load方法中,我们再跟进createBeanDefinitionLoader方法

protected BeanDefinitionLoader createBeanDefinitionLoader(BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}

再跟进构造方法

BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
this.sources = sources;
// 注解方式的读取器
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
// xml方式的读取器
this.xmlReader = new XmlBeanDefinitionReader(registry); // 类路径下的扫描器
this.scanner = new ClassPathBeanDefinitionScanner(registry);
// 扫描排除当前main方法的主类
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}

我们看到加载器支持注解、xml两种方式。类路径下的扫描器排除了当前的主类

回到load方法

protected void load(ApplicationContext context, Object[] sources) {
// 获取BeanDefinition加载器
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
// 加载资源
loader.load();
}

此时,我们已经获取了BeanDefinitionLoader,下面调用该loader的load方法开始加载

跟进第二个load方法

public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
}

再跟进第三个load方法

private int load(Object source) {
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
if (source instanceof Resource) {
return load((Resource) source);
}
if (source instanceof Package) {
return load((Package) source);
}
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}

由于我们的主类是一个class,所以进入第一个if分支的load方法

继续跟进

private int load(Class<?> source) {
// 省略
if (isComponent(source)) {
this.annotatedReader.register(source);
return 1;
}
return 0;
}

该方法先通过isComponent方法判断了主类是不是被@Component注解,如果是,那么调用注解方式的阅读器,注册该资源。

跟进isComponent方法,看看怎么判断的

private boolean isComponent(Class<?> type) {
// 找到是否匹配@Component注解
if (AnnotationUtils.findAnnotation(type, Component.class) != null) {
return true;
}
// 省略
}

其实就是找这个类是否有@Component注解,但请注意我们通常都使用@SpringBootApplication这个注解,并没有直接注解@Component。而@SpringBootApplication是一个组合注解,其中就组合了@Component

而AnnotationUtils.findAnnotation方法将会递归遍历注解,最终找到@Component。

isComponent判断为true以后,我们再跟进annotationReader.register(source)阅读一下读取主类的过程

public void register(Class<?>... annotatedClasses) {
for (Class<?> annotatedClass : annotatedClasses) {
registerBean(annotatedClass);
}
}

继续跟进registerBean方法

public void registerBean(Class<?> annotatedClass) {
doRegisterBean(annotatedClass, null, null, null);
}

再跟进doRegisterBean方法,该方法比较长,我们省略掉一些次要的部分

<T> void doRegisterBean(
Class<T> annotatedClass,
@Nullable Supplier<T> instanceSupplier,
@Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers,
BeanDefinitionCustomizer... definitionCustomizers
) {
// 先包装成BeanDefinition
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass); // 解析scope元数据
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName()); // 生成bean的名
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); // 解析一些常见的注解元数据
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 注册BeanDefinition到ApplicationContext
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

可以看到,doRegisterBean方法的主要逻辑就是包装并解析出一个BeanDefinition,然后调用registerBeanDefinition方法把BeanDefinition给注册到ApplicationContext中。

注册相关的本文就不再继续展开了,后续的文章会跟进这些内容。

总结

总的来说,prepareContext方法主要就是为了加载并注册主类的BeanDefinition到ApplicationContext。这里注意!我们一直都在说注册到ApplicationContext,但熟悉spring的都会知道无论是Bean还是BeanDefinition都是注册到BeanFactory中的。但我们一直没有严格区分它,后续的文章我们将会把ApplicationContext和BeanFactory进行区分。

springboot启动流程(六)ioc容器刷新前prepareContext的更多相关文章

  1. springboot启动流程(目录)

    springboot出现有段时间了,不过却一直没有怎么去更多地了解它.一方面是工作的原因,另一方面是原来觉得是否有这个必要,但要持续做java似乎最终逃不开要去了解它的命运.于是考虑花一段时间去学习一 ...

  2. SpringBoot启动流程分析(六):IoC容器依赖注入

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  3. SpringBoot启动流程分析(四):IoC容器的初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  4. springboot启动流程(七)ioc容器refresh过程(上篇)

    所有文章 https://www.cnblogs.com/lay2017/p/11478237.html 正文 在前面的几篇文章中,我们看到Environment创建.application配置文件的 ...

  5. SpringBoot启动流程分析(二):SpringApplication的run方法

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  6. SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  7. SpringBoot启动流程分析(五):SpringBoot自动装配原理实现

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  8. SpringBoot启动流程分析(一):SpringApplication类初始化过程

    SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...

  9. SpringBoot启动流程及其原理

    Spring Boot.Spring MVC 和 Spring 有什么区别? 分别描述各自的特征: Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等:但他们的 ...

随机推荐

  1. ISO/IEC 9899:2011 条款6.2.2——标识符的连接

    6.2.2 标识符的连接 1.在不同作用域中声明的一个标识符或在同一作用域多次出现的一个标识符可以被用作对同一个对象或函数的引用,通过一个称为连接的过程.[注:在两个不同的标识符之间没有连接.]有三种 ...

  2. WGS84 2 GCJ-02

    #include ; ) { x=-x; ff=; } cc=) ff=; ) ff=; } x=tt; ss=x; s2=x; tt=tt*tt; s2=s2*tt; ss=ss-s2* ) ss= ...

  3. IFC数据模型构件控制

    控制ifc构件的隐藏与显示.着色 osg::ref_ptr<osg::Geode> geode1 = new osg::Geode(); osg::ref_ptr<osg::Stat ...

  4. ireport如何拼接sql?

    ireport如何拼接sql   ireport如何拼接sql? 解决方法: 1.ireport的sql select * from emp as e $P!{whereSQL}; 2.java代码 ...

  5. 详解VMware 虚拟机中添加新硬盘的方法

    一.VMware新增磁盘的设置步骤 (建议:在设置虚拟的时候,不要运行虚拟机的系统,不然添加了新的虚拟磁盘则要重启虚拟机) 1.选择“VM”----“设置”并打开,将光标定位在“硬盘(SCSI)”这一 ...

  6. Block pool ID needed, but service not yet registered with NN java.lang.Exception: trace 异常解决

    以上为报错信息: 原因大概为:dd和nd关联的versionId不同导致, 解决方案,备份之前的current文件夹,让其自己生成新的.

  7. PAT 甲级 1041 Be Unique (20 分)(简单,一遍过)

    1041 Be Unique (20 分)   Being unique is so important to people on Mars that even their lottery is de ...

  8. EOF使用

    1.cat向文件覆盖内容 cat > local.repo << EOF [local]name=localbaseurl=file:///mnt/cdromgpgcheck=0en ...

  9. Linux下通过shell进MySQL执行SQL或导入脚本

    这条命令表示通过用户名和密码执行shell然后在shell里面执行一个建表语句: USER="root" PASS="root" mysql -u $USER ...

  10. C#RSA加密解密(对接PHP)

    上篇文章中写的RSA加密是针对C#的,现在外部调用的是PHP,我们平常见到的RSA无论公钥和私钥都是一长串数字,很显然C#生成的XML不是通用的加密.如果外部调用需要处理一下. 一.首先可以去网上找一 ...