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. 初始化狀態(tài)位
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.初始化狀態(tài)位
一上來就修改兩個成員變量,active 改為 true, closed 改為 false:
- 成員變量 active 為 true 表示當前 context 處于激活狀態(tài)
- 成員變量 closed 為 true 表示當前 context 已經被關閉
這里修改了狀態(tài),后續(xù)有兩個地方使用。
第一個地方是容器關閉的時候(避免重復關閉)
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
protected void doClose() {
// 當前是激活狀態(tài) && 還沒有被關閉
// 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 方法來檢測容器的狀態(tài)。AbstractApplicationContext 中有二三十個地方使用了該方法。
比如最常見的各種重載的 AbstractApplicationContext.getBean(java.lang.String) 方法都會在將方法調用委托給 getBeanFactory().getBean(name, args); 之前調用 assertBeanFactoryActive() 來檢測容器狀態(tài);畢竟在一個已經關閉了的容器上 getBean() 是不正常的吧。
2.initPropertySources
這個方法主要是留給子類用來將 StubPropertySource(占位符) 替換為真實的 PropertySource。
比如在 servlet 環(huán)境下,會將 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 {
/**
* 重寫 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 中了。
所謂的驗證,邏輯也很簡單:所有指定的配置項名稱都遍歷一遍,如果發(fā)現 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(多播器) 發(fā)布的。 但現在是在 prepareRefresh 階段,多播器 實例還沒有初始化呢。 這時候要是有事件的話,就只能先將這種 "早期"事件保存下來,等到多播器初始化好之后再回過頭來發(fā)布這種"早期"事件。
處理早期事件 這一步所作的事情就是 初始化 用來 臨時 保存 "早期" 事件的兩個集合:
earlyApplicationEvents: 早期事件earlyApplicationListeners: 早期事件監(jiān)聽器
等到后續(xù)的 initApplicationEventMulticaster() 之后會回過頭來遍歷 earlyApplicationEvents 發(fā)布事件。
詳細內容會在 步驟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. 初始化狀態(tài)位
// ...
// 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創(chuàng)建二維碼并賦予url鏈接的功能實現
這篇文章給大家分享java創(chuàng)建二維碼并賦予url鏈接的功能實現,需要獲取要賦值給二維碼的鏈接后綴,通過設置二維碼的訪問路徑等一系列操作,具體實現代碼跟隨小編一起看看吧2021-06-06

