Spring 4.x Reference翻译(一)IOC容器

1、IOC容器

  • 本章对Spring FrameworkIOC实现。IoC也就是依赖注入DI)。依赖注入的过程是这样的,对象只通过构造函数参数、工厂方法参数或者属性来定义它们的依赖;当容器创建好一个对象之后,它会注入对象定义的这些依赖。这个过程就是控制反转,因为它相对于bean自己控制实例化或自己调用依赖类的构造函数(或者使用Service Locator之类的模式)来说是相反的。
  • org.springframework.beansorg.springframework.contextSpring 框架IoC容器的基础。BeanFactory接口提供了一个能够管理任何类型对象的可配置高级机制。ApplicationContext接口(BeanFactory的子接口);它增加了同Spring AOP特性进行集成,消息资源处理(为了使用国际化),事件发布,以及和特定应用层相关的Context,比如:和web应用相关的WebApplicationContext
  • 一句话:BeanFactory提供了配置框架和基础功能,而ApplicationContext则增加了更多企业化的功能。ApplicationContextBeanFactory的超集,在本章都是使用ApplicationContext来描述SpringIoC容器。
  • Spring中,组成应用程序骨架,并被Spring IoC容器进行管理的对象,被称为beans;一个bean就是被Spring IoC容器进行初始化、组装和管理的对象。Beansdependencies是通过容器使用的configuration metadata反映出来的。

1.1容器概述

  • 接口org.springframework.context.ApplicationContext代表了Spring IoC容器,负责对beans进行实例化、配置和组装。容器通过读取configuration metadata来得到它需要实例化、配置和组装什么样的对象。configuration metadata通过XMLJava注解或Java代码来表示,它们允许我们定义组成应用的对象以及对象之间的复杂依赖关系。
  • Spring中已经默认提供了多个ApplicationContext接口的实现;在独立的应用当中,经常会使用类ClassPathXmlApplicationContext FileSystemXmlApplicationContext。虽然定义Configuration metadata的传统格式还是XML,但是现在我们可以使用Java 注解或Java 代码,然后提供少量的XML配置来让Spring来支持这两种形式就可以了。
  • 在大多数的场景下, 使用显示的用户代码来初始化Spring IoC容器是不需要的;比如:在web应用中,使用几行模板似的XML(在web.xml当中)就可以了。如果你使用Spring Tool Suite Eclipse增强开发环境,要得到这些模板配置,只需要点击几次鼠标或敲几下键盘就可以了。
  • 下图是对Spring IoC工作方式的高层次试图。我们的应用类是通过Configuration metadata来组合在一起的。在ApplicationContext创建好并初始化之后,你就有一个完全配置好,并可以执行的应用或系统了。


    ConfigurationMetadata

  • 正如上图所给出的,Spring IoC容器消费一个Configuration Metadata,这个配置代表你作为应用开发者告诉Spring 容器如何初始化、配置和组装应用中的对象的。
  • 一个应用中的bean一般都有多个,在XML中是通过<bean>元素来描述的,在Java代码中,则是 拥有@Configuration类中的@Bean方法来描述的。
  • 这些bean definitions和组成我们应用的真正对象是对应的;典型的,你会定义业务层对象,数据访问层对象(DAOs),展示层对象(比如:Struts中的Action实例),架构对象(比如:HibernateSessionFactoriesJMS Queues等等)。
  • 一般来说,我们不会在容器中配置细粒度的领域对象(domain object),因为DAOs和业务逻辑来负责加载和创建领域对象。
  • 然而,我们可以通过Spring AspectJ来集成和配置哪些不在Spring IoC容器管理范围内的对象。

    实例化容器

  • 实例化一个IoC容器是非常直接的,通过给ApplicationContext的构造函数传入一个或多个配置文件路径,容器就会加载这些配置文件(也叫资源,比如:本地文件系统,或者JavaCLASSPATH等等)中的 configuration metadata

比如:

ApplicationContextcontext =

newClassPathXmlApplicationContext(new String[] {"services.xml","daos.xml"});

    基于XMLConfigurationmetadata组成

  • 除了可以向ApplicationContext中传入多个Resource路径,也可以在某个Resource当中,通过import元素来包含其他Resource。比如:

  • 上图中的services.xml必须在当前importing文件相同目录或classpath当中,messageSource.xmlthemeSource.xml必须在当前importing文件目录的子目录resources当中(对于相对路径,建议把最开始的/去掉)。被importing的配置文件中的元素必须都是有效的。
  • 注意:import元素中,可以通过../相对路径来引用父目录中的文件。但是这么做的话,就会让应用依赖于当前应用之外的一个文件。特别的,这个引用方式不建议用于“classpath:URLs(比如:classpath:../servie.xml),因为Spring在解析的时候会选择一个"nearest" classpath作为根,然后查看它的父目录。如果Classpath配置发生了变化,会导致一个不正确的错误选择。 我们可以使用全路径来代替相对路径来指示资源位置,比如:“file:C:/config/services.xml”或“classpath/config/service.xml”。然而,要记住的是,这样做会把应用和特定的绝对路径耦合在一起了。一个更好的方式使用一种间接地方式来传入这种绝对路径,比如:${…}来获取JVM运行时系统属性。

     使用容器

  • ApplicationContext接口提供了给不同beans及其依赖进行注册的高级工厂。我们可以使用方法

T getBean(String name,Class<T>requiredType)获取bean的实例。

  • ApplicationContext接口还提供了一些其它获取bean的方法,但是我们一般都不会使用到他们,比如:SpringWeb框架进行集成,可以使用ControllerJSF-管理的beans。

1.2 Bean概述

  • 在容器内部,bean的定义是通过BeanDefinition对象来表示的。它由以下属性组成:

1包全路径类名称:定义bean的真正实现类

2bean的行为配置元素:用来定义bean在容器中的行为(scopelifecycle回调等等)

3指向依赖bean的引用:这些被引用的bean也叫做:collaborators(合作者)或dependencies(依赖)

4其它用于设置新创建bean的配置:比如:连接池的初始化连接数目,pool的大小限制等等。

IoC容器中,除了包含BeanDefinition外,ApplicationContext实现还可以允许注册用户不在容器中创建的现存对象。实现这个的流程是:调用ApplicationContextgetBeanFactory方法,获取DefaultListableBeanFactory;然后调用DefaultListableBeanFactoryregisterSingleton(..)registerBeanDefinition(..)方法。不过,典型的应用都是通过configuration metadata来获取创建bean需要的信息。

注意:为了在Autowring和其它introspection步骤当中,容器能够正确地发现它们,Bean Metadata和手工提供的单例bean需要尽可能早的在容器中进行注册。尽管从某种角度来说,覆盖已经存在的metadata或单例bean是可以的,但是在运行时进行bean的注册操作,spring官方是不支持的,因为这样做可能会带来并发访问异常,或者使容器处于不一致的状态。

bean命名

  • 每个bean都有一个以上的标识符,这些标识符在容器中必须是唯一的;一般来说一个bean只有一个标识符,不过如果需要多个,其它被称之为aliases
  • bean元素中,用id来命名bean的唯一id值,通过name来给bean赋予别名(aliases),如果有多个别名,可以使用逗号(,)分号(;)或空白符( )来进行分隔
  • 我们也可以不给bean提供idname,如果这样的话,Spring会自动给bean生成一个唯一ID;但是,如果需要通过ref来引用bean(或Service Locator),就必须提供一个名字。不过在下述场景下是 不用提供bean id的,比如:inner beansAutowiring collaborators
  • Bean命名规范:使用Java实例字段的命名规范,以小写字母开头,然后遵循驼峰命名规则,比如:accountManager等等;bean命名规则保持一致,使得配置文件更容易阅读和理解;如果使用Spring AOP,那么对于给一系列相关bean apply advice非常有帮助。

bean定义之外给bean取别名

  • 可以使用alias元素来进行定义(对于大型系统,配置被按照系统进行分割,每个系统都有一个配置)

<aliasname="fromName" alias="toName"/>

实例化bean

  • 容器会根据传入的bean name来找bean definition,然后根据bean definitionconfiguration metadata来创建一个对象。
  • 使用XML来配置的话,使用bean元素的class属性来实例化bean。对于这个class实例化bean,有两种方式:1)使用反射来调用bean的构造函数来实例化bean 2)调用class内部的static工厂方法来创建bean,工厂方法返回的bean可能是同一个类或者是另外一个类

注意:配置静态内部类的bean definitionclass 属性需要使用:com.example.Foo$Bar(BarFoo的静态内部类)

使用构造函数实例化bean

  • 不仅仅是遵循JavaBean规范的bean,可以被Spring容器进行实例化;哪些不遵循JavaBean规范的bean也能够被Spring容器进行管理。

使用静态工厂方法实例化bean

  • 当你定义了一个通过静态工厂方法来创建的bean,使用bean元素的class属性来说明包含静态工厂方法的类全路径名称,factory-method属性来说明工厂方法的名称。这个工厂方法必须是能够被调用的(权限等)并返回一个对象;对于Spring容器来说,这个返回的对象,就如同构造函数创建的一样。需要注意的是:在XML中不用说明静态工厂方法返回的对象类型。

使用实例工厂方法实例化bean

  • 同使用静态工厂方法一样,可以调用某个实例的非静态工厂方法来创建对象;为了使用这个机制,将class属性置为空,factory-bean属性来说明包含有工厂方法的具体bean的名称;factory-method是工厂方法的名称,它负责创建具体的bean

<!--the factory bean, which contains a method called createInstance() -->

<beanid="serviceLocator"class="examples.DefaultServiceLocator">

<!--inject any dependencies required by this locator bean -->

</bean>

<!--the bean to be created via the factory bean -->

<beanid="clientService"

factory-bean="serviceLocator"

factory-method="createClientServiceInstance"/>

 

  • 一个包含工厂方法的实例可以包括构造不同bean的工厂方法

<beanid="serviceLocator"class="examples.DefaultServiceLocator">

<!--inject any dependencies required by this locator bean -->

</bean>

<beanid="clientService"

factory-bean="serviceLocator"

factory-method="createClientServiceInstance"/>

<beanid="accountService"

factory-bean="serviceLocator"

factory-method="createAccountServiceInstance"/>

1.3 依赖

  • 遵循DI规则,会使代码更加简洁;当对象同它们的依赖一起提供的时候,解耦可以做到更彻底。对象不用查询它的依赖,也不用知道它的依赖所在的位置或类名称。如果这样的话,你的类将会变得更加容易测试,特别是当依赖都是以接口或抽象类的形式出现的时候,因为这样就很容易在单元测试的时候提供stubmock的实现。

基于构造函数的依赖注入

  • 通过向构造函数(或工厂方法)传入参数来注入依赖,每个参数都是一个依赖
  • 构造函数参数的解析是使用类型来匹配的;如果不存在模拟两可的情况,在bean definition中的参数顺序和构造函数的参数顺序是对应的。

packagex.y;

publicclass Foo {

public Foo(Bar bar, Baz baz) {

//...

}

}

上面的BarBaz是没有继承关系的两个类型,那么下面的配置是OK的,不用在constructor-arg元素中说明具体的参数index

<beans>

<beanid="foo" class="x.y.Foo">

<constructor-argref="bar"/>

<constructor-argref="baz"/>

</bean>

<beanid="bar" class="x.y.Bar"/>

<beanid="baz" class="x.y.Baz"/>

</beans>

当使用的是简单类型的时候,比如:<value>true</value>Spring不能确定这个value的类型(是string?还是bool?),也就不能使用类型来进行匹配了,比如下面这个类:

packageexamples;

publicclass ExampleBean {

//Number of years to calculate the Ultimate Answer

privateint years;

//The Answer to Life, the Universe, and Everything

privateString ultimateAnswer;

publicExampleBean(int years, String ultimateAnswer) {

this.years= years;

this.ultimateAnswer= ultimateAnswer;

}

}

怎么解决这个问题呢?

1)在constructor-arg元素中使用type来明确说明类型是什么,比如:

2)在constuctor-arg元素中使用index来明确说明顺序:

3)使用index,还可以解决,构造函数参数中,有相同类型的情况(index是从0开始计数的)

4)在constuctor-arg元素中使用name来说明参数的名称:

不过这种情况,需要在编译代码的时候需要打开debug flag;或者在bean的构造函数或工厂方法上加上@ConstructorProperties注解:

基于setter的依赖注入

  • setter-based DI是在容器调用无参构造函数或无参工厂方法生成bean之后,然后调用set方法来注入依赖。下面的方法只能通过set进行依赖注入:

  • ApplicationContext既支持constructor-basedDI,也支持setter-basedDI;同时它还支持一些bean在通过constuctor-basedDI注入部分属性的时候,再通过setter-based DI注入其它属性。
  • 我们是按照BeanDefinition的形式来配置依赖的,它同PropertyEditor实例一起,将属性从一种格式转换为另外一种。然而绝大数用户都不会直接和这些来打交道;而是通过XML、注解(@Component@Controller等)、基于Java代码的@Configuration类中的@Bean方法,它们最终会被Spring转化为BeanDefinition,然后用于加载整个Spring Ioc容器中的实例。

是使用构造函数注入还是setter注入呢?

  • Spring团队的建议是对于必须要的属性,使用构造函数注入(当然,使用@required注解,可以使setter注入方法的属性是必须的)。如果构造函数有很多参数,那这个类的设计应该存在问题,需要将这个类进行分割了(它负责了太多的功能了)
  • setter-based注入用于可选的依赖,如果不是这样的话,非空检测需要在所有使用这个属性的地方机进行执行。
  • 使用第三方类的时候,如果没有setter函数可以使用,那只能使用构造函数依赖了。

依赖解析过程

  • 容器根据Configuration metadata(用来描述bean的)来创建和初始化ApplicationContextConfiguration metadata可以使用XML、注解和Java Code来进行描述。
  • 对于每一个bean,依赖是通过构造函数参数、属性或静态工厂方法参数的形式来给出的;当bean真正被创建之后,这些依赖会被注入。
  • 每一个属性或构造函数参数都是一个需要set值的定义或者指向本容器的另外一个bean的引用
  • 每个属性或构造函数参数都是从特定的格式转换为它们的真正类型。默认的,Spring可以将字符串转换为所有Java的内置类型,包括:intlongStrringboolean等等
  • 在容器被创建的时候,Spring会对所有bean的配置进行有效性验证。但是,bean的属性此时不会被赋值,直到它们被真正创建的时候。只有那些单例bean,并且设置为pre-instantiated(默认情况就是这样)才会在容器启动的时候被创建。对于其它的bean,只有当bean被请求的时候才会被创建。一个bean的创建会导致和它相关的依赖bean都会被创建(以及依赖的依赖)。在这些依赖beans之间解析不匹配的情况可能会稍晚被发现,比如:在受影响bean第一次创建的时候。

循环依赖

  • 如果主要是使用构造函数注入,有可能会出现循环依赖;比如:类A需要一个类B的实例(通过构造函数注入),类B需要一个类A的实例(通过构造函数注入)。如果我们配置类A和类B相互进行注入,那么Spring IoC容器会发现这种情况,并抛出异常BeanCurrentlyInCreationException
  • 一个解决方案就是,编辑一些类的源代码,将某个构造函数注入,转化为set方式注入。一种可选的方式是,避免进行构造函数注入,都使用set方式注入。也就是说,尽快这种方式不推荐,我们可以通过set方式来配置循环依赖。
  • 和一般的情形不一样(没有循环依赖),bean Abean B的循环依赖会迫使一个bean在它还未完全初始化之前就被注入到了其它bean当中(经典的鸡生蛋和蛋生鸡的问题)。
  • 我们可以信赖Spring会做正确的事情,它会在容器加载的时候,检测到配置错误,比如:引用不存在的bean和循环依赖等等。当bean被真正创建的时候,Spring会尽量晚的设置属性和解析依赖。这就意味着已经加载成功的Spring容器,在我们请求获取某个bean的时候可能会抛出异常,因为创建这个bean或它的依赖存在问题。比如:这个bean有属性没有赋值或属性无效。这种延迟发现配置错误的问题,也是ApplicationContext默认pre-instantiated单例bean的原因。在bean被真正需要之前,使用部分初始化时间和空间来预先初始化,我们会发现ApplicationContext在初始化的时候就发现配置问题,而不是在晚些时候。我们可以覆盖这个默认行为,在单例bean上使用lazy-init
  • 如果没有循环依赖,一个或多个合作bean被注入到依赖这些bean的实例当中,在注入之前,这些bean都是被完全初始化好了的。这也就是说如果A依赖于B,那么在B注入到A之前,Spring IoC容器已经将B完全配置好了。总之,如果一个实例被Spring IoC初始化,也就意味着,它的依赖都被设置,生命周期相关的方法都被调用(比如:init-method)。

依赖和配置的详细说明

  • 直接值(Straight values:在<property/>元素的value属性中,以字符串的形式来说明一个属性或构造函数参数。SpringConversion Service被使用,将String转化为属性或构造函数参数的真正类型。

下面的例子使用p-namespace提供更简洁的XML配置:

上面的XML已经够简洁了,但是错别字只能在运行的时候被发现,而不是设计的时候,除非你使用ItelliJIDEASpring Tool Suite,它们支持在创建bean定义的时候自动补全属性的名称。

 

对于java.util.Properties可以使用下面的方式进行配置:

Spring会将value中的字符串转化为Properties类型(使用PropertyEditor机制)。

 

idref是一个简单的防止错误的方式,它将另外一个bean(在当前容器当中)的id是字符串值,不是引用)赋值给<constructor-arg/><property/>元素。

 

上面的配置和下面的配置是一样的。

但是更建议使用上面的方式,因为idref提供了在部署的时候进行错误的校验(防止将错误的id字符串值赋给了属性);下面的方式则不会进行校验,只能等到实际运行的时候才会被发现,对于prototypebean,可能会过很久才会发现这个问题。

一个常常使用idref的地方是在ProxyFactoryBean定义中的AOP Interceptors配置;使用idref元素可以防止错误拼写一个interceptorid

  • 引用其它的beanref是用来指向另外一个bean,它用在<constructor-arg/><property/>元素当中;它具有localparentbean属性; bean是使用最多的,它可以指向本容器或parent容器里面的bean,而不管指向的bean是否在相同的XML文件当中;属性bean的值和引用beanid值(或名称)一样;属性parent的值,则会引用父容器中的beanid值(或名称),使用parent只有在子容器的bean和父容器的bean具有相同的id,且自容器的bean想作为父容器的bean的代理:

属性local4.0中,已经被删除。

  • 内部beaninner-bean:一个<bean />元素在<constructor-arg/><property/>元素内部,它就定义了一个内部beaninner-bean

一个inner bean的定义不需要idname,如果定义了,容器也不会把这个idname作为内部beanidname,容器也会忽略对inner beanscope定义; inner bean肯定是匿名的,它肯定随着outterbean的创建而创建;对于其它的bean不能引用内部bean作为协作bean或单独访问他们。【未完,待续


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值