标签:
1、IOC容器
- 本章对Spring Framework的IOC实现。IoC也就是依赖注入(DI)。依赖注入的过程是这样的,对象只通过构造函数参数、工厂方法参数或者属性来定义它们的依赖;当容器创建好一个对象之后,它会注入对象定义的这些依赖。这个过程就是控制反转,因为它相对于bean自己控制实例化或自己调用依赖类的构造函数(或者使用Service
Locator之类的模式)来说是相反的。
- 包org.springframework.beans和org.springframework.context是Spring
框架IoC容器的基础。BeanFactory接口提供了一个能够管理任何类型对象的可配置高级机制。ApplicationContext接口(BeanFactory的子接口);它增加了同Spring
AOP特性进行集成,消息资源处理(为了使用国际化),事件发布,以及和特定应用层相关的Context,比如:和web应用相关的WebApplicationContext。
- 一句话:BeanFactory提供了配置框架和基础功能,而ApplicationContext则增加了更多企业化的功能。ApplicationContext是BeanFactory的超集,在本章都是使用ApplicationContext来描述Spring的IoC容器。
- 在Spring中,组成应用程序骨架,并被Spring
IoC容器进行管理的对象,被称为beans;一个bean就是被Spring
IoC容器进行初始化、组装和管理的对象。Beans和dependencies是通过容器使用的configuration
metadata反映出来的。
1.1容器概述
- 接口org.springframework.context.ApplicationContext代表了Spring
IoC容器,负责对beans进行实例化、配置和组装。容器通过读取configuration
metadata来得到它需要实例化、配置和组装什么样的对象。configuration metadata通过XML、Java注解或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实例),架构对象(比如:Hibernate的SessionFactories,JMS
Queues等等)。
- 一般来说,我们不会在容器中配置细粒度的领域对象(domain object),因为DAOs和业务逻辑来负责加载和创建领域对象。
- 然而,我们可以通过Spring
的AspectJ来集成和配置哪些不在Spring
IoC容器管理范围内的对象。
实例化容器
- 实例化一个IoC容器是非常直接的,通过给ApplicationContext的构造函数传入一个或多个配置文件路径,容器就会加载这些配置文件(也叫资源,比如:本地文件系统,或者Java的CLASSPATH等等)中的
configuration metadata。
比如:
ApplicationContextcontext =
newClassPathXmlApplicationContext(new String[] {"services.xml","daos.xml"});
基于XML的Configurationmetadata组成
- 除了可以向ApplicationContext中传入多个Resource路径,也可以在某个Resource当中,通过import元素来包含其他Resource。比如:
- 上图中的services.xml必须在当前importing文件相同目录或classpath当中,messageSource.xml和themeSource.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的方法,但是我们一般都不会使用到他们,比如:Spring和Web框架进行集成,可以使用Controller或JSF-管理的beans。
1.2 Bean概述
- 在容器内部,bean的定义是通过BeanDefinition对象来表示的。它由以下属性组成:
1)包全路径类名称:定义bean的真正实现类
2)bean的行为配置元素:用来定义bean在容器中的行为(scope、lifecycle回调等等)
3)指向依赖bean的引用:这些被引用的bean也叫做:collaborators(合作者)或dependencies(依赖)
4)其它用于设置新创建bean的配置:比如:连接池的初始化连接数目,pool的大小限制等等。
在IoC容器中,除了包含BeanDefinition外,ApplicationContext实现还可以允许注册用户不在容器中创建的现存对象。实现这个的流程是:调用ApplicationContext的getBeanFactory方法,获取DefaultListableBeanFactory;然后调用DefaultListableBeanFactory的registerSingleton(..)和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提供id或name,如果这样的话,Spring会自动给bean生成一个唯一ID;但是,如果需要通过ref来引用bean(或Service
Locator),就必须提供一个名字。不过在下述场景下是
不用提供bean id的,比如:inner
beans和Autowiring collaborators
- Bean命名规范:使用Java实例字段的命名规范,以小写字母开头,然后遵循驼峰命名规则,比如:accountManager等等;bean命名规则保持一致,使得配置文件更容易阅读和理解;如果使用Spring
AOP,那么对于给一系列相关bean apply advice非常有帮助。
在bean定义之外给bean取别名
- 可以使用alias元素来进行定义(对于大型系统,配置被按照系统进行分割,每个系统都有一个配置)
<aliasname="fromName" alias="toName"/>
实例化bean
- 容器会根据传入的bean name来找bean
definition,然后根据bean definition的configuration
metadata来创建一个对象。
- 使用XML来配置的话,使用bean元素的class属性来实例化bean。对于这个class实例化bean,有两种方式:1)使用反射来调用bean的构造函数来实例化bean
2)调用class内部的static工厂方法来创建bean,工厂方法返回的bean可能是同一个类或者是另外一个类
注意:配置静态内部类的bean
definition,class
属性需要使用:com.example.Foo$Bar(Bar是Foo的静态内部类)
使用构造函数实例化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规则,会使代码更加简洁;当对象同它们的依赖一起提供的时候,解耦可以做到更彻底。对象不用查询它的依赖,也不用知道它的依赖所在的位置或类名称。如果这样的话,你的类将会变得更加容易测试,特别是当依赖都是以接口或抽象类的形式出现的时候,因为这样就很容易在单元测试的时候提供stub或mock的实现。
基于构造函数的依赖注入
- 通过向构造函数(或工厂方法)传入参数来注入依赖,每个参数都是一个依赖
- 构造函数参数的解析是使用类型来匹配的;如果不存在模拟两可的情况,在bean definition中的参数顺序和构造函数的参数顺序是对应的。
packagex.y;
publicclass Foo {
public Foo(Bar bar, Baz baz) {
//...
}
}
上面的Bar和Baz是没有继承关系的两个类型,那么下面的配置是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-based的DI,也支持setter-based的DI;同时它还支持一些bean在通过constuctor-based的DI注入部分属性的时候,再通过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的)来创建和初始化ApplicationContext。Configuration
metadata可以使用XML、注解和Java
Code来进行描述。
- 对于每一个bean,依赖是通过构造函数参数、属性或静态工厂方法参数的形式来给出的;当bean真正被创建之后,这些依赖会被注入。
- 每一个属性或构造函数参数都是一个需要set值的定义或者指向本容器的另外一个bean的引用
- 每个属性或构造函数参数都是从特定的格式转换为它们的真正类型。默认的,Spring可以将字符串转换为所有Java的内置类型,包括:int,long,Strring,boolean等等
- 在容器被创建的时候,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 A和bean
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属性中,以字符串的形式来说明一个属性或构造函数参数。Spring的Conversion
Service被使用,将String转化为属性或构造函数参数的真正类型。
下面的例子使用p-namespace提供更简洁的XML配置:
上面的XML已经够简洁了,但是错别字只能在运行的时候被发现,而不是设计的时候,除非你使用ItelliJIDEA或Spring
Tool Suite,它们支持在创建bean定义的时候自动补全属性的名称。
对于java.util.Properties可以使用下面的方式进行配置:
Spring会将value中的字符串转化为Properties类型(使用PropertyEditor机制)。
idref是一个简单的防止错误的方式,它将另外一个bean(在当前容器当中)的id(是字符串值,不是引用)赋值给<constructor-arg/>或<property/>元素。
上面的配置和下面的配置是一样的。
但是更建议使用上面的方式,因为idref提供了在部署的时候进行错误的校验(防止将错误的id字符串值赋给了属性);下面的方式则不会进行校验,只能等到实际运行的时候才会被发现,对于prototype的bean,可能会过很久才会发现这个问题。
一个常常使用idref的地方是在ProxyFactoryBean定义中的AOP
Interceptors配置;使用idref元素可以防止错误拼写一个interceptor的id。
- 引用其它的bean:ref是用来指向另外一个bean,它用在<constructor-arg/>或
<property/>元素当中;它具有local、parent和bean属性;
bean是使用最多的,它可以指向本容器或parent容器里面的bean,而不管指向的bean是否在相同的XML文件当中;属性bean的值和引用bean的id值(或名称)一样;属性parent的值,则会引用父容器中的bean的id值(或名称),使用parent只有在子容器的bean和父容器的bean具有相同的id,且自容器的bean想作为父容器的bean的代理:
属性local在4.0中,已经被删除。
- 内部bean(inner-bean):一个<bean
/>元素在<constructor-arg/>或
<property/>元素内部,它就定义了一个内部bean(inner-bean)。
一个inner bean的定义不需要id或name,如果定义了,容器也不会把这个id或name作为内部bean的id或name,容器也会忽略对inner
bean的scope定义;
inner bean肯定是匿名的,它肯定随着outterbean的创建而创建;对于其它的bean不能引用内部bean作为协作bean或单独访问他们。【未完,待续】
Spring 4.x Reference翻译(一)IOC容器
标签:
原文地址:http://blog.csdn.net/qq_28674045/article/details/51362239