Spring容器刷新prepareRefresh第一步
关键源码
这次的内容是上图中的第1步,容器刷新前的准备工作。基本上都是一些初始化动作。
下面是这部分的涉及到的源码中的关键部分:
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
private long startupDate;
private final AtomicBoolean active = new AtomicBoolean();
private final AtomicBoolean closed = new AtomicBoolean();
@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:
- 成员变量 active 为 true 表示当前 context 处于激活状态
- 成员变量 closed 为 true 表示当前 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 环境下,会将 ServletContextPropertySource
和 ServletConfigPropertySource
加入(替换 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 {
@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 {
@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<>();
@Nullable
private Set<ApplicationListener<?>> earlyApplicationListeners;
@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容器刷新的资料请关注其它相关文章!
相关文章