spring environment_Spring 中的 ApplicationContext

v2-49e6ee8ab94ae50ebbb41abf078eb53e_1440w.jpg?source=172ae18b

[toc]

Spring

Spring 作为bean容器的印象似乎已经深入人心,但Spring 首先是一个应用框架,然后才会是一个组件容器。 Spring 的核心接口ApplicationContext作为应用的一方面从接口定义来看应该是明显的。

public interface ApplicationContext
extends 
EnvironmentCapable,  // 继承环境对象容器接口
ListableBeanFactory,  
HierarchicalBeanFactory, // 继承beanFactory
MessageSource,  // 集成消息解析器
ApplicationEventPublisher, // 继承应用事件发布器
ResourcePatternResolver // 继承模式资源解析器
{}

一个应用应该能做什么?

1. EnvironmentCapable

首先,一个应用应该有自己的环境上下文。ApplicationContext继承了EnvironmentCapable。非常简单一接口, 但能为应用提供统一的配置入口。

public interface EnvironmentCapable {

    /**
     * Return the {@link Environment} associated with this component.
     */
    Environment getEnvironment();
}

环境对象Environment是应用配置的核心。主要有两个方面的功能,一个是关于应用Property的配置,另一方面是关于Profile的配置。

这层抽象的作用是,无论配置从何而来,只要通过Environment对象实例就能获得。

在Environment类之下,还有一层PropertySource

public abstract class PropertySource<T> {}

这里的Property不是Bean Property。仅管它们在概念上有共通之处,但应用属性只是一个更简单的键值对,不包含额外的上下文。更准确的说,Property是一个键到一个值的映射,键是字符串,值可以是任何类型的对象(大部分情况下也是字符串)。

例如,从实现类SystemEnvironmentPropertySource将读取系统的环境变量。

同样,可以无限继承PropertySource,实现不同来源的配置读取。Spring默认读取系统的环境变量和运行jvm时提供的变量。所有这些PropertySource被Environment放入一个队列中,逐个读取,直至提供的键获得了对应的值。因此,在队列前端的PropertySource提供的属性优先级比后面的高。例如,通过Environment对象,要求一个"password"属性时,如果在第一个PropertySource里查找到了这个值,就不会再找下一个值。

Profile 功能是环境的运行描述。例如,如果当前是在test环境下运行,则将profile设置为"test",应用可以根据这个值来调整自己的行为。

2. ListableBeanFactory 和 HierarchicalBeanFactory

这两个是组件容器的核心接口。 注意,ApplicationContext是直接继承了ListableBeanFactory, 而不是BeanFactory。后者比前者少了一些枚举接口。

继承这两个接口也就是意味着,我们的应用是一个组件容器。

而众所周知,ApplicationContext不仅是一个组件容器,还是一个Ioc容器,提供依赖注入功能。什么是依赖注入呢?这与类的实例化有关。假如有一个类A, 它有一个依赖类B。

public class A{
    private B b;
}

如果类B的实例是在类A外实例化的话,则称之为依赖反转。显然,在这种情况下,我们需要通过某种方式,使B的实例能顺利赋值给b,于是——

public class A{
    private B b;

    /**
    * 构造器可以赋值一个b。
    */
    public A(B b){
        this.b = b;
    }

    /**
    * setter也可以。
    */
    public setB(B b){
        this.b = b;
    }
}

反之,在传统情况下,B是在A中实例化的。

public class A{
    private B b;

    /**
    * 构造器可以赋值一个b。
    */
    public A(){
        this.b = new B();
    }
}

我们把前面那种注入依赖,而不是实例化依赖的方法称为依赖注入。Spring 就像是一个按照菜谱做菜的厨师,我们声明这个类的依赖是什么,那个类的依赖又是什么,然后Spring帮我们把每个依赖实例化好,然后注入到对应的bean中。

3. MessageSource

public interface MessageSource {

    @Nullable
    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

    String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}

应用还是应该是一个消息解析器。这玩意乍看之下似乎和Environment的功能没什么区别。实际上,形式上确实也没多少区别,只是应用的场景下不太一样,MessageSource提供跨语言环境的支撑。

例如我们需要一段文本,这段文本提供了更新信息。 但不巧的是, 我们是个跨国公司,需要提供不同语言的版本。硬编码是一个方法。

if(inChinese){
    text = "版本3.2";
}else(inEnglish){
    text = "Version3.2"
}

MessageSource提供了不同的思路。所谓消息就是文本的代号。

code = "application.version";
Locale locale = getCurrentLocale(); // Locale对象标定了当前使用哪种语言
text = messageSource.getMessage(code, locale);

想想一些国外游戏的所谓“中文资源包”,就是这些code到中文翻译文本的映射,游戏本身只是编码上这个code,提供什么资源包,就显示什么语言。

4. ApplicationEventPublisher

Spring应用提供发布事件功能。

public interface ApplicationEventPublisher {

    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }

    void publishEvent(Object event);
}

事件发布是一种编程模式,是回调编程的一个变种。具体而言,就是我们可以在Spring上发布一个事件,然后Spring会寻找这个容器的监听器,然后调用监听器的代码。

例如,Spring 应用关闭后,会发布一个应用关闭事件。 应用代码可以注册这个事件的监听器, 处理一些资源关闭事件。

但这个模式最强的地方在于,它能把业务逻辑流程高度抽象起来,然后在这些抽象的流程中间插入事件发布。例如一个订单业务,遵循下单-付款-发货-收货,即可以抽象出对应的事件,如果我们想在用户下单时,弹出一些优惠提示,我们就可以注册一个下单事件的监听器来进行这个工作。一旦优惠截止,只需要卸载这个监听器即可。

有意无意的,事件流描述了一个应用逻辑流程的生命周期各个重要的节点。所以经常的,当你发现只要是涉及生命周期这个话题时,事件出镜率总是非常高。事实上,对于这个模式强编码出类似于"publish"和"event"的描述,确实有些过度抽象的嫌疑。这种命名方式对于业务逻辑代码的自说明性并不友好,不过意思就只是这么个意思。

5. ResourcePatternResolver

这是一个有趣的话题。一个ResourcePatternResolver是一个ResourceLoader。但前者比后者比多一个模式匹配的接口。通常,我们认为一个资源位置字符串代表了一个资源,但一个模式字符串可以匹配多个资源。

Resource[] getResources(String locationPattern) throws IOException;

所谓资源,在java的定义中,是一段字节流。文件是一段命名字节流,网络消息是一段字节流,内存是一块大字节数组……诸如此类,Resource接口抽象出这些来源,提供共同的操作接口。即从应用代码的角度出发,你不再需要管它是一个File还是一个URI,只需要老实调用getInputStream()readableChannel()方法就可以了。

Spring的配置文件就是通过ResourcePatternResolver读取的。触类旁通,你当然也可以通过它来获得你想要的文件,无论它在classpath下、一个本地文件还是一个网络位置。

6. Spring应用和Bean容器

Bean 即应用组件。Spring的容器功能是由BeanFactory提供的。BeanFactory不预设任何关于应用的信息。总得说来,它只做三件事情:

  1. 管理BeanDefinition。
  2. 响应获得某个特定组件的请求
  3. 在各个组件的生命周期事件里调用相应的回调

应用(application)当然是由组件(component)组成的,所以无疑BeanFactory是一个非常重要的部分。

但我们觉得application应该是可定制的。因此,对于BeanFactory,应用提供了BeanFactoryPostProcessor接口。application从beanFactory中查找BeanFactoryPostProcessor,然后用这个接口来完成诸如注册额外bean的功能。

另外,application还在组件容器注册了一些特殊的bean,即Environment,ApplicationEventPublisher等。这意味着,这些对象在应用的任意组件中是可用的。

由于组件容器的特殊性质,application常做的事情就是查找bean容器里的bean,然后用这些bean来配置应用,甚至是bean容器本身。例如,bean容器不预设关于应用的信息,它甚至不会配置bean的生命周期回调类,需要appliction帮它从容器中查到关于BeanPostProcessor的信息,然后注册到bean容器上。此外,application常用到一种风格,即提前注册BeanPostProcessor去处理某一种实现了某种接口的对象,例如ApplicationListenerDetector, 注册所有ApplicationListener,而ApplicationContextAwareProcessor处理EmbeddedValueResolverAware,ResourceLoaderAware在内的多个接口。

7. 应用构成

实际上的应用构成就只是上面实现的接口。但值得注意的是,spring并不直接实现这些接口,应用就好像一个超级代理类,虽然它声称自己实现了这些接口,但实际上却是派发到其它类去执行这个工作。

例如:

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

显然,application没有打算自己去获得bean,而是派发这个任务给内部的beanFactory。

这直接导致了spring应用是强组装性的,你可以任意替换一些组件完成自己的工作。不过,对于beanFactory并不推荐如此做(不妨碍它可以这么干),因为里面涉及大量的接口协定,如果你不是spring的开发人员,很容易在一些细微的地方搞出问题。

8. 继承体系核心

AbstractApplicationContext是spring应用继承体系的核心。这个类实现了应用的一般流程,一方面,它实现了ApplicationContext接口本身的内容,诸如id,displayname之类的描述性信息,另一方面,它通过或者代理或者继承的方式实现了父接口的内容。

在重点方法AbstractApplicationContext.refresh方法实现了:

  1. 初始化应用状态
  2. 调用refreshBeanFactory(由子类实现)刷新内部的beanFactory。此时,它假设beanfacotry已经加载了配置好的bean组件。
  3. 向beanFacotry中注入通用组件,注入environment,注册通用beanPostProcessor等等。
  4. 调用子类的beanFactory处理方法(在调用beanFactoryPostProcessor时给子类一个机会介入)。
  5. 调用beanFactoryPostProcessor。注意,对于Configuration类定义的配置,或者扫描实现的配置,是在这个时期把bean定义加载入beanFactory的。此外,spring会在beanFactory中自动寻找beanFactoryPostProcessor,但也可以通过ConfigurableApplicationContext接口手动注册。
  6. 在beanFactory其它配置完成后,注册所有其它的beanPostProcessor。
  7. 完成其它如messageSource, ApplicationEventMulticaster组件的初始化工作。
@Override
 public void refresh() throws BeansException, IllegalStateException {
 synchronized (this.startupShutdownMonitor) {
 // Prepare this context for refreshing.
 prepareRefresh();

 // Tell the subclass to refresh the internal bean factory.
 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

 // Prepare the bean factory for use in this context.
 prepareBeanFactory(beanFactory);

 try {
 // Allows post-processing of the bean factory in context subclasses.
 postProcessBeanFactory(beanFactory);

 // Invoke factory processors registered as beans in the context.
 invokeBeanFactoryPostProcessors(beanFactory);

 // Register bean processors that intercept bean creation.
 registerBeanPostProcessors(beanFactory);

 // Initialize message source for this context.
 initMessageSource();

 // Initialize event multicaster for this context.
 initApplicationEventMulticaster();

 // Initialize other special beans in specific context subclasses.
 onRefresh();

 // Check for listener beans and register them.
 registerListeners();

 // Instantiate all remaining (non-lazy-init) singletons.
 finishBeanFactoryInitialization(beanFactory);

 // Last step: publish corresponding event.
 finishRefresh();
            }

 catch (BeansException ex) {
 if (logger.isWarnEnabled()) {
 logger.warn("Exception encountered during context initialization - " +
 "cancelling refresh attempt: " + ex);
                }

 // Destroy already created singletons to avoid dangling resources.
 destroyBeans();

 // Reset 'active' flag.
 cancelRefresh(ex);

 // Propagate exception to caller.
 throw ex;
            }

 finally {
 // Reset common introspection caches in Spring's core, since we
 // might not ever need metadata for singleton beans anymore...
 resetCommonCaches();
            }
        }
    }

可以说 AbstractApplicationContext 已经将高层的应用逻辑抽象得十分完备,现在只剩下加载初始化的bean组件到beanFactory这一件事了。

因此,AbstractApplicationContext只留下了三个接口给子类实现,它们分别是:

protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;


protected abstract void closeBeanFactory();


@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

其中,refreshBeanFactory要做的事情就是加载所有初始化的bean组件。

9. ApplicationContext更具体的实现

针对AbstractApplicationContext这一设定,spring又提供了两个分支的子类型。

一方面,在前者的基础上,重新提供了AbstractRefreshableApplicationContext子类型。此类型继承AbstractApplicationContext,为refreshBeanFactory方法提供了一个逻辑实现——如果已有beanFactory刷新过了,则先关闭它,然后重建一个,并且为它加载bean定义。它提供了一个loadBeanDefinitions方法给子类实现。至于子类从哪加载,如何加载,并不过问。

另一方面GenericApplicationContext则是直接实现了AbstractApplicationContext。这也是我们目前看到的第一个非抽象类。它的刷新方法refreshBeanFactory非常简单, 只是判断是否刷新过,如果刷新过就抛异常。在加载bean定义这件事上,它并不交给子类去做,而是自己实现了一个BeanDefinitionRegistry,也就是说将bean定义从哪里来的事情交给了外部类来考虑。

回到AbstractRefreshableApplicationContext类的这条线上。loadBeanDefinitions方法没有提到如何加载bean定义,AbstractRefreshableConfigApplicationContext补上了这个缺陷,它认为所有的bean定义应该从configLocations处加载。但是,美中不足的是,这个类仍然没有说明configLocations应该是什么,从代码来看,它仅仅只是个字符串数组。

每个location可以解释为一个xml的位置,于是AbstractXmlApplicationContext应运而生。它将location解释为xml,并将xml的内容加载为beanDefinition注册到beanFactory中。此外,它提供了额外的Resource数组(内容必须是xml),使已经构建好的Resource对象不必再拆封装一次。然而,虽然这个类依然被声明为abstract,但它并没有提供更多的抽象方法。

秘密在AbstractApplicationContext上。AbstractApplicationContext继承了DefaultResourceLoader以实现ResourceLoader接口。这个类在实现getResource方法的时候,提供了一个getResourceByPath方法。具体而言,逻辑是这样的,如果location以"/"开头,则调用getResourceByPath方法,如果以"classpath:"开头,则搜索类路径,否则视作URL资源。

于是,所有AbstractApplicationContext都可以覆盖getResourceByPath来实现自己的默认路径类型(也就是不带前缀,仅仅是以"/"开头的路径,默认是classpath)。而在AbstractXmlApplicationContext中,this对象被用来加载资源。所以AbstractXmlApplicationContext只需要实现getResourceByPath来实现自己的特殊资源位置需求。

例如FileSystemXmlApplicationContext

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

ClassPathXmlApplicationContext会更简单,因为classpath是默认值,无须对此做任何覆盖。因此ClassPathXmlApplicationContext只做了一些简单的构造器重载,覆盖了getConfigResources方法。

10. 从Java类加载配置的原理

加载Configuration类的原理在于,向beanFactory注册一个ConfigurationClassPostProcessor。这个类会循环加载Configuration定义。

我们重新看一下在AbstractApplicationContext中,bean定义加载分成几个阶段。

  1. 指示子类刷新BeanFactory,此时会加载bean的初始化定义。xml配置在这里读取。
  2. 加载应用通用组件
  3. 调用BeanFacotryPostProcessor,可能会发生bean的注册。

顺便一说,有细心的同学可能会发现,这里有一个问题,如果说一个beanFacotryPostProcessor又加载了一个BeanFacotryPostProcessor定义应该怎么办?Spring定义了BeanFacotryPostProcessor的一个子接口BeanDefinitionRegistryPostProcessor,注册bean定义应该通过这个子接口来实现,spring首先会不断循环调用这个接口,直至没有新的BeanDefinitionRegistryPostProcessor注入,然后再调用一般的BeanFacotryPostProcessor。将一个主接口分成不同子接口的这种技巧在BeanPostProcessor也有体现。

那么对于应用程序客户端来说最终的bean来源即可能有两个——通用组件没办法控制。所以,一方面,我们可以直接通过xml直接注册,另一方面,我们可以在初始注册的bean中加入一个BeanFacotryPostProcessor,然后这个BeanFacotryPostProcessor会注册bean定义。

显然,我们可以首先注入一个ConfigurationClassPostProcessor实例,然后由ConfigurationClassPostProcessor提取beanFacotry中的Configuration。

11. 使用注解配置bean原理

前面说到的BeanFacotryPostProcessor可以注册bean。然而针对单个bean的配置时,我们需要用到BeanPostProcessor。ApplicationContext会自动注册BeanPostProcessor。在每个bean实例化的时候,BeanPostProcessor都会被调用。

例如,CommonAnnotationBeanPostProcessor注册后,每次在bean创建的时候,就会识别并使用JSR-250注解。而AutowiredAnnotationBeanPostProcessor则识别和使用@Autowired注解。

12. 扫描配置的原理

在spring framework 中扫描的利用方式有三种。一种是在xml文件中配置扫描包,另一种则是调用AnnotationConfigApplicationContext.scan()方法,最后一种是在@Configuration类上提供@ComponentScan注解。

ComponentScanBeanDefinitionParser被用来在xml中配置扫描组件类,即配置了@Component的类,例如,在xml中配置了:

<component-scan/>

ComponentScanBeanDefinitionParser.parse()就会被调用。最后,这个parse方法会把真正的扫描工作分派给ClassPathBeanDefinitionScanner去完成扫描。

显然,由于读取的是xml的配置,这里的bean扫描进的是初始化配置。

AnnotationConfigApplicationContext读取则是直接依赖ClassPathBeanDefinitionScanner来实现。前面提到GenericApplicationContext依靠自己读取bean定义,AnnotationConfigApplicationContext则是对它的增强,把注册方法聚拢在scanregister方法`上。

@ComponentScan 是与 @Configuration 一并被处理的标签。最终也是分派给ClassPathBeanDefinitionScanner完成任务。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值