关于Spring的IoC容器,你了解多少

IoC的基本概念

  Ioc的全称是Inversion of Control,中文通常翻译为“控制反转”。好莱坞原则“Dont't call us, we will call you.”,恰如其分表达了“反转”的意味,是用来形容Ioc最多的一句话。

  IoC的理念就是,让别人为你服务。在下图中,也就是让IoC Service Provider来为你服务!

  在通常情况下,被注入对象会直接依赖于被依赖对象。但是,在IoC场景中,二者之间是通过IoC Service Provider来打交道,所有的被注入对象和依赖对象现在由IoC Service Provider统一管理,被注入对象需要什么,直接跟IoC Service Provider招呼一声,后者就会把相应的被依赖对象注入到被注入对象中,从而达到IoC Service Provider为被注入对象服务的目的。IoC Service Provider在这里就是通常的IoC容器所充当的角色。从被注入对象的角度看,与之前直接寻求依赖对象相比,依赖对象的取得方式发生了反转,控制也从被注入对象转到了IoC Service Provider那里。

  J2EE的一种核心模式就是通过引入中间代理者消除对象间复杂的耦合关系,并统一管理分散的复杂耦合关系。

IoC的实现有两种:

  • DI(Dependence Injection),中文名称叫依赖注入,对象和对象的属性都是以注入的方式添加到容器中,Spring中使用的这种,本文也主要是讲述该种实现。
  • DL(Dependence Lookup),中文名称叫依赖查找,类似于目录的查找方法。

Ioc容器的三种注入方式:

  • 构造方法注入:这种注入方式的优点就是,对象在构造完成后,即已进入就绪状态,可以立即使用。缺点就是,当依赖对象比较多的时候,构造方法的参数列表会比较长。而通过反射构造对象的时候,对相同类型的参数的处理会比较困难。而且在Java中,构造方法无法被继承,无法设置默认值。对于非必须的依赖处理,可能需要引入多个构造方法,而参数数量的变动可能造成维护上的不便。
  • setter方法注入:因为方法可以命名,所以setter方法注入在描述性上要比构造注入好一些。另外,setter方法可以被继承,允许设置默认值。缺点就是对象无法在构造完成后马上进入就绪状态。
  • 接口注入:该方式已被弃用。因为它强制被注入对象实现不必要的接口,带有侵入性。

  如果要用一句话来概括IoC可以带给我们什么,那么我希望是,IoC是一种可以帮助我们解耦各业务对象间依赖关系的对象绑定方式。

掌管大局的IoC Service Provider

IoC Service Provider的职责

  IoC Service Provider的职责相对来说比较简单,主要有两个:业务对象的构建管理和业务对象间的依赖绑定。

  • 业务对象的构建管理:在IoC场景中,业务对象无需关心所依赖的对象如何构建如何获取得,但这部分工作始终都需要有人来做。所以,IoC Service Provider需要将对象的构建逻辑从客户端对象哪里剥离出来,以免这部分逻辑污染业务对象的实现。
  • 业务对象间的绑定:对于IoC Service Provider来说,这是它的最终使命之所在。如果不能完成这个职责,那么,无论业务对象如何的“呼喊”,也不会得到依赖对象的任何响应(最常见的倒是会收到一个NullPointException)。IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

IoC Service Provider如何管理对象间的依赖关系

  1. 直接编码方式:在容器启动之前,我们就可以通过程序编码的方式将被注入对象和被依赖对象注册到容器中,并明确他们互相之间的依赖注入关系。如下的伪代码就演示了这样一个过程:
IoContainer container = ...;
container.register(FXNewsProviderc.lass, new FXNewsProvider());
container.register(IFXNewsListener.class, new DownJonsNewsListener());
...
FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
newsProvider.getAndPersisNews();
  1. 配置文件方式:这是一种较为普遍的依赖注入关系管理方式。像普通文本文件、properties文件、XML文件等,都可以成为管理依赖注入关系的载体。对于如上的伪代码示例来说,我们也可以使用Spring 配置文件的方式来配置和管理各个对象间的依赖关系。
<bean id="newsProvider" class="...FXNewsProvider">
  <property name="newsListener">
    <ref bean="djNewsListener" />
  </property>
  <property name="newPersister">
    <ref bean="djNewsPersister" />
  </property>
</bean>

<bean id="djNewsListener" class="...impl.DownJonesListener" />
<bean id="djNewsPersister" class="...impl.DownJonesPersister" />

最后,从读取配置文件完成对象组装的容器中的获取FXNewsProvider并使用:

...
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("newsProvider");
newsProvider.getAndPersisNews();
  1. 元数据方式:这种方式的代表实现是Google Guice,这是Bob Lee在Java 5的注解和Generic的基础上开发的一套IoC框架。我们可以直接在类中使用元数据信息来标注各个对象间的依赖关系,然后Guice框架根据这些注解所提供的信息将这些对象组装后,交给客户端使用。

Spring的IoC容器之BeanFactory

  我们前面说过,Spring的IoC容器是一个IoC Service Provider,但是,这只是她被冠以IoC之名的部分原因,我们不能忽略的是"容器"。Spring的IoC容器和IoC Service Provider所提供的服务之间存在一定的交集,而这个关系如下图:

Spring提供了两种容器类型:

  • BeanFactory:基础类型的IoC容器,提供完整的IoC服务支持,如果没有特殊指定,默认采用延迟加载策略(lazy-load)。只有当客户端对象需要访问容器中某个受管理对象的时候,才对该受管理对象进行初始化以及依赖注入操作。所以,相对来说,容器启动初期速度较快,所需要的资源有限。对于资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的IoC容器选择。
  • ApplicationContext:ApplicationContext在BeanFactory的基础上构建,是相对比较高级的容器实现,除了拥有BeanFactory的所有支持,ApplicationContext还提供了其它高级特性,比如事件发布、国际化信息支持等。ApplicationContext所管理的对象,在该类型容器启动后,默认全部初始化并绑定完成。所以,相对于BeanFactory来说,ApplicationContext要求更多的系统资源,同时,因为在启动时就完成所有初始化,容器启动时间较之BeanFactory也会长一些。

通过下图,我们可以对BeanFactory和ApplicationContext之间的关系有个更清晰的认知:

  BeanFactory,顾名思义,就是生产Bean的工厂。作为Spring提供的基本IoC容器,BeanFactory可以完成作为IoC Service Provider的所有职责,包括业务对象的注册和对象间依赖关系的绑定。

BeanFactory的对象注册和依赖绑定方式

  BeanFactory作为一个IoC Service Provider为了能够明确管理各个业务对象及业务对象之间依赖绑定关系,同样需要某种途径来记录和管理这些信息。

1.直接编码方式
   public static void main(String[] args) {
        DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
        BeanFactory container = bindViaCode(beanRegistry);
        FXNewsProvider djNewsProvider = (FXNewsProvider) container.getBean("djNewsProvider");
        djNewsProvider.getAndPersisNews();
    }

    private static BeanFactory bindViaCode(DefaultListableBeanFactory registry) {
        AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class);
        AbstractBeanDefinition newsListener = new RootBeanDefinition(DownJonesNewsListener.class);
        AbstractBeanDefinition newsPersister = new RootBeanDefinition(DownJonesNewsPersister.class);

        // 将bean定义注册到容器中
        registry.registerBeanDefinition("djNewsProvider", newsProvider);
        registry.registerBeanDefinition("djListener", newsListener);
        registry.registerBeanDefinition("djPersister", newsPersister);

        // 指定依赖关系
        // 1. 可以通过构造方法注入方式
        ConstructorArgumentValues argValues = new ConstructorArgumentValues();
        argValues.addIndexedArgumentValue(0, newsListener);
        argValues.addIndexedArgumentValue(1, newsPersister);
        newsProvider.setConstructorArgumentValues(argValues);

        // 2.或者通过setter方法注入方式。该方式基于无参构造
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.addPropertyValue("newsListener", newsListener);
        propertyValues.addPropertyValue("newsPersister", newsPersister);
        newsProvider.setPropertyValues(propertyValues);

        // 绑定完成
        return registry;
    }

  BeanFactory只是一个接口,我们最终需要一个该接口的实现类来进行实际的Bean管理。DefaultListableBeanFactory就是这么一个比较通用的BeanFactory实现类。DefaultListableBeanFactory除了间接地实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,该接口才是在BeanFacotry的实现中担当Bean的注册管理的角色。基本上,BeanFacotry接口只定义如何访问容器内管理的Bean方法,各个BeanFacotry的具体实现类负责具体Bean的注册及管理工作。BeanDefinitionRegistry接口定义抽象了Bean的注册逻辑。通常情况下,具体的BeanFacotry实现类会实现这个接口来管理Bean的注册。它们之间的关系如下图所示:

  每一个受管的对象,在容器中都会有一个BeanDefinition的实例与之相对应,该BeanDefinition的实例负责保存对象的所有必要信息,包括其对应的对象的class类型,是否是抽象类,构造方法参数以及其他属性等。当客户端向BeanFactory请求相应对象的时候,BeanFactory会通过这些信息为客户端返回一个完备可用的对象实例,RootBeanDefinition和ChildBeanDefinition是BeanDefinition的两个主要实现类。

2.外部配置文件方式

  Spring的IoC容器支持两种配置文件格式:Properties文件格式和XML文件格式。采用外部配置文件时,Spring的IoC容器有一个统一的处理方式。通常情况下,需要根据不同的外部配置文件格式,给出相应的BeanDefinitionReader实现类,由BeanDefinitionReader的相应的实现类负责将相应的配置文件内容读取并映射到BeanDefinition,然后将映射后的BeanDefinition注册到一个BeanDefinitionRegistry,之后,BeanDefinitionRegistry即完成Bean的注册和加载。当然,大部分工作,包括解析文件格式、装配BeanDefinition之类的工作,都是由BeanDefinitionReader的相应实现类来做的,BeanDefinitionRegistry只不过负责保管而已。

3.注解方式

  在Spring 2.5发布之后,Spring框架开始正式支持基于注解方式的依赖注入。如果不使用classpath-scanning功能的话,任然部分依赖于”基于XML配置文件“的依赖注入方式。

容器背后的秘密

  Spring的IoC容器所起的作用,就如下图所示,它会以某种方式加载Configuration Metadate(通常也就是XML格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。

  Spring的IoC容器所实现以上功能,基本上可以按照类似的流程划分为两个阶段,即容器启动阶段和Bean的实例化阶段。如下图所示:

  1. 容器启动阶段
      容器启动伊始,首先会通过某种途径加载Configuration Metadate。除了代码方式比较直接,在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的Configuration Metadate进行解析和分析,并将分析后的信息编组为相应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器的启动的工作就完成了。如下图演示了这个阶段的主要工作:

  总地来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。当然,一些验证性或者辅助性的工作也可以在这个阶段完成。
2. Bean实例化阶段
  经过第一阶段,现在所有的bean定义信息通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求方通过容器的getBean()方法明确请求某个对象,或者因依赖关系容器需要隐式调用getBean()方法时,就会触发第二阶段的活动。
  该阶段,容器会先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕后,容器会立即将其返回给请求方使用。如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。

插手”容器的启动“

  Spring 提供了一种叫做BeanFactoryPostProcessor的容器扩展机制,该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息进行相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等。

  如果要自定义实现BeanFactoryPostProcessor,通常我们需要实现BeanFactoryPostProcessor接口。同时,因为一个容器可能拥有多个BeanFactoryPostProcessor,这个时候我们可能需要实现类同时实现org.springframework.core.Ordered接口。以保证各个BeanFactoryPostProcessor可以按照预先设定的顺序执行。但是,因为Spring提供了几个现成的BeanFactoryPostProcessor实现类,所以,大多时候,我们很少自己去实现某个BeanFactoryPostProcessor。其中,PropertyPlaceholderConfigurer和PropertyOverrideConfigurer是两个比较常用的BeanFactoryPostProcessor。另外,为了处理配置文件中的数据类型与真正的业务对象所定义的数据类型转换,Spring还允许我们通过CustomEditorConfigurer来注册自定义的PropertyEditor来补助容器中默认的PropertyEditor。

  1. PropertyPlaceholderConfigurer
      通常情况下,我们不想将类似于系统管理相关的信息同业务对象相关的配置信息混杂到XML配置文件中,以免部署或者维护期间因为改动繁杂的XML配置文件而出现问题。我们会将一些数据库连接、邮件服务器等相关信息配置单独配置到一个properties文件中,这样,如果因系统资源变动的话,只需要关注这些简单的properties配置文件即可。
      PropertyPlaceholderConfigurer允许我们在XML配置文件中使用占位符,并将这些占位符所代表的资源单独配置到简单的properties文件中来加载。PropertyPlaceholderConfigurer不单会从其配置的properties文件中加载配置项,同时还会检查System类中的Properties,可以通过setSystemPropertiesMode()或者setPropertiesModeName()来控制是否加载或覆盖System相应的Properties的行为。

  2. PropertyOverrideConfigurer
      可以通过PropertyOverrideConfigurer对容器中配置的任何你想处理的bean定义的property信息进行覆盖替换。当容器中配置多个PropertyOverrideConfigurer对同一个bean定义的同一个property值进行处理的时候,只有最后一个将会生效。

  3. CustomEditorConfigurer
      其它两个BeanFactoryPostProcessor都是通过对BeanDefinition中的数据进行变更以达到某种目的。与它们有所不同的是,CustomEditorConfigurer是另一种类型的BeanFactoryPostProcessor实现,它只是辅助性地将后期会用到的信息注册到容器,对BeanDefinition没有做任何变动。

  我们知道,不管对象是什么类型,也不管这些对象所声明的依赖对象是什么类型,通常都是通过XML(properties甚至其它媒介)文件格式来配置这些对象类型。但XML所记载的,都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用到程序却是由各种类型的对象所构成。要想完成这种由字符串到具体对象的转换,都需要这种转换规则相关的信息,而CustomEditorConfigurer就是帮助我们来传达类似信息的。

  Spring内部通过JavaBean的PropertyEditor来帮助进行String类型到其它类型的转换工作。只要为每种对象类型提供一个PropertyEditor,就可以根据该对象类型取得与其相对应的PropertyEditor来做具体的类型转换。Spring容器内部在做具体的类型转换的时候,会采用JavaBean框架内默认的PropertyEditor搜寻逻辑。同时,Spring框架还提供了自身实现的一些PropertyEditor。这些PropertyEditor大部分都位于org.springframework.beans.propertyeditors包下。以下是这些Spring提供的部分PropertyEditor的简要说明:

  • StringArrayPropertyEditor:该PropertyEditor会将负荷CSV格式的字符串转换成String[]数组形式,默认是以逗号分隔字符串。与此类似的还有ByteArrayPropertyEditor、CharArrayPropertyEditor等都属于类型功能的PropertyEditor。
  • ClassEditor:根据String类型的Class名称,直接将其转换成相应的Class对象,相当于通过Class.forName()完成的功效。
  • FileEditor:Spring提供的对应java.io.File类型的PropertyEditor,同属于对资源进行定位的PropertyEditor还有InputStreamEditor、URLEditor等。
  • LocaleEditor:针对java.util.Locale类型的PropertyEditor。
  • PatternEditor:针对java.util.regex.Pattern的PropertyEditor。
  • 自定义PropertyEditor
    1. 直接实现PropertyEditor接口,不过,通常情况下,我们可以直接继承PropertyEditorSupport类,以避免事项PropertyEditor接口中的所有方法。如果仅仅是支持单向的从String到相应对象类型的转换,只要覆写方法setAsText()即可,如果像支持双向转换,需要同时考虑getAsText()方法的覆写。
    2. 通过CustomEditorConfigurer注册自定义的PropertyEditor。Spring 2.0之前通常是通过CustomEditorConfigurer的customEditors属性来指定自定义的PropertyEditor。2.0之后,比较提倡使用propertyEditorRegistrars属性来指定自定义的PropertyEditor。不过,这样我们就需要再多做一步工作,就是给出一个PropertyEditorRegistrar的实现。
了解bean的一生

  在已经可以借助于BeanFactoryPostProcessor来干预Magic实现的第一个阶段(容器启动阶段)的活动之后,我们就可以开始探索下一个阶段,即bean实例化阶段的实现逻辑了。
  容器启动之后,并不会马上就实例化相应的bean定义。我们知道,容器现在仅仅拥有所有对象的BeanDefinition来保存实例化阶段将要用的必要信息。只要当请求方通过BeanFactory的getBean()方法请求某个对象实例的时候,才有可能触发Bean实例化阶段的活动。BeanFactory的getBean()方法可以被客户端对象显示调用,也可以在容器内部隐式地被调用。隐式调用有如下两种情况:

  • 对于BeanFactory来说,对象实例化默认采用延迟初始化。通常情况下,当对象A被请求而需要第一次实例化的时候,如果它所依赖的对象B之前没有被实例化,那么容器会先实例化对象A依赖的对象。这时容器内部就会首先实例化对象B,以及对象A依赖的其它还没有被实例化的对象。这种情况是容器内部调用getBean(),对于本次请求的请求方是隐式的。
  • ApplicationContext启动之后就会实例化所有的bean定义,但ApplicationContext在实现的过程中依然遵循Spring容器实现流程的两个阶段。只不过它会在启动阶段的活动完成之后,紧接着调用注册到该容器的所有bean定义的实例化方法getBean()。这就是为什么当你得到ApplicationContext类型的容器引用时,容器内所有对象已经被全部被实例化完成。不信你查一下org.springframework.context.support.AbstractApplicationContext的refresh()方法。

  之所以说getBean()方法是有可能触发Bean实例化阶段的活动,是因为只有当对应某个bean定义的getBean()方法第一次被调用时,不管是显示的还是隐式的,Bean实例化阶段的活动才会被触发,第二次调用则会直接返回容器缓存的第一次实例化完的对象实例(prototype类型bean除外)。当getBean()方法内部发现该bean定义之前还没有被实例化之后,会通过createBean()进行具体的对象实例化,实例化过程如下:

提示: 可以在org.springframework.beans.factory.support.AbstractBeanFactory类的代码中查看到getBean()方法的完整代码实现逻辑,可以在其子类org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory的代码中一窥createBean()方法的全貌。

  Spring容器将其所管理的对象全部给予统一的生命周期管理,这些被管理的对象拜托了原来那种”new完后被使用,脱离作用域后被收回“的命运。下面我们将详细看一看每个bean在容器中是如何走过一生的。

  1. Bean的实例化与BeanWrapper
      容器在内部实例化的时候,采用“策略模式”来决定采用何种方式初始化bean实例。通常,可以通过反射或者cglib动态字节码生成来初始化相应的bean实例或者动态生成其子类。InstantiationStrategy是实例化策略的抽象接口,其直接子类SimpleInstantiationStrategy实现了简单的对象实例化功能,可以通过反射来实例化对象实例,但不支持方法注入方式的对象实例化。CglibSubclassingInstantiationStrategy继承了SimpleInstantiationStrategy的以反射方式实例化对象的功能,并且通过cglib的动态字节码生成功能,该策略实现类可以动态生成某个类的子类,进而满足了方法注入所需的对象实例化需求,默认情况下,容器内部采用的是CglibSubclassingInstantiationStrategy。
      容器只要根据bean定义的BeanDefinition取得实例化信息,结合CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。但是,不是直接返回构造完成的对象实例,而是返回相应的BeanWrapper实例,它有一个实现类BeanWrapperImpl,其作用是对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或获取bean的响应属性值。

  2. 各色的Aware接口
      当对象实例化完成并且相关属性以及依赖设置完成后,spring容器会检查当前对象实例是否实现了一系列的以Aware命名结尾的接口定义。如果是,则将这些Aware接口定义中规定的依赖注入给当前对象实例。常见的Aware接口有如下几个:BeanNameAware,BeanClassLoaderAware,BeanFactoryAware。这几个Aware接口只是针对BeanFactory类型的容器而言,对于ApplicationContext类型容器而言,也存在几个Aware相关接口,如ApplicationContextAware,ResourceLoaderAware,ApplicationEventPublisherAware,MessageSourceAware接口,不过检测这些接口并设置相关依赖的实现使用的是BeanPostProcessor。

  3. BeanPostProcessor
      BeanPostProcessor和BeanFactoryPostProcessor的概念容易混淆,但只要记住BeanPostProcessor是存在于对象实例化阶段,而BeanFactoryPostProcessor是存在于容器启动阶段,则很好区分了。
      通常比较常见的使用BeanPostProcessor场景,是处理标记接口实现类,或者为当前对象提供代理实现。上面提到的ApplicationContext对应的那些Aware接口实际上就是通过BeanPostProcessor的方式进行处理的。

  4. InitializingBean和init-method
      InitializingBean是容器内部广泛使用的的一个对象生命周期标识接口,其定义如下:

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

  该接口的定义很简单,其作用在于,在对象实例化过程中调用过“BeanPostProcessor”的前置处理后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则调用afterPropertiesSet()方法进一步调整对象实例的状态。由于让我们的业务对象去实现该接口,会显得Spring容器比较具有侵入性,所以,Spring还提供了通过配置bean的init-method属性来自定义对象的初始化操作。

  1. DisposableBean和destroy-method
      与InitializingBean和init-method用于对象的自定义初始化相对应,DisposableBean和destroy-method为对象提供了执行自定义销毁逻辑的机会。最常见到的该功能的使用场景就是pring容器中注册数据库连接池,在系统退出后,连接池应该关闭,以释放相应资源。

Spring ioC容器之ApplicationContext

  作为Spring提供的较之BeanFactory更为先进的IoC容器,ApplicationContext除了拥有BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProcessor,BeanPostProcessor以及其他特殊类型的bean的自动识别、容器启动后bean实例的自动初始化、国际化信息支持、容器内事件发布等。上一小节说明了ApplicationContext所支持的大部分功能,下面主要围绕ApplicationContext较之BeanFactory特有的一些特性展开讨论,即国际化信息支持、统一资源加载策略以及容器内事件发布等。

统一资源加载策略

  Spring框架内部使用Resource接口作为所有资源的抽象的访问接口。现在有了资源,但如何去查找和定位这些资源,则应该是ResourceLoader的职责了,ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出。或许把ResourceLoader称作统一资源定位器更恰当一些。ResourcePatternResolver——批量查找的ResourceLoader。ResourcePatternResolver是ResourceLoader的扩展,ResourceLoader每次只能根据资源路径返回确定的单个Resource实例,而ResourcePatternResolver则可以根据指定的资源路径匹配模式,每次返回多个Resource实例。

  由于ApplicaitonContext继承了ResourcePatternResolver,当然就间接实现了ResourceLoader接口。所以任何的ApplicaitonContext实现都可以看作是一个ResourceLoader甚至ResourcePatternResolver,而这就是ApplicaitonContext支持Spring内统一资源加载策略的真相。

国际化信息支持

  1. Java SE提供的国际化支持
      程序的国际化不是三言两语可以讲清楚的,它涉及许多内容,如货币的格式、时间的表现形式、各国家和地区的语言文字等。对于Java中的国际化信息处理,主要涉及两个类,即Locale和ResourceBundle。不同的Locale代表不同的国家和地区,每个国家和地区在Locale这里都有相应的简写代码表示,包括语言代码及国家代码,这些代码是ISO标准代码。ResourceBundle用来保存特定于某个Locale的信息,通常,ResourceBundle管理一组信息序列,所有的信息序列都有一个统一的basename,然后特定的Locale的信息,可以根据basename后追加的语言或地区代码来区分。
  2. MessageSource和ApplicationContext
      Spring在Java SE的国际化支持基础上,进一步抽象了国际化信息的访问接口,也就是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;
}

  现在我们知道ApplicationContext除了实现了ResourceLoader以支持统一的资源加载,它还实现了MessageSource接口,那么就跟ApplicationContext因为实现了ResourceLoader而可以当作ResourceLoader来使用一样,ApplicationContext现在也是一个MessageSource了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章