1. public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
  2. this();
  3. register(componentClasses);
  4. // 3.刷新整个容器
  5. refresh();
  6. }

一. refresh()方法整体概览

构造方法中, 前面两步已经做了很多事情了, 但这些事情都是准备工作. 比起refresh()而言, 还是小巫见大巫. 在refresh()方法中,包含着各后置处理的作用 / 调用时机; Bean的诞生过程; 循环依赖如何解决以及Spring的其他组件使用等. 接下来迎接风暴!

  1. // refresh()本身是一个聚合方法, 具体的功能都委托给了各个子方法.
  2. public void refresh() throws BeansException, IllegalStateException {
  3. synchronized (this.startupShutdownMonitor) {
  4. // 1.在刷新容器之前,做一些基础的处理. 不难
  5. prepareRefresh();
  6. // 2. 获取刷新后的beanFactory.注意:这里并不是要把当前的beanFactory全部刷新,只是刷新部分数据.不然之前的准备工作岂不是白做了. 不难
  7. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  8. // 3. 准备BeanFactory. 重要!!!
  9. prepareBeanFactory(beanFactory);
  10. try {
  11. // 4. 空方法 Spring后版本可能会用
  12. postProcessBeanFactory(beanFactory);
  13. // 5. 调用后置处理器 重要!!!
  14. invokeBeanFactoryPostProcessors(beanFactory);
  15. // 6. 注册后置处理的Bean到容器当中 重要!!!
  16. registerBeanPostProcessors(beanFactory);
  17. // 7. 初始化MessageSource并将其bean注入到容器中
  18. initMessageSource();
  19. // 8. 初始化事件多播器并将其bean注入到容器中
  20. initApplicationEventMulticaster();
  21. // 9. 在特定上下文子类中初始化其他特殊bean. 当前是空方法, Spring后续版本可能会用
  22. onRefresh();
  23. // 10. 注册监听器到容器中
  24. registerListeners();
  25. // 11. 实例化所有剩余的bean(非延迟)到容器中 重要!!!
  26. finishBeanFactoryInitialization(beanFactory);
  27. // 12. 最后, 发布容器刷新完成事件
  28. finishRefresh();
  29. }
  30. catch (BeansException ex) {
  31. // 异常情况下, 销毁容器中所有的bean
  32. destroyBeans();
  33. cancelRefresh(ex);
  34. throw ex;
  35. }
  36. finally {
  37. // 清除一些不必要的缓存
  38. resetCommonCaches();
  39. }
  40. }
  41. }

二、prepareRefresh()详解

  1. protected void prepareRefresh() {
  2. // 这一句很明显的获取了系统当前时间,其实他的作用是来记录当前的启动时间的
  3. this.startupDate = System.currentTimeMillis();
  4. // 这两个状态的设置,前者关闭程序设置为false,后者运行标识设置为true
  5. this.closed.set(false);
  6. this.active.set(true);
  7. // 初始化资源必要的资源. 目前是空方法. Spring后续版本可能会启用.
  8. initPropertySources();
  9. // 校验系统环境/JVM环境当中的必要参数
  10. getEnvironment().validateRequiredProperties();
  11. // 判断刷新前的运用程序监听集合是否为空,为空初始化applicationListeners监听,不为空,则清空监听器
  12. if (this.earlyApplicationListeners == null) {
  13. // 这里会保存pre-refresh状态下的listeners,即使此后再调用刷新,这些listeners会包含在所有的
  14. // listeners集合中。这么做的目的应该是用户在调用刷新前手动注册了一些liteners,而这些liteners
  15. // 并不是bean无法自动发现,这里只会设置一次值
  16. this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
  17. }
  18. else {
  19. // 再次 fresh,把之前添加的监听器添加到 applicationListeners
  20. this.applicationListeners.clear();
  21. this.applicationListeners.addAll(this.earlyApplicationListeners);
  22. }
  23. // 允许收集早期的ApplicationEvent,一旦多播器可用,便会发布. 实际上这里也是一个empty set
  24. this.earlyApplicationEvents = new LinkedHashSet<>();
  25. }
  1. 这段代码前面的是记录当前的启动时间以及设置运行标识为true状态。
  2. 然后执行initPropertySources()方法,该方法Spring源码是没有实现的,在Spring的源码中一般没有实现的方法大部分就是Spring预留出来给用户进行高级扩展的能力,用户可以根据自身的需要重写 initPropertySources()方法,并在方法中进行个性化的属性处理及设置。
  3. 紧接着是执行对属性进行验证的方法getEnvironment().validateRequiredProperties(),其验证的逻辑是交给AbstractPropertyResolver# validateRequiredProperties()方法完成的,该方法主要是校验的是this.requiredProperties属性。这个属性初始化的时候是空的,所有看起来就像是什么都没有校验一样。但是如果用户扩展实现了initPropertySources()方法,并且设置了系统属性,那么在初始化的时候就this.requiredProperties属性就不为空,就需要执行校验逻辑。

假如现在有这样一个需求,工程在运行过程中用到的某个设置(例如 VAR )是从系统环境变量中取得的,而如果用户没有在系统环境变量中配置这个参数,那么工程可能不会工作。这一要求可能会有各种各样的解决办法,当然,在 Spring 中可以这样做,你可以直接修改 Spring 的源码,例如修改 ClassPathXmlApplicationContext。当然,最好的办法还是对源码进行扩展,我们可以自定义类:

  1. public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext {
  2. public MyClassPathXmlApplicationContext(String... configLocations) throws BeansException {
  3. super(configLocations);
  4. }
  5. @Override
  6. protected void initPropertySources() {
  7. // 添加验证要求
  8. getEnvironment().setRequiredProperties("VAR");
  9. }
  10. }

我们自定一义了继承自 ClassPathXmlApplicationContext 的 MyClassPathXmlApplicationContext , 并重写了initPropertySources方法,在方法中添加了我们的个性化需求,那么在验证的时候也就是程序走到 getEnvironment().validateRequiredProperties()代码的时候,如果系统并没有检测到对应 VAR 的环境变量,那么将抛出异常。当然我们还需要在使用的时候替换掉原有的ClassPathXmlApplicationContext:

  1. public static void main(String[] args) {
  2. ApplicationContext context = new MyClassPathXmlApplicationContext("spring/lookup-test.xml");
  3. GetBeanTest getBeanTest = (GetBeanTest) context.getBean("getBeanTest");
  4. getBeanTest.showMe();
  5. }

https://www.cnblogs.com/warehouse/p/9384735.html 这是一个优质的博主,可以看看他的源码解析的内容。

三、关于this.earlyApplicationListeners的思考

  1. // 判断刷新前的运用程序监听集合是否为空,为空初始化applicationListeners监听,不为空,则清空监听器
  2. if (this.earlyApplicationListeners == null) {
  3. // 这里会保存pre-refresh状态下的listeners,即使此后再调用刷新,这些listeners会包含在所有的
  4. // listeners集合中。这么做的目的应该是用户在调用刷新前手动注册了一些liteners,而这些liteners
  5. // 并不是bean无法自动发现,这里只会设置一次值
  6. this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
  7. }
  8. else {
  9. // 再次 fresh,把之前添加的监听器添加到 applicationListeners
  10. this.applicationListeners.clear();
  11. this.applicationListeners.addAll(this.earlyApplicationListeners);
  12. }

第一次在看这个代码进行调试的时候,看到的都是this.earlyApplicationListeners都是为null的。感觉既然都是null,那Spring为啥需要判断是否为null。而且还有后面else的逻辑,什么情况下this.earlyApplicationListeners 不等于 null呢?

  1. 可以通过addApplicationListener(...)ApplicationContext添加监听器,此时添加的监听器保存在applicationListeners属性中
  2. 假设在刷新过程中或者刷新后调用了addApplicationListener(...),那么再次刷新时,applicationListeners就包含了这些监听器。这里通过earlyApplicationListeners解决了此问题。
  3. 在刷新之前,添加的所有监听器保存在applicationListeners,而earlyApplicationListenersnull,当第一次refresh的时候将applicationListeners保在earlyApplicationListeners中,如果重新刷新,那么用earlyApplicationListeners覆盖applicationListerners
  4. 这样就确保了第一次刷新之前通过addApplicationListener(...)添加了哪些监听器,无论之后添加了多少监听器,再次refresh都不起作用
    1. public static void main(String[] args) {
    2. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    3. // pre-refresh状态下的listeners监听器。此监听器保存在applicationListeners属性中
    4. context.addApplicationListener(new DemoMyListener());
    5. // 注册配置类
    6. context.register(AppConfig.class);
    7. // 刷新上下文
    8. context.refresh();
    9. // 刷新上下文
    10. context.refresh();
    11. }
    04-Spring的refresh-prepareRefresh() - 图1
    https://bbs.huaweicloud.com/blogs/281015