1、Spring IOC容器和Bean简介
IOC也称为依赖注入(DI)。IOC是指: 对象通过构造函数参数、工厂方法的参数或从工厂方法返回后在对象实例上设置的属性来定义其依赖项, 然后容器在创建Bean时注入那些依赖项。本质上是通过使用类的直接构造或诸如服务器定位之类的控件来控制其依赖项的实例化。
Spring框架中org.spring.framework.context和org.spring.framework.beans软件包时实现IOC的基础。BeanFactory接口提供了一种高级配置机制, 能够Management任何类型的对象, 提供了配置框架和基本功能。ApplicationContext是BeanFactory的子接口, 添加了更多企业定制的功能。它增加了:
- 与Spring的AOP功能轻松集成。
- 消息资源处理(用于国际化)。
- Event publication。
- 特定于应用程序的上下文。比如用于Web应用程序的
WebApplicationContext。
在Spring中, 构成应用程序并由IOC容器Management的对象称之为bean。 Bean是由Spring IOC容器初始化、组装和其他方式Management的对象, 否则他只是一个普通的类。Bean及其之间的依赖关系反映在容器使用的配置元数据中。
2、IOC容器概述
org.spring.framework.context.ApplicationContext接口代表Spring IOC容器, 并负责实例化、配置和组装Bean。
容器通过读取配置元数据来获取有关要实例化、配置和组装哪些对象那个的指令。 配置元数据以XML、Java Annotation、Java代码表示。
Spring提供了ApplicationContext的几种接口实现。通常在独立应用程序中, 通过创建ClassPathXmlApplicationContext和FileSystemXmlApplicationContext实例来获取IOC容器。
Spring工作原理的高级视图如下:

2.1 配置元数据
Spring IOC容器使用一种形式的配置元数据。此配置元数据表示告诉Spring容器如何实例化、配置和组装应用程序中的对象。传统中,Spring一般使用XML的格式配置元数据。
Spring提供了3种配置元数据的方式:
- 基于XML的配置方式。
- 基于Annotation-based configuration的配置方式, Spring 2.5 引入了对基于注解的配置元数据的支持。
- 基于Java-based configuration
的配置方式,从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能成为了核心 Spring Framework 的一部分。因此,您可以使用 Java 而不是 XML 文件来定义应用程序类外部的 bean。要使用这些新功能,请参见@Configuration,@Bean,@Import和@DependsOn注解。
Spring配置由容器Management的至少一个bean定义组成。基于XML的配置元数据将这些beans配置为顶级<beans>元素内的<bean>元素, 基于Java配置通常在@Configuation类中定义@Bean注解的方法。
这些beans定义对应于组成应用程序的实际对象, 通常定义服务层对象(service)、数据访问层对象(dao)、表示层对象(Structs action对象)、基础结构对象等。
XML配置的示例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="..."> (1) (2)
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
2.2、实例化容器
提供给ApplicationContext构造函数的位置路径是资源字符串, 这些资源字符串使容器可以从各种外部资源(例如本地文件系统, Java classpath)等加载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
以下示例显示了服务层配置文件service.xml的内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
构成基于XML的配置元数据
通常,每个单独的XML配置文件都代表体系结构中的逻辑层或模块。为了使程序的逻辑更加简洁独立, 一般应用程序中会存在多个配置文件,可以在应用程序中使用容器的构造函数从多个XML配置文件中加载bean定义,也可以使用<import/>元素从另一个配置文件中加载bean定义。
如下显示如何从多个配置文件中引入service.xml的dao.xml的bean定义:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
2.3、使用容器
ApplicationContext是高级工厂的接口,该工厂能维护不同bean及其依赖关系的注册表。通常使用T getBean(String name, Class<T> class)可以检索Bean的实例。
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
ApplicationContext接口还提供了其他几种检索Bean的方法, 但是理想情况下,永远不要使用它们。实际上基于Spring框架,应该根本不用调用getBean()方法, 因此完全不用依赖于Spring API。
3、Bean概述
Spring IOC容器Management一个或多个bean。这些bean是使用提供给容器的配置元数据创建的。 在容器本身内部,这些bean表示为BeanDefinition对象, 其中包含以下元数据:
- 包限定的类名: 通常, 定义了Bean的实际实现类。
- Bean行为配置元素: 用于声明Bean在容器中的行为(作用域、生命周期回调)等。
- 依赖项: 引用其他Bean进行工作所需的Bean。
- 要在新创建的配置中设置的其他配置项,例如池的大小或在Management连接池的bean的使用的连接数。
该元数据转换为构建Bean的每一组属性, 如下所示:
| 属性名称 | 说明 |
|---|---|
| Class | Bean的全限定类名 |
| Name | Bean 的名称 |
| Scope | Bean的生命周期 |
| Constructor arguments | Bean构造函数注入的参数 |
| Properties | Bean属性注入的参数 |
| Autowiring mode | 自动装配模式 |
| LazyInit mode | 延迟初始化模式 |
| Initialization method | 初始化方法回调 |
| Destruction method | 销毁方法回调 |
| FactoryBean Name | 工厂注入的Bean名称 |
| FactoryMethod Name | 工厂构建的方法名称 |
除过包含有关如何创建特定bean的信息的bean定义之外, ApplicationContext实现还允许注册在容器外部的现有对象。 这是通过getBeanFactory()方法访问 ApplicationContext 的 BeanFactory来完成的,该方法返回 BeanFactoryDefaultListableBeanFactory的实现。 DefaultListableBeanFactory通过registerSingleton(..)和registerBeanDefinition(..)方法支持此注册。但是,典型的应用程序只能与通过常规 bean 定义元数据定义的 bean 一起使用。
3.1、命名Bean
每个bean具有一个或多个标识符。这些标识符在承载bean的IOC容器中必须唯一。 一个bean通常只有一个标识符,如果有多个,则可以将多余的视为别名。
在基于XML的配置元数据中, 可以使用id属性和name属性, 也可以同时指定这两个属性。id属性可以为bean精确指定一个ID, 如果要为bean指定别名, 可以通过name属性指定。 bean id的唯一性由容器强制检查(此前有XML解析器执行)。
Bean可以不指定 id和name属性, 如果不指定,容器会为bean生成一个唯一ID。 如果需要 按照名称检索bean, 则必须提供一个名称。
Bean别名
XML的配置方式可以使用alias标签来配置别名。
<alias name="fromName" alias="toName"/>
Java-configuation方式使用@Bean方式可以提供别名。
3.2、 实例化Bean
Bean定义实质上创建一个或多个对象的方法。当被询问时, 容器会查看Bean的配置, 并使用bean定义封装的配置元数据来创建(或获取)实际对象。
用构造函数实例化
当使用构造函数创建一个Bean时, 所有普通的类都可以被Spring使用并与之兼容。Spring IOC容器几乎可以Management任何您要Management的类, 不仅限于JavaBean。它仅需要具备默认的无参构造函数, 并具有通过属性建模的适当的setter和getter方法即可。
例如, 使用XML配置元数据的时候, 可以通过如下方法指定Bean:
<bean id="exampleBean" class="org.example.Something">
<bean name="exampleBean2" class="org.example.Otherthing">
用静态工厂方法实例化
定义使用静态工厂方法创建的bean时,需要使用class属性指定包含静态工厂方法的类, 并使用名为factory-method的属性来指定工厂方法本身的名称。
例如, XML配置:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
静态工厂Java代码:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
用实例工厂方法实例化
与通过静态工厂方法实例化类似, 使用实例工厂方法实例化是容器调用现有的bean的非静态方法以创建新bean。 要使用此方式, bean的配置元数据中class属性需要留空, 并且在factory-bean属性中指定要创建该实例对象的实例方法的bean名称。
例如, XML配置:
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
对应的Java代码:
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类也可以包含一个以上的工厂方法。
4、Dependencies
典型的企业应用程序不包含单个对象(或 Spring 术语中的 bean)。即使是最简单的应用程序,也有一些对象可以协同工作,以呈现最终用户视为一致的应用程序。下一部分将说明如何从定义多个独立的 Bean 定义到实现对象协作以实现目标的完全实现的应用程序。
4.1、依赖注入(DI)
依赖注入是一个过程, 通过该过程, 对象只能通过构造函数参数、工厂方法参数或构造实例后在实例上面设置属性来定义其依赖关系。容器在创建bean时注入依赖项。
DI主要有两种形式: 基于构造函数的依赖注入和基于Setter的依赖注入。
基于构造函数的依赖注入
基于构造函数的DI是通过容器调用具有多个参数的构造函数来完成的。 调用带有特定参数的静态工厂方法构造Bean几乎是等效的。
以下示例显示只能基于构造函数的依赖注入的类:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
-
构造函数参数解析
构造函数参数的解析通过使用参数的类型进行。如果Bean定义的构造参数含义不存在歧义, 则在实例化Bean时,在Bean中定义的的构造函数参数的顺序就是构建Bean时使用的构造函数参数的顺序。
假设以下类为例:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
如果 ThingTwo和ThingThree没有通过继承关联, 且不存在任何歧义,因此可以通过下面这种配置正常工作, 无需在<conructor-arg>元素中指参数的索引或者类型。
<beans>
<bean id="thingOne" class="x.y.ThingOne">
<constructor-arg ref="thingTwo"/>
<constructor-arg ref="thingThree"/>
</bean>
<bean id="thingTwo" class="x.y.ThingTwo"/>
<bean id="thingThree" class="x.y.ThingThree"/>
</beans>
但是, 当使用简单类型时,Spring无法确定值的类型,因此在没有额外的帮助的情况下无法按照参数类型进行匹配。比如:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
-
构造函数参数类型匹配
上述类型, 可以使用type属性显示指定构造函数参数的类型,则容器可以使用简单的类型匹配。例如:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
-
构造函数参数索引
可以使用index属性来显示指定构造参数的索引,如下例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
它不仅可以解决多个简单值的歧义问题, 还可以解决多个参数类型相同的问题。
-
构造函数参数名称
可以使用构造函数参数的名称来消除歧义, 如下:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
使用以上方式, 在Spring中需要启用调试状态的情况下编译代码, 以便Spring可以从构造函数中查找参数名称。或者使用
@ConstructorProperties JDK注解 显式命名构造函数参数。
基于Setter的依赖注入
基于Setter的DI实在调用无参的构造函数或静态工厂方法以实例化对应的Bean, 然后在Bean上调用setter方法来完成的。
下面显示一个只能基于Setter来进行依赖注入的类:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext支持其 Management 的 bean 的基于构造函数和基于 setter 的 DI。在已经通过构造函数方法注入了某些依赖项之后,它还支持基于 setter 的 DI。