ioc-container、bean、denpendies
什么是依赖注入与控制反转?
原文
This chapter covers the Spring Framework implementation of the Inversion of Control (IoC) principle. IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern.
理解
依赖注入
首先容器(IOC container)是spring 最基础的单元。IOC也就是DI依赖注入,创建一个bean时,这个bean往往会有一堆依赖,spring的解决办法是,将这些依赖实例化,然后被bean所依赖。想象一个类,实例化时需要很多依赖的类实例,但是往往又不能每个都创建一次,但是如果将每个类都实例化,被依赖的类只需要保留一个对依赖类的句柄,主类实例化时将依赖的实例注入进去也就完成了实例化,这个实例化的类也成为一个新的容器,被其他类所依赖。
控制反转
试想一下,java中new一个实例,首先需要import 类的路径 比如import java.lang.String
,然后使用它的构造器。也就是控制或定位类的实例靠类的路径或者构造器的方法签名。IOC中,每个容器都有自己的名字name
,通过这个name反找出类的全路径,或者实例。这个过程是相反的。
1、介绍Spring IoC container和 Beans
包org.springframework.beans
和org.springframework.context
是IOC的基础。BeanFactory
接口提供了配置和管理组件的,ApplicationContext
是BeanFactory的子接口,它添加了
- 和Spring AOP特性更便捷的集成
- 消息资源的处理(初始化时使用)
- 事件发布
- 应用层更具体的容器例如
WebApplicationContext
简短的讲,BeanFactory
提供了框架和基础的功能。ApplicationContext
添加了更多企业化的功能,是一个完善的BeanFactory
超级集合仅仅被用来在这个章节里描述IoC容器,更多的是使用BeanFactory
来讲解。
在Spring中,一系列被IoC 容器管理的对象被称为beans。一个bean是一个可以被实例化、聚合或者被IoC容器管理的对象。
2、container 总览
接口org.springframework.context.ApplicationContext
代表了IoC容器,并且负责管理beans。
容器获取自身关于什么对象被实例化、配置和聚合依赖读取元数据信息。这个元数据信息的格式有XML、java注解、java代码。元数据信息能充分表达对象之间的组合关系。
有几个关于ApplicationContext
实现。独立应用模式,通常用
ClassPathXmlApplicationContext
和FileSystemXmlApplicationContext
。当XML被用来当做配置元数据的信息的格式,你可以用一个小的xml文件来配置注解和代码的关系。
大多数场景情况下,无需我们自己定义xml文件,例如web项目生成时有web.xml。
下图是展示spring如何工作:应用的类(pojos)和配置信息(metadata)联合起来,
在 ApplicationContext
被创建和初始化后,你就有一个完整的容器系统被使用。
2.1 Configuration Metadata
spring的配置,spring传统是基于xml配置。
总共有三种配置方式
- 基于xml。见下
- 基于注解。见下 spring 2.5开始支持
- 基于java code代码。见下 spring 3.0开始支持
xml
以<beans>
标签为顶层容器,<bean>
为元素。注解以@Cofiguration
为容器,@Bean
为元素。
定义的这些bean用来拼凑你的应用。典型的,你定义service layer
的对象,data access objects(DAOS)
数据模型,持久化
presentation
对象例如Struts的Action
对象,所使用框架的对象infrastructure objects
例如Hibernate SessionFactories, JMS Queues。典型的你不能定义一些domian
对象,他们依赖DAO和业务逻辑去生成和加载
domian对象,跟业务逻辑相关的动态对象,确切的信息生成确切的对象
xml格式的实例
但是你可以使用Spring 集成的AspectJ
去配置和管理IOC之外呢的对象。
- id 属性标识独立bean的定义
- class 具体说明类的全路径名称
2.2 Instantiating a Container
实例化一个容器。
ApplicationContext
的构造函数支持的一个或多个paths路径来加载上述的configuration 数据
,可以是本地文件系统,或者java的classpath等
形如
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
- 服务层对象的配置
service layer objects(services.xml)
- DAO对象的配置
基于xml-based配置数据
可以使用构造器一次性的加载多个xml文件,也可以使用汇聚多个xml到一个文件。要注意services.xml和做import的文件位于同一目录,且最好使用相对路径而不是以’/'开头的路径,也不推荐用..
。
2.3、使用容器
ApplicationContext
interface是一个高级的工厂容器,包含不同的beans和他们的依赖。通过使用T getBean(String name,Class<T> requiredType)
获取beans
获取定义的beans
比较包容的用法
这种方式可以混合使用
或者加载grovy格式的配置文件
虽然提供了getBean,但是你的应用不应该使用getBean
方式,否则就对spring的API产生了依赖。
3. bean 总览
一个spring的容器(Context)管理了很多bean。
对于容器自身来说,这些bean被BeanDefinition
对象所代表。其包含了下列的元数据。
-
A package-qualified class name: bean实际上的实现类
-
Bean behavioral configuration elements:bean的行为配置,这个bean在容器中的行为(scope, lifecycle callbacks, and so forth).
-
References to other beans that are needed for the bean to do its work. 依赖类的句柄,其依赖信息的定义
-
其他配置信息,例如连接池的size
bean的属性表
元数据信息由一系列的属性组成,下面就是这个表。
Property | 在XXX中解释 |
---|---|
Class | Instantiating Beans |
Name | Naming Beans |
Scope | Bean Scopes |
Constructor arguments | Dependency Injection |
Properties | Dependency Injection |
Autowiring mode | Autowiring Collaborators |
Lazy initialization mode | Lazy-initialized Beans |
Destruction method | Destruction Callbacks |
当然用户也可以自己去实现ApplicationContext
interface来定义如何去生成和加载一个bean。这个是通过ApplicationContext
的BeanFactory–getBeanFactory()方法获取,默认返回DefaultListableBeanFactory
。DefaultListableBeanFactory支持registerSingleton(…) 和registerBeanDefinition(…)方法加载bean。
3.1 Naming Beans
每个bean都有一个或多个标识。这些标识必须在容器中是独一无二的。通常只有一个,如果要求多个,那么多余的就被当做别名。
xml-based
可以使用id
或name
属性,或者一起作为bean的标识。id
一般是字母组成(‘myBean’,‘someService’),如果想有别名,请标记在name属性中
,使用,
、;
或空格分割。请注意id必须是独一无二的。
spring不要求一定要为bean配置id和name。容器会自动为bean生成一个。
如果想去引用一个bean,可以通过ref
元素或者Service Locator风格。
在bean的定义之外重命名一个bean
一次性命名多个别名
可以兼容不同容器。
如果你使用注解 @Bean支持设置别名
3.2 实例化Beans
定义一个bean本质上是为了创建一个或多个对象。当一个bean被需要时,容器就会根据bean的定义来寻找这个或创建一个bean。
如果你用xml-based
来配置元数据。那么class
配置的信息会被设置在上文的BeanDefinition
的class
属性里。
你可以用两种方式使用Class属性。
- 典型的,容器利用反射调用class的构造器,等同于使用new
- 或者具体指明是静态工厂
static
方法去创建实例。使用静态工厂方法创建的类,也许是一个sington或者是全新的实例
inner class names
内部类,如果你想去定义一个有static
嵌套的内部类,你必须使用嵌套类的二进制名称。
比如你有一个类叫SomeThing 在com.example包下,SomeThing里有静态类OtherThing,那么这个OtherThing的class
属性值应该是**com.example.SomeThing O t h e r T h i n g ∗ ∗ 注 意 用 OtherThing**注意用 OtherThing∗∗注意用符号来分割。
使用构造器来实例化
spring支持最好的就是构造器方法,推荐给类一个默认勾走方法。带有参数的构造器请详见后面依赖的注入
使用静态工厂方法实例化
要声明具体的静态方法类是什么。下面的xml指明了静态方法。
实例的静态工厂方法
首先实例化具体的工厂类。
用factory-bean
和
factory-method
来指明实例化的方法。可以有多个
4 dependencies
4.1 DI 依赖注入
依赖注入是指对象事先定义了内部属性,这些属性同过构造器的传参或者工厂方法,属性设置方法将实例和定义的内部属性关联。
DI原则的代码十分干净和有效。并且当内部属性是interface和abstract时,十分便于mock测试。
DI有两种方式
- 基于构造器:constructor-based
- 基于set方法:setter
constructor-based注入
单纯依赖构造器
Constructor Argument 构造器参数的解决
展示了利用<constructor-arg/>
元素注入参数
当注入的参数属性类型和值是已知的简单类别时,靠<value>true</value>
注入
相关类
参数类型match
参数索引match
以0开始
参数名称match
注解
可以使用JDK注解@ConstructorProperties
来进一步指明构造器参数的名称,以便容器方便找到
setter-based DI set方法注入
下面是一个完全基于set方法的注入类
ApplicationContext
支持constructor-based和setter-based DI混合。也支持是在constructor-based设置之后额外使用使用setter-based方法设置。
以BeanDefinition
格式装配依赖时,可以使用PropertyEditor
转化propertie的格式。大多数spring的使用者并不会直接用这些类配置(除了xml),使用注解@Component, @Controller,或者基于@Configuration的@Bean,这些注解内部会转换成BeanDefinition
属性并加载一个完整的Spring Ioc容器实例。
constructor-based或者setter-based DI
由于可以混用c-based和s-based,提倡对于必要的属性使用c-based,可选属性使用s-based。注意使用@Required
注解在一个setter方法上可以指明为必要。
spring team推荐使用c-based DI,因为可以迅速得到一个可用的对象并确保属性不为null
。要注意一个构造器有太多函数是一种坏味道,合理分离。
s-based DI应主要用于可选的依赖性,并设默认值。
依赖解决过程描述
容器解决bean依赖过程如下
ApplicationContext
的创建和初始化依赖configuration metadata
–描述了所有的beans。元数据配置格式可以是xml、java code、annotations- 对于每个bean,其依赖记录在在bean的一堆属性、构造器参数、或者工厂方法的参数中。这些依赖会在生产这个bean时被提供。
- 每个属性或者构造器参数是一个具体的值或者另一个bean的引用句柄。
- 每个属性或者构造器参数的值会被转化为定义的类型。默认可以转化的类型只有内置类型
int、long、String、boolean
等等。
当容器被创建时,spring容器会验证每个bean的配置。然而,在bean被创建出来之前bean的属性是不会被set的。singleton-scoped域下的Bean会在容器创建时创建。
其他的bean会在需要时创建,创建时依赖的类会被先创建。
循环依赖问题
抛出BeanCurrentlyInCreationException
DI示例
setter-based
xml配置示例
相关的类
- constructor-based配置示例
相关的类
- 静态工程方法的配置
相关的类
4.2 Dependencies and Configuration in Detail
配置细节,上一节学会setter和constructor参数的配置,这一届细讲<property/>
和<constructor-arg/>
属性。
直接设置values
直接利用value设置属性值
p-namespace风格
也可以如下配置
idref
明确指明是和id匹配,在 or 中
等价于
会让container验证id对应的bean存不存在,第二种不会验证。
引用其他标签
ref元素在 or 中
一般情况下ref可以直接引用目标,先按找id找,找不到按name找。也是会检查bean存不存在。
但是存在不同容器下id相同的情况。比如你有一个层次化容器的设计。parent容器和sub容器有相同的id,这时用parent
指明。
比如父容器中有个类id="accountService"
,子容器为了代理
"accountService"
会有一个同名的类。
子容器的AOP是接受目标类参数,然后将自己设置为同名 ,这样就实现了代理。
inner bean
一个内部的类不能明确具体的ID或name,定义了容器也不能使用其值做识别。同样也会无视scope
标记,也不可能被注入到其他bean中
集合Collections
,,
集合合并
parent容器有集合,child容器也有自己的集合。child集合的结果是两者合并后的结果。
如上,child的 merage=true
意味着child会继承parent属性,并覆盖相同的值。adminEmails最终的值为
集合合并的限制
不能合并不同的集合类型,否则会有Execption抛出
强类型集合
强类型会被赋值。
null和空String
等价于
对于空值
等价于
p-namespace风格
c-namespace风格
复合属性name
something有属性fred,fred有属性sammy,sammy有属性bob。
4.3 使用 depends-on
bean初始化的先后。
有些依赖关系并不直接,(直接的可以用ref),比如数据库的连接必须在DAO类初始化之前
depends-on表明,两个bean初始化顺序。
4.4 懒加载beans
ApplicationContext默认是希望所有的singleton bean作为初始化进程的一部分被加载,有错误也能被早发现。但是有时bean的行为不确定,不能预先确定加载那些bean,有些bean被请求时才会被加载。
not.lazy会被预先加载而lazy被使用时才会被加载。但是如果lazy被singleton依赖,也会被预先加载。
是否延时加载可以层级化控制,要配置在上
4.5 自动关联
spring Container可以自动建立bean的关联关系。自动关联有以下两个优点。
- 可以显著减少对具体的属性或者构造器参数的需要
- 当依赖升级时,自动关联可以升级配置。例如,如果你需要对一个class添加dependency,该依赖可以自动升级并且完全不用修改原先的配置。因此对开发中的变化多的情形有用,也完全不会对稳定不变的情形有坏处。
当使用XML-based配置,autoware属性可以配置在标签里。autowire有四种属性
模式 | 解释 |
---|---|
no | 默认不开启,bean的引用必须依赖ref |
byName | 依赖property name,如果一个属性是master ,他就会找setMaster(..) 方法 |
byType | 依赖类型的匹配,如果有多个匹配就会报错 |
constructor | 类似byType,但是针对构造器参数 |
自动关联的缺点
- 严格的依赖总能被property和constructor-arg设置满足。但是autowaire无法注入简单类型,比如String等
- autowaire也没有以上两个描述的精确
排除Bean from Autowiring
使用autowire-candidate=false可以解决。
4.6 方法注入
大多数场景,大多数beans都是singleton模式。当一个singleton
需要和另一个singleton bean或者非singleton bean和一个singleton bean关联时,一般你需要定义一个为另一个的属性。但是当bean的生命周期不同时,问题就发生了。假设singleton bean A需要使用非singleton bean B–如果B的每个方法都被B调用。但是容器只会创造一次A,因此A只有一次机会去设置属性。容器不能提供给A一个新的B实例,当每次B被需要的时候。
一个可以的解决办法是放弃部分反转控制。你可以使A实现ApplicationContextAware
接口,通过使用getBean(“B”)方法去每次通过容器来获取B实例来调用相关方法。
如下示例
寻找方法注入
Lookup method injection是一种容器的能力去返回指定bean的方法。典型的是涉及原型bean。
对于动态子类,子类或子类的方法不能为final修饰
单元测试abstract
方法,的注入方法需要abstrace方法的实现方法
具体的方法被具体的类承载
lookup方法不能和工厂方法和@Bean修饰的方法放在一起。因为容器不能创建实例也不能在运行时生成子类
示例
抽象方法必须是如下的格式
方法注入如下
注意prototype,即用即创建如果是singleton,每次都返回同一个实例
等同于使用@Lookup
注解
寻找MyCommand实例的createCommand方法。
也可以如下
进行随意的方法替换
xml-based可以使用replaced-method属性进行存在方法的替换。
示例
意味着replacementComputeValue的reimplemet方法会被当成computeValue方法。以上的xml配置会匹配方法签名为
的方法