Spring 原始碼解析二:上下文元件(WebApplicationContext)

senntyou發表於2021-10-27

Spring 原始碼解析二:上下文元件(WebApplicationContext)

上一篇解析了 DispatcherServletContextLoaderListener 這兩個類,解析了應用初始化與請求處理的流程,但還有一些元件需要解析:

  • ConfigurableWebApplicationContext.refresh 重新整理上下文
  • ApplicationContext.getBean 從上下文中獲取 bean
  • DispatcherServlet.properties 檔案中定義的策略處理
  • ContextLoader.properties 檔案中定義的策略處理
  • View.render 檢視渲染

這一章來看看 ContextLoader.properties 檔案中定義的策略處理

ContextLoader.properties 檔案中只定義了一個策略

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

預設使用 XmlWebApplicationContext(基於 XML 載入)作為應用上下文

spring-web 內部定義 5 個應用上下文類:

  • GenericWebApplicationContext
    WebApplicationContext的基礎實現,但不能通過配置檔案和註解載入應用配置與 bean,一般用於擴充套件實現(如 SpringBoot),很少直接使用
  • StaticWebApplicationContext
    :也是WebApplicationContext的基礎實現,但不支援 i18n,主要用於測試,不用產品環境
  • XmlWebApplicationContext
    :基於 XML 載入應用配置與 bean 的WebApplicationContext實現,是 SpringMVC 的預設 Context
  • AnnotationConfigWebApplicationContext
    :基於註解如 @Configuration, @bean 等載入應用配置與 bean 的WebApplicationContext實現
  • GroovyWebApplicationContext
    :與XmlWebApplicationContext的實現差不多,但可以用 Groovy 代替 xml 做配置檔案,目前用得不多

先來看看這 5 個應用上下文類各自的繼承關係

- DefaultResourceLoader
  - AbstractApplicationContext
    - GenericApplicationContext
      - GenericWebApplicationContext

- DefaultResourceLoader
  - AbstractApplicationContext
    - GenericApplicationContext
      - StaticApplicationContext
        - StaticWebApplicationContext

- DefaultResourceLoader
  - AbstractApplicationContext
    - AbstractRefreshableApplicationContext
      - AbstractRefreshableConfigApplicationContext
        - AbstractRefreshableWebApplicationContext
          - XmlWebApplicationContext

- DefaultResourceLoader
  - AbstractApplicationContext
    - AbstractRefreshableApplicationContext
      - AbstractRefreshableConfigApplicationContext
        - AbstractRefreshableWebApplicationContext
          - AnnotationConfigWebApplicationContext

- DefaultResourceLoader
  - AbstractApplicationContext
    - AbstractRefreshableApplicationContext
      - AbstractRefreshableConfigApplicationContext
        - AbstractRefreshableWebApplicationContext
          - GroovyWebApplicationContext

我們可以發現每個類都繼承 AbstractApplicationContext,而 XmlWebApplicationContext, AnnotationConfigWebApplicationContext,
GroovyWebApplicationContext 都繼承 AbstractRefreshableWebApplicationContext

1. DefaultResourceLoader

DefaultResourceLoader
的主要功能是實現資源載入

public class DefaultResourceLoader implements ResourceLoader {}

先來看看介面ResourceLoader

public interface ResourceLoader {
    // 根據一個字元位置資訊獲取資源
    Resource getResource(String location);

    // 獲取資源載入器
    ClassLoader getClassLoader();
}

DefaultResourceLoader
ResourceLoader 的預設實現

public class DefaultResourceLoader implements ResourceLoader {
    @Override
    public ClassLoader getClassLoader() {
        // 如果有指定的classLoader,則返回指定的,沒有則返回預設的
        return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
    }

    @Override
    public Resource getResource(String location) {
        // 自定義協議解析
        for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
            Resource resource = protocolResolver.resolve(location, this);
            if (resource != null) {
                return resource;
            }
        }

        // 如果以/開頭,則認為是classpath資源
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        // 如果以classpath:開頭的classpath資源
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
            try {
                // 嘗試以檔案或url對待
                URL url = new URL(location);
                return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
            }
            catch (MalformedURLException ex) {
                // 失敗則預設是classpath資源
                return getResourceByPath(location);
            }
        }
    }
}

2. AbstractApplicationContext

AbstractApplicationContext
的主要功能是通過名字、型別或註解獲取 bean 例項,獲取上下文的環境物件與資源、重新整理上下文資料

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {}

介面 ConfigurableApplicationContext
及其繼承的介面主要定義以下的方法

public interface ConfigurableApplicationContext {
    // 獲取bean
    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    Object getBean(String name, Object... args) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

    // 通過型別或註解獲取bean
    <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException;
    <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
            throws BeansException;
    Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;

    // 獲取環境
     ConfigurableEnvironment getEnvironment();

    // 重新整理上下文資料
    void refresh() throws BeansException, IllegalStateException;

    // 根據locationPattern獲取多個資源,如萬用字元*
    Resource[] getResources(String locationPattern) throws IOException;
}

2.1. AbstractApplicationContext.getEnvironment

AbstractApplicationContext.getEnvironment
獲取環境

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    @Override
    public ConfigurableEnvironment getEnvironment() {
        if (this.environment == null) {
            this.environment = createEnvironment();
        }
        return this.environment;
    }

    protected ConfigurableEnvironment createEnvironment() {
        // 內建的標準環境(也可以通過setEnvironment方法自定義環境處理機制)
        // 這是可以使用 `application-dev.yml, application-test.yml, application-prod.yml, ...` 來根據環境載入不同的配置的底層實現
        // 是spring-boot的基本功能
        return new StandardEnvironment();
    }
}

2.2. AbstractApplicationContext.getBean

AbstractApplicationContext.getBean
獲取 bean

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    @Override
    public Object getBean(String name) throws BeansException {
        return getBeanFactory().getBean(name);
    }

    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return getBeanFactory().getBean(name, requiredType);
    }

    @Override
    public Object getBean(String name, Object... args) throws BeansException {
        return getBeanFactory().getBean(name, args);
    }

    @Override
    public <T> T getBean(Class<T> requiredType) throws BeansException {
        return getBeanFactory().getBean(requiredType);
    }

    @Override
    public <T> T getBean(Class<T> requiredType, Object... args) throws BeansException {
        return getBeanFactory().getBean(requiredType, args);
    }

    // 留給子類實現
    public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}

因為不同的Context註冊 bean 的方式不一樣,所以getBeanFactory留給子類來實現

2.3. AbstractApplicationContext.getBeansOfType

AbstractApplicationContext.getBeansOfType
通過型別或註解獲取 bean

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    @Override
    public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException {
        return getBeanFactory().getBeansOfType(type);
    }

    @Override
    public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
            throws BeansException {
        return getBeanFactory().getBeansOfType(type, includeNonSingletons, allowEagerInit);
    }

    @Override
    public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType)
            throws BeansException {
        return getBeanFactory().getBeansWithAnnotation(annotationType);
    }

    // 留給子類實現
    public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}

2.4. AbstractApplicationContext.refresh

AbstractApplicationContext.refresh
重新整理上下文資料

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // ... 程式碼省略

            // 初始化事件容器與監聽器,檢查必須的屬性配置,並載入必要的例項
            prepareRefresh();

            // 重新整理上下文的bean,獲取bean工廠
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // 預備bean工廠
            prepareBeanFactory(beanFactory);

            try {
                // 後置處理bean工廠
                postProcessBeanFactory(beanFactory);

                // ... 程式碼省略

                // 呼叫bean工廠的後置處理器,以使在所有bean例項化之前,可以自定義新增自己的BeanPostProcessor(bean例項化後置操作)
                invokeBeanFactoryPostProcessors(beanFactory);

                // 給bean工廠註冊BeanPostProcessor(bean例項化後置操作)
                registerBeanPostProcessors(beanFactory);

                // ... 程式碼省略

                // 例項化applicationEventMulticaster bean,作為應用事件廣播器
                initApplicationEventMulticaster();

                // 擴充套件實現,留給開發者,預設不實現
                onRefresh();

                // 註冊應用事件監聽器
                registerListeners();

                // 初始化所有單例的bean
                finishBeanFactoryInitialization(beanFactory);

                // 重新整理上下文資料完成,做一些後續處理
                finishRefresh();
            }

            catch (BeansException ex) {
                // ... 程式碼省略
            }

            finally {
                // ... 程式碼省略
            }
        }
    }

    // 重新整理上下文的bean,獲取bean工廠
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        // 重新整理上下文的bean
        refreshBeanFactory();
        // 獲取bean工廠
        return getBeanFactory();
    }

    // 重新整理上下文的bean,由子類實現
    protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

    // 獲取bean工廠,由子類實現
    public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

    // 擴充套件實現,留給開發者,預設不實現
    protected void onRefresh() throws BeansException {}

    // 初始化所有單例的bean
    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        // ... 程式碼省略

        // 固化所有bean的配置,後面不再更改
        beanFactory.freezeConfiguration();

        // 初始化所有單例的bean
        beanFactory.preInstantiateSingletons();
    }

    // 重新整理上下文資料完成,做一些後續處理
    protected void finishRefresh() {
        // 清除一些資源快取
        clearResourceCaches();

        // 例項化lifecycleProcessor bean
        initLifecycleProcessor();

        // 例項化Lifecycle bean,並呼叫這些bean的start方法
        getLifecycleProcessor().onRefresh();

        // 派發事件
        publishEvent(new ContextRefreshedEvent(this));

        // ... 程式碼省略
    }
}
  • 因為不同的Context註冊 bean 的方式不一樣,所以refreshBeanFactory, postProcessBeanFactory留給子類來實現
  • ConfigurableListableBeanFactory如何載入、例項化 bean,後面再解析

2.5. AbstractApplicationContext.prepareBeanFactory

AbstractApplicationContext.prepareBeanFactory
預備 bean 工廠

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        // ... 程式碼省略

        // 新增對 #{} SpEL Spring 表示式語言的支援
        if (!shouldIgnoreSpel) {
            beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
        }

        // 新增屬性編輯器,xml、yaml 中定義的值轉換成物件就是依賴這裡實現的
        beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

        // 新增一個BeanPostProcessor,後置處理器
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));

        // ... 程式碼省略

        // 註冊幾個可以autowirable自動載入的例項
        beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
        beanFactory.registerResolvableDependency(ResourceLoader.class, this);
        beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
        beanFactory.registerResolvableDependency(ApplicationContext.class, this);

        // ... 程式碼省略

        // 註冊幾個單例bean
        if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
        }
        if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
            beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
        }
        if (!beanFactory.containsLocalBean(APPLICATION_STARTUP_BEAN_NAME)) {
            beanFactory.registerSingleton(APPLICATION_STARTUP_BEAN_NAME, getApplicationStartup());
        }
    }
}
  • ResourceEditorRegistrar如何註冊屬性編輯器、屬性編輯器如何解析為物件,後面再解析

2.6. AbstractApplicationContext.getResources

AbstractApplicationContext.getResources
根據 locationPattern 獲取多個資源,如萬用字元*

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext {
    private ResourcePatternResolver resourcePatternResolver;

    public AbstractApplicationContext() {
        this.resourcePatternResolver = getResourcePatternResolver();
    }

    protected ResourcePatternResolver getResourcePatternResolver() {
        return new PathMatchingResourcePatternResolver(this);
    }

    @Override
    public Resource[] getResources(String locationPattern) throws IOException {
        return this.resourcePatternResolver.getResources(locationPattern);
    }
}
  • PathMatchingResourcePatternResolver如何解析、載入 locationPattern 指定的資源,後面再解析

2.7. 綜述

總的來說,AbstractApplicationContext 類完成上下文環境的大部分功能,包括環境載入、bean 的載入與前置後置處理、事件派發、完成一些初始化工作等,
但擴充套件了幾個介面給子類實現,如如何載入、註冊、例項化 bean 等

3. GenericApplicationContext

GenericApplicationContext
的主要功能是註冊、管理 bean 的定義與別名

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {}

BeanDefinitionRegistry
這個介面主要定義了註冊 bean 的定義及別名

public interface BeanDefinitionRegistry {
    // 註冊bean定義
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException;

    // 刪除bean定義
    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    // 獲取bean定義
    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    // 檢查bean定義
    boolean containsBeanDefinition(String beanName);

    // 註冊bean別名
    void registerAlias(String name, String alias);

    // 刪除bean別名
    void removeAlias(String alias);

    // 檢查bean別名
    boolean isAlias(String name);

    // 獲取bean別名
    String[] getAliases(String name);
}

來看看 GenericApplicationContext 如何實現這些介面的

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

        this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
    }

    @Override
    public void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
        this.beanFactory.removeBeanDefinition(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
        return this.beanFactory.getBeanDefinition(beanName);
    }

    @Override
    public void registerAlias(String beanName, String alias) {
        this.beanFactory.registerAlias(beanName, alias);
    }

    @Override
    public void removeAlias(String alias) {
        this.beanFactory.removeAlias(alias);
    }

    @Override
    public boolean isAlias(String beanName) {
        return this.beanFactory.isAlias(beanName);
    }
}

最終還是落腳在 beanFactory

4. GenericWebApplicationContext

GenericWebApplicationContext
的主要功能是新增了設定 bean 配置檔案來源,允許通過配置的方式例項化上下文環境

public class GenericWebApplicationContext extends GenericApplicationContext
        implements ConfigurableWebApplicationContext, ThemeSource {}

ConfigurableWebApplicationContext

public interface ConfigurableWebApplicationContext {
    // 設定bean配置檔案來源
    void setConfigLocation(String configLocation);
    // 設定多個bean配置檔案來源
    void setConfigLocations(String... configLocations);
    // 獲取bean配置檔案來源
    String[] getConfigLocations();
}

ConfigurableWebApplicationContext擴充套件了WebApplicationContext,定義了允許通過配置的方式例項化上下文環境

public class GenericWebApplicationContext extends GenericApplicationContext
        implements ConfigurableWebApplicationContext, ThemeSource {
    @Override
    protected ConfigurableEnvironment createEnvironment() {
        // StandardServletEnvironment擴充套件了StandardEnvironment
        // 增加了可以從Servlet context init parameters和Servlet config init parameters增加應用配置來源
        return new StandardServletEnvironment();
    }

    // 不可設定bean配置檔案來源
    @Override
    public void setConfigLocation(String configLocation) {
        if (StringUtils.hasText(configLocation)) {
            throw new UnsupportedOperationException(
                    "GenericWebApplicationContext does not support setConfigLocation(). " +
                    "Do you still have an 'contextConfigLocations' init-param set?");
        }
    }

    @Override
    public void setConfigLocations(String... configLocations) {
        if (!ObjectUtils.isEmpty(configLocations)) {
            throw new UnsupportedOperationException(
                    "GenericWebApplicationContext does not support setConfigLocations(). " +
                    "Do you still have an 'contextConfigLocations' init-param set?");
        }
    }

    @Override
    public String[] getConfigLocations() {
        throw new UnsupportedOperationException(
                "GenericWebApplicationContext does not support getConfigLocations()");
    }
}

GenericWebApplicationContext 並未實現 ConfigurableWebApplicationContext 的核心方法,也就不能通過檔案載入配置,
該類設計的目的不是在web.xml中進行宣告式的安裝,而是程式設計式的安裝,例如使用WebApplicationInitializers來構建內嵌的上下文;一般很少用到

5. StaticWebApplicationContext

因為 StaticApplicationContext
實現功能比較少,放在這裡一起解析

public class StaticApplicationContext extends GenericApplicationContext {
    private final StaticMessageSource staticMessageSource;

    public StaticApplicationContext(@Nullable ApplicationContext parent) throws BeansException {
        super(parent);

        // 上下文物件中有一個messageSource元件,實現了i18n功能
        // 而StaticMessageSource實現的是由程式載入文字,而非檔案,便是去掉了i18n功能
        this.staticMessageSource = new StaticMessageSource();
        getBeanFactory().registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.staticMessageSource);
    }
}

StaticWebApplicationContext
實現功能也比較少

public class StaticWebApplicationContext extends StaticApplicationContext
        implements ConfigurableWebApplicationContext, ThemeSource {
    // 不可設定bean配置檔案來源
    @Override
    public void setConfigLocation(String configLocation) {
        throw new UnsupportedOperationException("StaticWebApplicationContext does not support config locations");
    }

    @Override
    public void setConfigLocations(String... configLocations) {
        throw new UnsupportedOperationException("StaticWebApplicationContext does not support config locations");
    }

    @Override
    public String[] getConfigLocations() {
        return null;
    }
}

StaticWebApplicationContext 也並未實現 ConfigurableWebApplicationContext 的核心方法,也就不能通過檔案載入配置,
該類設計的目的主要用於測試,不用於產品環境

6. AbstractRefreshableApplicationContext

AbstractRefreshableApplicationContext
的主要功能是建立 bean 工廠,重新整理上下文資料

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
    @Override
    protected final void refreshBeanFactory() throws BeansException {
        // ... 程式碼省略
        try {
            // 建立bean工廠
            DefaultListableBeanFactory beanFactory = createBeanFactory();

            // ... 程式碼省略

            // 載入bean的定義
            loadBeanDefinitions(beanFactory);
            this.beanFactory = beanFactory;
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

    // 建立bean工廠
    protected DefaultListableBeanFactory createBeanFactory() {
        // 預設使用DefaultListableBeanFactory建立bean工廠
        return new DefaultListableBeanFactory(getInternalParentBeanFactory());
    }

    // 載入bean的定義,由子類實現
    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
                throws BeansException, IOException;
}

7. AbstractRefreshableConfigApplicationContext

AbstractRefreshableConfigApplicationContext
的主要功能是可以通過檔案載入配置

public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
        implements BeanNameAware, InitializingBean {
    // 設定配置檔案來源,以",; \t\n"分隔多個
    public void setConfigLocation(String location) {
        setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
    }

    public void setConfigLocations(@Nullable String... locations) {
        if (locations != null) {
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                // 解析路徑,替換${}佔位符
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
    }

    // 獲取配置檔案來源集,如果沒有,則返回預設的
    protected String[] getConfigLocations() {
        return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
    }

    // 預設的配置檔案來源集由子類實現
    protected String[] getDefaultConfigLocations() {
        return null;
    }

    // 解析路徑,替換${}佔位符,有PropertySourcesPropertyResolver.resolveRequiredPlaceholders實現此功能
    protected String resolvePath(String path) {
        return getEnvironment().resolveRequiredPlaceholders(path);
    }
}
  • AbstractRefreshableConfigApplicationContext 實現了 ConfigurableWebApplicationContext 的核心方法,也就是可以檔案載入配置
  • PropertySourcesPropertyResolver如何是解析路徑的,後面再解析

8. XmlWebApplicationContext

因為 AbstractRefreshableWebApplicationContext
實現功能比較少,放在這裡一起解析

public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext
        implements ConfigurableWebApplicationContext, ThemeSource {
    @Override
    protected ConfigurableEnvironment createEnvironment() {
        // StandardServletEnvironment擴充套件了StandardEnvironment
        // 增加了可以從Servlet context init parameters和Servlet config init parameters增加應用配置來源
        return new StandardServletEnvironment();
    }
}

XmlWebApplicationContext
的主要功能是定義了預設的配置檔案,建立一個 bean 定義的 xml 解析器,並註冊 bean 定義

public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
    // 預設配置檔案
    public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

    // 預設配置檔案字首
    public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";

    // 預設配置檔案字尾
    public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 建立一個bean定義的xml解析器,用XmlBeanDefinitionReader實現
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // ... 程式碼省略

        // 載入bean定義
        loadBeanDefinitions(beanDefinitionReader);
    }

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            for (String configLocation : configLocations) {
                // 通過配置檔案載入bean定義
                reader.loadBeanDefinitions(configLocation);
            }
        }
    }

    @Override
    protected String[] getDefaultConfigLocations() {
        if (getNamespace() != null) {
            // 如果有servlet-name(如testapp),用字首字尾包裹為"/WEB-INF/testapp-servlet.xml"
            return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
        }
        else {
            // 如果沒有,預設為"/WEB-INF/applicationContext.xml"檔案
            return new String[] {DEFAULT_CONFIG_LOCATION};
        }
    }
}

XmlWebApplicationContext 主要解決了 2 個問題:

  1. 定義了預設的配置檔案,有 servlet-name(如testapp),用字首字尾包裹為/WEB-INF/testapp-servlet.xml,如果沒有 servlet-name,則為/WEB-INF/applicationContext.xml
  2. 建立一個 bean 定義的 xml 解析器,並通過配置檔案載入 bean 定義

SpringMVC 框架的預設載入機制便是使用XmlWebApplicationContext作為上下文環境,從 xml 檔案載入配置與 bean 定義

至於XmlBeanDefinitionReader如何是解析 bean 定義的,後面再解析

9. AnnotationConfigWebApplicationContext

AnnotationConfigWebApplicationContext
的主要功能是可以通過註解載入配置和 bean 定義

public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWebApplicationContext
        implements AnnotationConfigRegistry {}

先來看看 AnnotationConfigRegistry

public interface AnnotationConfigRegistry {
    // 根據類名註冊元件
    void register(Class<?>... componentClasses);

    // 根據包名掃描元件
    void scan(String... basePackages);
}

這兩個方法正好是通過註解如 @Configuration, @bean, @Component, @Controller, @Service 等註冊 bean 的底層機制

來看看 AnnotationConfigWebApplicationContext 是如何實現的

public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWebApplicationContext
        implements AnnotationConfigRegistry {
    // 元件類集合
    private final Set<Class<?>> componentClasses = new LinkedHashSet<>();
    // 掃描包名集合
    private final Set<String> basePackages = new LinkedHashSet<>();

    // 註冊元件
    @Override
    public void register(Class<?>... componentClasses) {
        Collections.addAll(this.componentClasses, componentClasses);
    }

    // 新增掃描包名
    @Override
    public void scan(String... basePackages) {
        Collections.addAll(this.basePackages, basePackages);
    }

    // 載入bean定義
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
        // 建立一個bean定義的註解解析器,用AnnotatedBeanDefinitionReader實現
        AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
        // 建立一個基於包名的bean註解掃描器,用ClassPathBeanDefinitionScanner實現
        ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);

        // 建立一個bean命名生成器
        BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
        if (beanNameGenerator != null) {
            reader.setBeanNameGenerator(beanNameGenerator);
            scanner.setBeanNameGenerator(beanNameGenerator);
            beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
        }

        // 建立一個bean作用域元資訊解析器,判斷註冊的bean是原生型別(prototype)還是單例型別(singleton)
        ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
        if (scopeMetadataResolver != null) {
            reader.setScopeMetadataResolver(scopeMetadataResolver);
            scanner.setScopeMetadataResolver(scopeMetadataResolver);
        }

        // 註冊元件類
        if (!this.componentClasses.isEmpty()) {
            reader.register(ClassUtils.toClassArray(this.componentClasses));
        }

        // 掃描包
        if (!this.basePackages.isEmpty()) {
            scanner.scan(StringUtils.toStringArray(this.basePackages));
        }

        // 通過定義的配置來源註冊元件類或掃描包名
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            for (String configLocation : configLocations) {
                try {
                    Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
                    reader.register(clazz);
                }
                catch (ClassNotFoundException ex) {
                    int count = scanner.scan(configLocation);
                    // ... 程式碼省略
                }
            }
        }
    }

    // 建立一個bean定義的註解解析器,用AnnotatedBeanDefinitionReader實現
    protected AnnotatedBeanDefinitionReader getAnnotatedBeanDefinitionReader(DefaultListableBeanFactory beanFactory) {
        return new AnnotatedBeanDefinitionReader(beanFactory, getEnvironment());
    }

    // 建立一個基於包名的bean註解掃描器,用ClassPathBeanDefinitionScanner實現
    protected ClassPathBeanDefinitionScanner getClassPathBeanDefinitionScanner(DefaultListableBeanFactory beanFactory) {
        return new ClassPathBeanDefinitionScanner(beanFactory, true, getEnvironment());
    }
}

實際上,註冊 bean 是由AnnotatedBeanDefinitionReader完成,掃描包是由ClassPathBeanDefinitionScanner完成,這兩個類後面再解析

10. GroovyWebApplicationContext

GroovyWebApplicationContext
的執行機制與 XmlWebApplicationContext 差不多,從 groovy 檔案載入配置與 bean 定義

public class GroovyWebApplicationContext extends AbstractRefreshableWebApplicationContext implements GroovyObject {
    // 預設配置檔案
    public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.groovy";

    // 預設配置檔案字首
    public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";

    // 預設配置檔案字尾
    public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".groovy";

    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 建立一個bean定義的Groovy解析器,用GroovyBeanDefinitionReader實現
        GroovyBeanDefinitionReader beanDefinitionReader = new GroovyBeanDefinitionReader(beanFactory);

        // ... 程式碼省略

        // 載入bean定義
        loadBeanDefinitions(beanDefinitionReader);
    }

    protected void loadBeanDefinitions(GroovyBeanDefinitionReader reader) throws IOException {
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            for (String configLocation : configLocations) {
                // 通過配置檔案載入bean定義
                reader.loadBeanDefinitions(configLocation);
            }
        }
    }

    @Override
    protected String[] getDefaultConfigLocations() {
        if (getNamespace() != null) {
            // 如果有servlet-name(如testapp),用字首字尾包裹為"/WEB-INF/testapp-servlet.groovy"
            return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
        }
        else {
            // 如果沒有,預設為"/WEB-INF/applicationContext.groovy"檔案
            return new String[] {DEFAULT_CONFIG_LOCATION};
        }
    }
}

11. 綜述

WebApplicationContext
定義了 Web 應用初始化的基本流程,主要有 5 個實現類,常用的是:基於 Xml 載入的XmlWebApplicationContext
與基於註解載入的AnnotationConfigWebApplicationContext

12. 未完

這一節仍然有一些點留待下次解析:

  • ConfigurableListableBeanFactory如何載入、例項化 bean
  • ResourceEditorRegistrar如何註冊屬性編輯器、屬性編輯器如何解析為物件
  • PathMatchingResourcePatternResolver如何解析、載入 locationPattern 指定的資源
  • PropertySourcesPropertyResolver如何是解析路徑的
  • XmlBeanDefinitionReader如何是解析 bean 定義的
  • AnnotatedBeanDefinitionReader是如何註冊 bean 的
  • ClassPathBeanDefinitionScanner是如何掃描包的

後續

更多部落格,檢視 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版權宣告:自由轉載-非商用-非衍生-保持署名(創意共享 3.0 許可證

相關文章