您的位置:首页 > 编程语言 > Java开发

Spring源码阅读-ApplicationContext体系结构分析

2019-07-15 22:38 1726 查看

目录

  • WebApplicationContext
  • 本文思维导图
  • 上篇已经对IoC容器的设计进行了分析(Spring源码阅读-IoC容器解析),本篇将对

    ApplicationContext
    经典的继承层次图进行详细的分析,在心中形成一个大致的印象,以便后面一步步调试源码的时候,不会太眼花缭乱。让我们一步步的前进吧...

    继承层次图概览

    使用IDEA的继承层次工具生成如下的图(选中ApplicationContext --> Ctrl+H):

    (温馨提示:双击可查看高清大图)

    从上图能很清楚的看出,

    ApplicationContext
    的子接口分为两个部分:

    • ConfigurableApplicationContext
      :大部分的应用上下文都实现了该接口
    • WebApplicationContext
      :在web应用程序中使用

    ConfigurableApplicationContext分析

    从上面的类的继承层次图能看到,

    ConfigurableApplicationContext
    是比较上层的一个接口,该接口也是比较重要的一个接口,几乎所有的应用上下文都实现了该接口。该接口在
    ApplicationContext
    的基础上提供了配置应用上下文的能力,此外提供了生命周期的控制能力。先看一下该接口的继承关系图(为了更加简洁,去掉了
    ApplicationContext
    继承的接口):

    (温馨提示:双击可查看高清大图)

    Closeable
    接口用于关闭应用上下文,释放所有的资源和锁,这也包括摧毁所有缓存的单例的bean,常见的try-with-resources用法如下,执行完try体中的代码后会自动的调用
    close
    方法:

    try (ConfigurableApplicationContext cac = ...) {
    // 编写代码
    ...
    }

    Lifecycle
    定义了启动/停止生命周期的控制的一些方法,其中的方法如下:

    void start(); // 启动组件
    void stop(); // 停止组件
    boolean isRunning(); // 组件是否正在运行

    接下来看一下

    ConfigurableApplicationContext
    中的方法:

    void setId(String id); // 设置应用上下文唯一的id
    void setParent(ApplicationContext parent); // 设置应用程序上下文的父级
    void setEnvironment(ConfigurableEnvironment environment); // 设置应用上下文的环境
    ConfigurableEnvironment getEnvironment();  // 获取应用上下文的环境
    // 添加一个新的BeanFactoryPostProcessor
    void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
    // 添加应用程序监听器
    void addApplicationListener(ApplicationListener<?> listener);
    // 添加协议解析器,可能会覆盖默认的规则
    void addProtocolResolver(ProtocolResolver resolver);
    // 加载或者刷新配置
    void refresh() throws BeansException, IllegalStateException;
    // 向JVM runtime注册一个关闭钩子,JVM关闭时关闭这个上下文
    void registerShutdownHook();
    // 应用程序上问下是否是激活状态
    boolean isActive();
    // 获取应用上下文内部的bean factory
    ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

    上面的这些方法基本上是提供了对某些特性的实现进行支撑的方法。

    看了这么多方法,下面看一下

    ApplicationContext
    的抽象的实现。

    AbstractApplicationContext

    AbstractApplicationContext
    ApplicationContext
    接口的抽象实现,这个抽象类仅仅是实现了公共的上下文特性。这个抽象类使用了模板方法设计模式,需要具体的实现类去实现这些抽象的方法。对相关接口的实现如下:

    • ApplicationContext
      接口的实现
    • ConfigurableApplicationContext
      接口的实现
    • BeanFactory
      接口的实现
    • ListableBeanFactory
      接口的实现
    • HierarchicalBeanFactory
      接口的实现
    • MessageSource
      接口的实现
    • ResourcePatternResolver
      的实现
    • Lifecycle
      接口的实现

    本文不会详细的讲解这个类中的具体的实现细节,后面会有更加的详细的介绍。下面看下里面的抽象方法:

    // 刷新BeanFactory,用于执行实际的配置加载,该方法在其他的初始化工作之前被refresh()方法调用
    protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
    // 关闭BeanFactory,用于释放内部使用的BeanFactory·
    protected abstract void closeBeanFactory();
    // 获取内部使用的BeanFactory
    public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

    那么对需要实现的方法经过抽象后,只剩下少量的需要子类去实现的方法。

    GenericApplicationContext

    GenericApplicationContext
    继承自
    AbstractApplicationContext
    ,是为通用目的设计的,它能加载各种配置文件,例如xml,properties等等。它的内部持有一个
    DefaultListableBeanFactory
    的实例,实现了
    BeanDefinitionRegistry
    接口,以便允许向其应用任何bean的定义的读取器。为了能够注册bean的定义,
    refresh()
    只允许调用一次。常见的使用如下:

    GenericApplicationContext ctx = new GenericApplicationContext();
    XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
    xmlReader.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
    PropertiesBeanDefinitionReader propReader = new PropertiesBeanDefinitionReader(ctx);
    propReader.loadBeanDefinitions(new ClassPathResource("otherBeans.properties"));
    ctx.refresh();
    
    MyBean myBean = (MyBean) ctx.getBean("myBean");
    ..

    这个类的实现没有太多需要注意的地方,需要注意的有两点:

    • 内部使用的
      DefaultListableBeanFactory
      的实例,提供了一些方法来配置该实例,例如是否允许bean定义的覆盖、是否允许bean之间的循环应用等等。
    • 该类实现了
      BeanDefinitionRegistry
      ,bean的定义注册。以便能通过
      BeanDefinitionReader
      读取bean的配置,并注册。
      BeanDefinitionRegistry
      接口的实现是直接使用内部的
      DefaultListableBeanFactory
      的实例。

    GenericXmlApplicationContext

    GenericXmlApplicationContext
    继承自
    GenericApplicationContext
    ,内置了对XML的支持。它非常的方便和灵活,是
    ClassPathXmlApplicationContext
    FileSystemXmlApplicationContext
    的一种替代品。可以发现,它的内部有一个
    XmlBeanDefinitionReader
    的实例,专门用于处理XML的配置。

    StaticApplicationContext

    StaticApplicationContext
    继承自
    GenericApplicationContext
    ,主要用于编程式的注入bean和消息,而不是从外部的配置源读取bean的定义。主要是在测试时非常有用。通过阅读源代码可以看到,它的内部有一个
    StaticMessageSource
    的实例,使用
    addMessage
    方法添加消息。每次在编程式的注入bean时,都会创建一个
    GenericBeanDefinition
    的实例。

    ResourceAdapterApplicationContext

    ResourceAdapterApplicationContext
    继承自
    GenericApplicationContext
    ,是为JCA(J2EE Connector Architecture)的ResourceAdapter设计的,主要用于传递
    BootstrapContext
    的实例给实现了
    BootstrapContextAware
    接口且由spring管理的bean。覆盖了
    postProcessBeanFactory
    方法来实现此功能。

    GenericGroovyApplicationContext

    GenericGroovyApplicationContext
    继承自
    GenericApplicationContext
    ,实现了
    GroovyObject
    接口以便能够使用点的语法(.xx)取代
    getBean
    方法来获取bean。它主要用于Groovy bean的定义,与
    GenericXmlApplicationContext
    一样,它也能解析XML格式定义的bean。内部使用
    GroovyBeanDefinitionReader
    来完成groovy脚本和XML的解析。

    AnnotationConfigApplicationContext

    AnnotationConfigApplicationContext
    继承自
    GenericApplicationContext
    ,提供了注解配置(例如:Configuration、Component、inject等)和类路径扫描(scan方法)的支持,可以使用
    register(Class<?>... annotatedClasses)
    来注册一个一个的进行注册。实现了AnnotationConfigRegistry接口,来完成对注册配置的支持,只有两个方法:register和scan。内部使用
    AnnotatedBeanDefinitionReader
    来完成注解配置的解析,使用
    ClassPathBeanDefinitionScanner
    来完成类路径下的bean定义的扫描。

    AbstractRefreshableApplicationContext

    AbstractRefreshableApplicationContext
    继承自
    AbstractApplicationContext
    ,支持多次进行刷新(多次调用
    refresh
    方法),每次刷新时在内部创建一个新的bean工厂的实例。子类仅仅需要实现
    loadBeanDefinitions
    方法,该方法在每次刷新时都会调用。

    AbstractRefreshableConfigApplicationContext

    AbstractRefreshableConfigApplicationContext
    继承自
    AbstractRefreshableApplicationContext
    ,添加了对指定的配置文件路径的公共的处理,可以把他看作基于XML的应用上下文的基类。实现了如下的两个接口:

    • BeanNameAware
      用于设置上下文的bean的名称,只有一个方法:
      void setBeanName(String name)
    • InitializingBean
      用于上下文一切就绪后,如果还未刷新,那么就执行刷新操作,只有一个方法:
      void afterPropertiesSet()

    AbstractXmlApplicationContext

    AbstractXmlApplicationContext
    继承自
    AbstractRefreshableConfigApplicationContext
    ,用于描绘包含能被
    XmlBeanDefinitionReader
    所理解的bean定义的XML文档。子类只需要实现
    getConfigResources
    getConfigLocations
    来提供配置文件资源。

    FileSystemXmlApplicationContext

    FileSystemXmlApplicationContext
    继承自
    AbstractXmlApplicationContext
    ,用于解析文件系统中XML配置文件。文件的路径可以是具体的文件路径,例如:xxx/application.xml,也可以是ant风格的配置,例如:xxx/*-context.xml。

    看下面的通过文件路径来获取资源的代码:

    @Override
    protected Resource getResourceByPath(String path) {
    if (path.startsWith("/")) {
    path = path.substring(1);
    }
    return new FileSystemResource(path);
    }

    文件路径前面的

    /
    会被去掉,无论是否路径前面是否加上
    /
    ,文件路径都会解析成相对路径,即基于JVM的当前工作路径。获取到的资源对象是
    FileSystemResource
    ,用于处理文件系统相关的资源。

    ClassPathXmlApplicationContext

    ClassPathXmlApplicationContext
    继承自
    AbstractXmlApplicationContext
    ,和
    FileSystemXmlApplicationContext
    类似,只不过
    ClassPathXmlApplicationContext
    是用于处理类路径下的XML配置文件。文件的路径可以是具体的文件路径,例如:xxx/application.xml,也可以是ant风格的配置,例如:xxx/*-context.xml。

    WebApplicationContext

    该接口提供了在web应用中的配置,接口提供了一个

    ServletContext getServletContext()
    用来获取
    ServletContext
    对象。该接口会和ServletContext的一个属性进行绑定,这个属性就是
    ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
    。定义了三个作用域的名称:
    SCOPE_REQUEST
    ,
    SCOPE_SESSION
    ,
    SCOPE_APPLICATION
    。在工厂中的bean的名称:
    SERVLET_CONTEXT_BEAN_NAME
    。ServletContext初始化参数名称:
    CONTEXT_PARAMETERS_BEAN_NAME
    。在工厂中ServletContext属性值环境bean的名称:
    CONTEXT_ATTRIBUTES_BEAN_NAME

    ConfigurableWebApplicationContext

    ConfigurableWebApplicationContext
    继承自
    WebApplicationContext
    ConfigurableApplicationContext
    ,提供了web应用上下文的可配置的能力。相关接口定义如下:

    // 设置web应用上下文的ServletContext
    void setServletContext(@Nullable ServletContext servletContext);
    // 设置web应用上下文的ServletConfig
    void setServletConfig(@Nullable ServletConfig servletConfig);
    // 获取web应用上下文的ServletConfig
    ServletConfig getServletConfig();
    // 设置web应用上下文的命名空间
    void setNamespace(@Nullable String namespace);
    // 获取web应用上下文的命名空间
    String getNamespace();
    // 以初始化参数的形式设置web应用上下文的配置文件位置
    void setConfigLocation(String configLocation);
    // 设置web应用上下文的配置文件的位置
    void setConfigLocations(String... configLocations);
    // 获取web应用上下文的配置文件位置
    String[] getConfigLocations();

    上面的接口主要都是一些设置或者获取的方法,在web应用上下文中需要用到的一些东西。

    GenericWebApplicationContext

    GenericWebApplicationContext
    继承自
    GenericApplicationContext
    ,实现了
    ConfigurableWebApplicationContext
    ThemeSource
    接口。该类设计的目的不是在web.xml中进行声明式的安装,而是编程式的安装,例如使用
    WebApplicationInitializers
    来构建内嵌的上下文。该接口在
    ConfigurableWebApplicationContext
    的内容都是一个伪实现,调用其中的大多数方法都会抛出异常。你也许注意到了,他实现了
    ThemeSource
    接口,那么他有什么用呢?字面意思是主题源,它设计的目的主要是用于消息的国际化。

    StaticWebApplicationContext

    StaticWebApplicationContext
    继承自
    StaticApplicationContext
    实现了
    ConfigurableWebApplicationContext
    ThemeSource
    接口。该接口主要是用在测试的环境,不用于产品环境。

    AbstractRefreshableWebApplicationContext

    GenericWebApplicationContext
    继承自
    AbstractRefreshableConfigApplicationContext
    ,实现了
    ConfigurableWebApplicationContext
    ThemeSource
    接口,主要是用于web环境下。在web程序启动的时候,提供一个
    configLocations
    属性,通过
    ConfigurableWebApplicationContext
    接口来进行填充。子类化这个接口是很简单的,所有你所需要做的事情就是实现
    loadBeanDefinitions
    方法,来实现你自己的bean定义的加载逻辑。

    XmlWebApplicationContext

    XmlWebApplicationContext
    继承自
    AbstractRefreshableWebApplicationContext
    ,接受能被
    XmlBeanDefinitionReader
    所理解的XML文档配置。对于根上下文,默认的配置文件路径是
    /WEB-INF/applicationContext.xml
    ,对于命名空间为test-servlet的上下文,默认的配置文件路径是
    /WEB-INF/test-servlet.xml
    (就像servlet-name为test的DispatcherServlet实例)。

    默认的配置文件路径处理的代码如下:

    protected String[] getDefaultConfigLocations() {
    if (getNamespace() != null) {
    return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
    }
    else {
    return new String[] {DEFAULT_CONFIG_LOCATION};
    }
    }

    和其他的上下文一样,bean定义的加载也是在

    void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
    方法中,使用的是
    XmlBeanDefinitionReader

    GroovyWebApplicationContext

    GroovyWebApplicationContext
    继承自
    AbstractRefreshableWebApplicationContext
    ,实现了
    GroovyObject
    接口,接受能被
    GroovyBeanDefinitionReader
    所理解的groovy bean定义脚本和XML文档配置。对于web环境,基本上是和
    GenericGroovyApplicationContext
    是等价的。对于根上下文,默认的配置文件路径是
    /WEB-INF/applicationContext.groovy
    ,对于命名空间为test-servlet的上下文,默认的配置文件路径是
    /WEB-INF/test-servlet.xml
    (就像servlet-name为test的DispatcherServlet实例)。

    默认的配置文件路径处理的代码如下:

    protected String[] getDefaultConfigLocations() {
    if (getNamespace() != null) {
    return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
    }
    else {
    return new String[] {DEFAULT_CONFIG_LOCATION};
    }
    }

    和其他的上下文一样,bean定义的加载也是在

    void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
    方法中,使用的是
    GroovyBeanDefinitionReader

    AnnotationConfigWebApplicationContext

    AnnotationConfigWebApplicationContext
    继承自
    AbstractRefreshableWebApplicationContext

    接受注解的类作为输入(特殊的@Configuration注解类,一般的@Component注解类,与JSR-330兼容的javax.inject注解)。允许一个一个的注入,同样也能使用类路径扫描。对于web环境,基本上是和

    AnnotationConfigApplicationContext
    等价的。使用
    AnnotatedBeanDefinitionReader
    来对注解的bean进行处理,使用
    ClassPathBeanDefinitionScanner
    来对类路径下的bean进行扫描。

    部分代码如下:

    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
    AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
    ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);
    ...
    if (!this.annotatedClasses.isEmpty()) {
    ....
    reader.register(ClassUtils.toClassArray(this.annotatedClasses));
    }
    
    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());
    if (logger.isTraceEnabled()) {
    logger.trace("Registering [" + configLocation + "]");
    }
    reader.register(clazz);
    }
    catch (ClassNotFoundException ex) {
    ....
    }
    }
    }
    }
    }

    本文思维导图

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: