Spring容器刷新prepareRefresh第一步

 更新时间:2023年03月19日 15:11:00   作者:今年三岁半  
这篇文章主要为大家介绍了Spring容器刷新prepareRefresh第一步示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

关键源码

这次的内容是上图中的第1步,容器刷新前的准备工作。基本上都是一些初始化动作。

下面是这部分的涉及到的源码中的关键部分:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    private long startupDate;
    /** Flag that indicates whether this context is currently active. */
    private final AtomicBoolean active = new AtomicBoolean();
    /** Flag that indicates whether this context has been closed already. */
    private final AtomicBoolean closed = new AtomicBoolean();
    /** Environment used by this context. */
    @Nullable
    private ConfigurableEnvironment environment;
    protected void prepareRefresh() {
        // Switch to active.
        this.startupDate = System.currentTimeMillis();
        // 1. 初始化状态位
        this.closed.set(false);
        this.active.set(true);
        if (logger.isDebugEnabled()) {
            if (logger.isTraceEnabled()) {
                logger.trace("Refreshing " + this);
            } else {
                logger.debug("Refreshing " + getDisplayName());
            }
        }
        // 2. 留给子类的扩展方法
        // Initialize any placeholder property sources in the context environment.
        initPropertySources();
        // 3. 验证必须的配置项是否存在
        // Validate that all properties marked as required are resolvable:
        // see ConfigurablePropertyResolver#setRequiredProperties
        getEnvironment().validateRequiredProperties();
        // 4. 处理早期事件
        // Store pre-refresh ApplicationListeners...
        if (this.earlyApplicationListeners == null) {
            this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
        } else {
            // Reset local application listeners to pre-refresh state.
            this.applicationListeners.clear();
            this.applicationListeners.addAll(this.earlyApplicationListeners);
        }
        // Allow for the collection of early ApplicationEvents,
        // to be published once the multicaster is available...
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }
}

1.初始化状态位

一上来就修改两个成员变量,active 改为 true, closed 改为 false

  • 成员变量 activetrue 表示当前 context 处于激活状态
  • 成员变量 closedtrue 表示当前 context 已经被关闭

这里修改了状态,后续有两个地方使用。

第一个地方是容器关闭的时候(避免重复关闭)

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    protected void doClose() {
        // 当前是激活状态 && 还没有被关闭
        // Check whether an actual close attempt is necessary...
        if (this.active.get() && this.closed.compareAndSet(false, true)) {
            // 这里省略 N 行代码
            // 这里省略 N 行代码
            // Switch to inactive.
            this.active.set(false);
        }
    }
}

第二个地方是和 BeanFactory 交互的时候作断言用的

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    protected void assertBeanFactoryActive() {
        if (!this.active.get()) {
            if (this.closed.get()) {
                throw new IllegalStateException(getDisplayName() + " has been closed already");
            } else {
                throw new IllegalStateException(getDisplayName() + " has not been refreshed yet");
            }
        }
    }
}

几乎所有和 BeanFactory 交互的方法都需要调用 assertBeanFactoryActive 方法来检测容器的状态。AbstractApplicationContext 中有二三十个地方使用了该方法。

比如最常见的各种重载的 AbstractApplicationContext.getBean(java.lang.String) 方法都会在将方法调用委托给 getBeanFactory().getBean(name, args); 之前调用 assertBeanFactoryActive() 来检测容器状态;毕竟在一个已经关闭了的容器上 getBean() 是不正常的吧。

2.initPropertySources

这个方法主要是留给子类用来将 StubPropertySource(占位符) 替换为真实的 PropertySource

比如在 servlet 环境下,会将 ServletContextPropertySourceServletConfigPropertySource 加入(替换 Stub)到 Environment 中。

public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext
        implements ConfigurableWebApplicationContext, ThemeSource {
    @Override
    protected void initPropertySources() {
        ConfigurableEnvironment env = getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            // 这里实际上是调用了 WebApplicationContextUtils#initServletPropertySources
            ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, this.servletConfig);
        }
    }
}
public abstract class WebApplicationContextUtils {
    public static void initServletPropertySources(MutablePropertySources sources,
                                                  @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
        Assert.notNull(sources, "'propertySources' must not be null");
        String name = StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;
        // servletContextInitParams
        if (servletContext != null && sources.get(name) instanceof StubPropertySource) {
            sources.replace(name, new ServletContextPropertySource(name, servletContext));
        }
        name = StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;
        // servletConfigInitParams
        if (servletConfig != null && sources.get(name) instanceof StubPropertySource) {
            sources.replace(name, new ServletConfigPropertySource(name, servletConfig));
        }
    }
}

当然,你可以在这里直接 修改/替换 Environment 中的任何 PropertySource

也就是说,可以在这里做类似于 spring-boot 中提供的 EnvironmentPostProcessor 能做的事情。

如果是 spring-boot 项目的话,还是推荐直接使用 EnvironmentPostProcessor。 而不是像下面这样再搞一个 ApplicationContext 的实现类。

public class PrepareRefreshTest {
    /**
     * 重写 initPropertySources(),给 Environment 中新增两个自定义的配置项 "osName" 和 "a.b.c.d"
     */
    @Test
    void initPropertySourcesTest() {
        final ApplicationContext applicationContext = new AnnotationConfigApplicationContext(PrepareRefreshTest.class) {
            @Override
            protected void initPropertySources() {
                super.initPropertySources();
                final ConfigurableEnvironment environment = getEnvironment();
                final Map<String, Object> config = new HashMap<>();
                config.put("osName", System.getProperty("os.name", "UNKNOWN"));
                config.put("a.b.c.d", "haha");
                environment.getPropertySources().addFirst(new MapPropertySource("demo-property-source", config));
            }
        };
        final Environment environment = applicationContext.getEnvironment();
        Assertions.assertEquals(System.getProperty("os.name"), environment.getProperty("osName"));
        Assertions.assertEquals("haha", environment.getProperty("a.b.c.d"));
    }
}

3.validateRequiredProperties

这里主要是验证 ConfigurablePropertyResolver.setRequiredProperties(String... requiredProperties) 方法中指定的那些 必须出现的配置项 是不是都已经在 Environment 中了。

所谓的验证,逻辑也很简单:所有指定的配置项名称都遍历一遍,如果发现 Environment 中获取不到对应的配置项就直接抛出 MissingRequiredPropertiesException

public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
    @Override
    public void validateRequiredProperties() {
        MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
        for (String key : this.requiredProperties) {
            if (this.getProperty(key) == null) {
                ex.addMissingRequiredProperty(key);
            }
        }
        if (!ex.getMissingRequiredProperties().isEmpty()) {
            throw ex;
        }
    }
}

下面这段代码是验证 validateRequiredProperties() 方法的(同样的功能,可以使用 spring-boot 提供的 EnvironmentPostProcessor 来完成)。

public class PrepareRefreshTest {
    /**
     * 验证 getEnvironment().validateRequiredProperties(); 的功能
     * <p>
     * 抛出 MissingRequiredPropertiesException 异常(Environment 中缺少必须出现的配置项"jdbc.url")
     */
    @Test
    void validateRequiredPropertiesTest() {
        Assertions.assertThrows(MissingRequiredPropertiesException.class, () -> {
                    final ApplicationContext applicationContext = new AnnotationConfigApplicationContext(PrepareRefreshTest.class) {
                        @Override
                        protected void initPropertySources() {
                            super.initPropertySources();
                            // 这里指定 Environment 中必须要有一个名为 "jdbc.url" 的配置项
                            // 如果 Environment 中没有名为 "jdbc.url" 的配置项, 就会在 validateRequiredProperties() 方法中抛出 MissingRequiredPropertiesException
                            getEnvironment().setRequiredProperties("jdbc.url");
                        }
                    };
                }
        );
    }
}

4.处理早期事件

什么叫做早期(early)事件?

spring 中的事件最终是委托给 ApplicationEventMulticaster(多播器) 发布的。 但现在是在 prepareRefresh 阶段,多播器 实例还没有初始化呢。 这时候要是有事件的话,就只能先将这种 "早期"事件保存下来,等到多播器初始化好之后再回过头来发布这种"早期"事件。

处理早期事件 这一步所作的事情就是 初始化 用来 临时 保存 "早期" 事件的两个集合:

  • earlyApplicationEvents: 早期事件
  • earlyApplicationListeners: 早期事件监听器

等到后续的 initApplicationEventMulticaster() 之后会回过头来遍历 earlyApplicationEvents 发布事件。

详细内容会在 步骤8-initApplicationEventMulticaster()步骤10-registerListeners() 相关的文章中介绍。这里只介绍和 prepareRefresh 相关的内容。

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
    /** Local listeners registered before refresh. */
    @Nullable
    private Set<ApplicationListener<?>> earlyApplicationListeners;
    /** ApplicationEvents published before the multicaster setup. */
    @Nullable
    private Set<ApplicationEvent> earlyApplicationEvents;
    protected void prepareRefresh() {
        // Switch to active.
        // 1. 初始化状态位
        // ...
        // 2. 留给子类的扩展方法
        // ...
        // 3. 验证必须的配置项是否存在
        // ...
        // 4. 处理早期事件
        // Store pre-refresh ApplicationListeners...
        if (this.earlyApplicationListeners == null) {
            this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
        } else {
            // Reset local application listeners to pre-refresh state.
            this.applicationListeners.clear();
            this.applicationListeners.addAll(this.earlyApplicationListeners);
        }
        // Allow for the collection of early ApplicationEvents,
        // to be published once the multicaster is available...
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }
}

以上就是Spring容器刷新prepareRefresh第一步的详细内容,更多关于Spring容器刷新的资料请关注脚本之家其它相关文章!

相关文章

  • java创建二维码并赋予url链接的功能实现

    java创建二维码并赋予url链接的功能实现

    这篇文章给大家分享java创建二维码并赋予url链接的功能实现,需要获取要赋值给二维码的链接后缀,通过设置二维码的访问路径等一系列操作,具体实现代码跟随小编一起看看吧
    2021-06-06
  • Java8新特性之Optional使用详解

    Java8新特性之Optional使用详解

    这篇文章主要介绍了Java8新特性之Optional使用详解,为了解决空指针异常更加优雅,Java8 提供了 Optional 类库,Optional 实际上是个容器,它可以保存类型T的值,或者仅仅保存null,,需要的朋友可以参考下
    2023-08-08
  • IDEA自动清理类中未使用的import包的操作方法

    IDEA自动清理类中未使用的import包的操作方法

    在项目开发中,经常会引入很多未使用的import包,这不仅增加了编译时间,还会使代码可读性变差,设置IDEA自动清理未使用的import包,可以提高代码的可读性,本文给大家介绍IDEA自动清理类中未使用的import包的方法,感兴趣的朋友一起看看吧
    2024-09-09
  • Windows10安装IDEA 2020.1.2的方法步骤

    Windows10安装IDEA 2020.1.2的方法步骤

    这篇文章主要介绍了Windows10安装IDEA 2020.1.2的方法步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • Java设计模式中的外观模式详解

    Java设计模式中的外观模式详解

    这篇文章主要介绍了Java设计模式中的外观模式详解,外观模式也叫“过程模式,外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
    2022-07-07
  • Java System类用法实战案例

    Java System类用法实战案例

    这篇文章主要介绍了Java System类用法,结合具体实例形式分析了java使用System类获取系统环境变量信息相关操作技巧,需要的朋友可以参考下
    2019-07-07
  • 详解SpringMVC中的异常处理

    详解SpringMVC中的异常处理

    这篇文章主要介绍了SpringMVC中的异常处理的相关资料,帮助大家更好的理解和学习使用SpringMVC,感兴趣的朋友可以了解下
    2021-03-03
  • 在java List中进行模糊查询的实现方法

    在java List中进行模糊查询的实现方法

    下面小编就为大家带来一篇在java List中进行模糊查询的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-11-11
  • java基于poi导出excel透视表代码实例

    java基于poi导出excel透视表代码实例

    这篇文章主要介绍了java基于poi导出excel透视表代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Java判断本机IP地址类型的方法

    Java判断本机IP地址类型的方法

    Java判断本机IP地址类型的方法,需要的朋友可以参考一下
    2013-03-03

最新评论