Spring IoC 容器
BeanFactory 和相关的接口,比如BeanFactoryAware、DisposableBean、InitializingBean,仍旧保留在 Spring 中,主要目的是向后兼容已经存在的和那些 Spring 整合在一起的第三方框架。
在 Spring 中,有大量对 BeanFactory 接口的实现。其中,最常被使用的是 XmlBeanFactory 类。这个容器从一个 XML 文件中读取配置元数据,由这些元数据来生成一个被配置化的系统或者应用。
在资源宝贵的移动设备或者基于 applet 的应用当中, BeanFactory 会被优先选择。否则,一般使用的是 ApplicationContext,除非你有更好的理由选择 BeanFactory。
ApplicationContext 包含 BeanFactory 所有的功能,一般情况下,相对于 BeanFactory,ApplicationContext 会更加优秀。当然,BeanFactory 仍可以在轻量级应用中使用,比如移动设备或者基于 applet 的应用程序。
最常被使用的 ApplicationContext 接口实现:
- FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径。
- ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
- WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
Spring Bean
下图表达了Bean 与 Spring 容器之间的关系:

Spring 配置元数据
Spring IoC 容器完全由实际编写的配置元数据的格式解耦。有下面三个重要的方法把配置元数据提供给 Spring 容器:
- 基于 XML 的配置文件
- 基于注解的配置
- 基于 Java 的配置
Bean 的作用域
当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton。
Spring 框架支持以下五个作用域,分别为singleton、prototype、request、session和global session,5种作用域说明如下所示,
注意,如果你使用 web-aware ApplicationContext 时,其中三个是可用的。
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean() |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境 |
global-session | 一般用于Portlet应用环境,该运用域仅适用于WebApplicationContext环境 |
singleton 作用域:
默认是singleton,在单例情况下,bean对象在某处的改变会影响到后面或者正在使用该bean的结果。因为一变全变。而prototype则每次获取的都是一个新的实例,所有对某个bean实例改变只对该bean有效,对其他值不变,其他bean创建时都是带默认的创建值状态。
prototype 作用域
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
Spring Bean 生命周期
Bean的生命周期可以表达为:Bean的定义——Bean的初始化——Bean的使用——Bean的销毁
为了定义安装和拆卸一个 bean,我们只要声明带有 init-method 和/或 destroy-method 参数的 。
建议你不要使用 InitializingBean 或者 DisposableBean 的回调方法,因为 XML 配置在命名方法上提供了极大的灵活性。
如果你有太多具有相同名称的初始化或者销毁方法的 Bean,那么你不需要在每一个 bean 上声明初始化方法和销毁方法。框架使用 元素中的 default-init-method 和 default-destroy-method 属性提供了灵活地配置这种情况 。
例子:
注意:当bean声明作用域为scope="prototype" 原型时,是不会执行destroy-method="destroy"方法,调用
context.registerShutdownHook();是不会执行的,因为对于容器来说原型的bean没有被销毁的概念,要用的时候就创建一个,只有当scope="singleton" 单例时才会执行这个destroy()钩子方法。
public class Message {
//在bean对象中声明初始化和结束工作后的方法
public void init(){
System.out.println("----------------init-----------------");
}
public void destroy(){
System.out.println("----------------destroy-----------------");
}
}
在配置bean实例时加入init-method 和 destroy-method
<bean name="message" class="com.self.Message" scope="singleton" init-method="init" destroy-method="destroy">
<property name="msg" value="hello lairf"></property>
</bean>
<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-3.0.xsd"
default-init-method="init"
default-destroy-method="destroy">
Spring Bean 后置处理器
Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。
BeanPostProcessor 接口定义回调方法,你可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。你也可以在 Spring 容器通过插入一个或多个 BeanPostProcessor 的实现来完成实例化,配置和初始化一个bean之后实现一些自定义逻辑回调方法。
你可以配置多个 BeanPostProcessor 接口,通过设置 BeanPostProcessor 实现的 Ordered 接口提供的 order 属性来控制这些 BeanPostProcessor 接口的执行顺序。
BeanPostProcessor 可以对 bean(或对象)实例进行操作,这意味着 Spring IoC 容器实例化一个 bean 实例,然后 BeanPostProcessor 接口进行它们的工作。
ApplicationContext 会自动检测由 BeanPostProcessor 接口的实现定义的 bean,注册这些 bean 为后置处理器,然后通过在容器中创建 bean,在适当的时候调用它。
举例:
实现 BeanPostProcessor 的类获得到的是可以对所有的类都作用的后置处理器(注意需要在beans实例化容器配置文件中配置实例化,才能生效),有点面向切面的感觉,是统一的对所有实例的操作,该接口有两个方法实现:postProcessBeforeInitialization() 和 postProcessAfterInitialization()分别对实例初始化流程的前后进行逻辑处理。且在init-method方法之前,毕竟init-method是在bean实例化后执行的。
public class BeforeInitMessage implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
System.out.println("----------------BeforeInitMessage begin--------------------"+s);
return o;
}
@Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
System.out.println("----------------BeforeInitMessage after--------------------"+s);
return o;
}
}
default-init-method="init"
default-destroy-method="destroy"
<bean name="message" class="com.self.Message" scope="singleton" >
<property name="msg" value="hello lairf"></property>
</bean>
<bean name="beforeInitMessage" class="com.self.BeforeInitMessage"/>
<bean class="com.self.PhoneCall" name="phoneCall" scope="singleton" >
<property name="phoneNumber" value="18959967751"/>
</bean>
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring/appcontext-core.xml");
Message message = (Message)context.getBean("message");
System.out.println("=============================message==="+message.getMsg());
PhoneCall phoneCall = (PhoneCall) context.getBean("phoneCall");
System.out.println("=============================message==="+phoneCall.getPhoneNumber());
context.registerShutdownHook();
}
}
输出:
----------------BeforeInitMessage begin--------------------message
----------------init-----------------
----------------BeforeInitMessage after--------------------message
----------------BeforeInitMessage begin--------------------phoneCall
---------------- PhoneCall init-----------------
----------------BeforeInitMessage after--------------------phoneCall
=============================message===hello lairf
=============================message===18959967751
----------------PhoneCall destroy-----------------
----------------destroy-----------------
Spring Bean 定义继承
子 bean 的定义继承父定义的配置数据。子定义可以根据需要重写一些值,或者添加其他值。
Spring Bean 定义的继承与 Java 类的继承无关,但是继承的概念是一样的。你可以定义一个父 bean 的定义作为模板和其他子 bean 就可以从父 bean 中继承所需的配置。
当你使用基于 XML 的配置元数据时,通过使用父属性,指定父 bean 作为该属性的值来表明子 bean 的定义。
Bean 定义模板
你可以创建一个 Bean 定义模板,不需要花太多功夫它就可以被其他子 bean 定义使用。在定义一个 Bean 定义模板时,你不应该指定类的属性,而应该指定带 true 值的抽象属性,即abstract="true"
父 bean 自身不能被实例化,因为它是不完整的,而且它也被明确地标记为抽象的。当一个定义是抽象的,它仅仅作为一个纯粹的模板 bean 定义来使用的,充当子定义的父定义使用。
实例:
<bean id="templateBean" abstract="true">
<property name="msg" value="hello world"></property>
</bean>
<bean name="message" class="com.self.Message" scope="singleton" >
<property name="msg" value="hello lairf"></property>
</bean>
<bean name="textMessage" class="com.self.TextMessage" parent="templateBean">
输出
=============================message===hello lairf
=============================message1===hello world
Spring 依赖注入
Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。
基于构造函数和基于 setter 方法的 两种DI方法。
序号 | 依赖注入类型 & 描述 |
---|---|
1 | Constructor-based dependency injection当容器调用带有多个参数的构造函数类时,实现基于构造函数的 DI,每个代表在其他类中的一个依赖关系。 |
2 | Setter-based dependency injection基于 setter 方法的 DI 是通过在调用无参数的构造函数或无参数的静态工厂方法实例化 bean 之后容器调用 beans 的 setter 方法来实现的。 |
Spring 基于构造函数的依赖注入实例
<bean id="tire" class="com.self.Tire" />
<bean id="vehicle" class="com.self.Vehicle">
<constructor-arg ref="tire"/>
</bean>
----------------------------------------------
<bean id="student" class="com.self.Student">
<constructor-arg index="0" value="29"/>
<constructor-arg index="1" value="lairf"/>
</bean>
Vehicle vehicle = (Vehicle)context.getBean("vehicle");
vehicle.turnOn();
Student student = (Student)context.getBean("student");
student.basicInfo();
public class Student {
private Integer age;
private String name;
public Student(Integer age, String name) {
this.age = age;
this.name = name;
}
public void basicInfo(){
System.out.println("age:"+age+"------"+"name :"+ name);
}
}
public class Vehicle {
private Tire tire;
public Vehicle(Tire tire) {
System.out.println("---------------Vehicle constructor");
this.tire =tire;
}
public void turnOn(){
tire.span();
}
}
public class Tire {
public Tire() {
System.out.println("---------------tire constructor");
}
public void span(){
System.out.println("---------------Tire span");
}
}
Spring 基于设值函数的依赖注入
当容器调用一个无参的构造函数或一个无参的静态 factory 方法来初始化你的 bean 后,通过容器在你的 bean 上调用设值函数,基于设值函数的 DI 就完成了。
注意定义在基于构造函数注入和基于设值函数注入中的 Beans.xml 文件的区别。唯一的区别就是在基于构造函数注入中,我们使用的是〈bean〉标签中的〈constructor-arg〉元素,而在基于设值函数的注入中,我们使用的是〈bean〉标签中的〈property〉元素。
第二个你需要注意的点是,如果你要把一个引用传递给一个对象,那么你需要使用 标签的 ref 属性,而如果你要直接传递一个值,那么你应该使用 value 属性。
配置文件可以使用 p-namespace 以一种更简洁的方式重写 。另一种property标签。
-ref 部分表明这不是一个直接的值,而是对另一个 bean 的引用。
实例:
<bean id="softEngineer" class="com.self.SoftEngineer">
<property name="computer" ref="computer"/>
</bean>
<bean id="computer" class="com.self.Computer"/>
<bean id="wife" class="com.self.Wife" p:name="maomao" p:location="China" p:postCode="362016"></bean>
<bean id="husband" class="com.self.Husband" p:name="Jack" p:spouse-ref="wife"></bean>
public class SoftEngineer {
public SoftEngineer() {
System.out.println("----------SoftEngineer constructor");
}
private Computer computer;
public Computer getComputer() {
return computer;
}
public void setComputer(Computer computer) {
this.computer = computer;
}
public void useComputer(){
computer.coding();
}
}
public class Computer {
public Computer() {
System.out.println("-------------Computer constructor");
}
public void coding(){
System.out.println("--------------product codes");
}
}
SoftEngineer softEngineer = (SoftEngineer)context.getBean("softEngineer");
softEngineer.useComputer();
Spring 注入内部 Beans
正如你所知道的 Java 内部类是在其他类的范围内被定义的,同理,inner beans 是在其他 bean 的范围内定义的 bean。因此在 或 元素内 元素被称为内部bean 。
其实内部bean并不是对应java的内部类,就是一个要被依赖注入的bean,只是配置bean写法有点不同而已。
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<property name="spellChecker">
<bean id="spellChecker" class="com.tutorialspoint.SpellChecker"/>
</property>
</bean>
Spring 注入集合
你已经看到了如何使用 value 属性来配置基本数据类型和在你的 bean 配置文件中使用<property>
标签的 ref 属性来配置对象引用。这两种情况下处理奇异值传递给一个 bean。
现在如果你想传递多个值,如 Java Collection 类型 List、Set、Map 和 Properties,应该怎么做呢。为了处理这种情况,Spring 提供了四种类型的集合的配置元素,如下所示:
元素 | 描述 |
---|---|
<list> | 它有助于连线,如注入一列值,允许重复。 |
<set> | 它有助于连线一组值,但不能重复。 |
<map> | 它可以用来注入名称-值对的集合,其中名称和值可以是任何类型。 |
<props> | 它可以用来注入名称-值对的集合,其中名称和值都是字符串类型。 |
你可以使用<list>
或<set>
来连接任何 java.util.Collection
的实现或数组。
你会遇到两种情况(a)传递集合中直接的值(b)传递一个 bean 的引用作为集合的元素。
注入 null 和空字符串的值
如果你需要传递一个空字符串作为值,那么你可以传递它,如下所示:
<bean id="..." class="exampleBean">
<property name="email" value=""/>
</bean>
前面的例子相当于 Java 代码:exampleBean.setEmail("")。
如果你需要传递一个 NULL 值,那么你可以传递它,如下所示:
<bean id="..." class="exampleBean">
<property name="email"><null/></property>
</bean>
前面的例子相当于 Java 代码:exampleBean.setEmail(null)。
例子:
public class Company {
private List hrList;
private Set engineerSet;
private Map subCompanyMap;
private Properties LeaderProps;
private String[] luckinArr;
public String[] getLuckinArr() {
return luckinArr;
}
public void setLuckinArr(String[] luckinArr) {
this.luckinArr = luckinArr;
}
public List getHrList() {
return hrList;
}
public void setHrList(List hrList) {
this.hrList = hrList;
}
public Set getEngineerSet() {
return engineerSet;
}
public void setEngineerSet(Set engineerSet) {
this.engineerSet = engineerSet;
}
public Map getSubCompanyMap() {
return subCompanyMap;
}
public void setSubCompanyMap(Map subCompanyMap) {
this.subCompanyMap = subCompanyMap;
}
public Properties getLeaderProps() {
return LeaderProps;
}
public void setLeaderProps(Properties leaderProps) {
LeaderProps = leaderProps;
}
}
<bean id="hrPeople" class="com.self.HrPeople"/>
<bean id="company" class="com.self.Company">
<property name="hrList">
<list>
<value>yingjie</value>
<value>zhangsan</value>
<value>yanjihong</value>
<value>yingjie</value>
<ref bean="hrPeople"/>
</list>
</property>
<property name="engineerSet">
<set>
<value>lairf</value>
<value>liudd</value>
<value>zhoujh</value>
<ref bean="hrPeople"/>
</set>
</property>
<property name="leaderProps">
<props>
<prop key="mmc">chensc</prop>
<prop key="kefu">laowang</prop>
<prop key="fcar">xiaoqi</prop>
</props>
</property>
<property name="subCompanyMap">
<map>
<entry key="xiamen" value="mmc"></entry>
<entry key="tianjin" value="uc"></entry>
<entry key="beijing" value="zc"></entry>
<entry key="shanghai" value="fcar"></entry>
<entry key="quanzhou" value-ref="hrPeople"/>
</map>
</property>
<property name="luckinArr">
<array>
<value>chenxf</value>
<value>zhutt</value>
<value>maoyh</value>
</array>
</property>
Company company = (Company)context.getBean("company");
System.out.println(company.getHrList());
System.out.println(company.getEngineerSet());
System.out.println(company.getLeaderProps());
System.out.println(company.getSubCompanyMap());
System.out.println(Arrays.toString(company.getLuckinArr()));
Spring Beans 自动装配
你已经学会如何使用<bean>
元素来声明 bean 和通过使用 XML 配置文件中的<constructor-arg>
和<property>
元素来注入 。
Spring 容器可以在不使用<constructor-arg>
和<property>
元素的情况下自动装配相互协作的 bean 之间的关系,这有助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量。
自动装配模式
下列自动装配模式,它们可用于指示 Spring 容器为来使用自动装配进行依赖注入。你可以使用<bean>
元素的 autowire 属性为一个 bean 定义指定自动装配模式。
模式 | 描述 |
---|---|
no | 这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。你不用为了连线做特殊的事。在依赖注入章节你已经看到这个了。 |
byName | 由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。然后尝试匹配,并且将它的属性与在配置文件中被定义为相同名称的 beans 的属性进行连接。 |
byType | 由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。然后如果它的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。如果存在不止一个这样的 bean,则一个致命的异常将会被抛出。 |
constructor | 类似于 byType,但该类型适用于构造函数参数类型。如果在容器中没有一个构造函数参数类型的 bean,则一个致命错误将会发生。 |
autodetect | Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。 |
可以使用 byType 或者 constructor 自动装配模式来连接数组和其他类型的集合。
自动装配的局限性
当自动装配始终在同一个项目中使用时,它的效果最好。如果通常不使用自动装配,它可能会使开发人员混淆的使用它来连接只有一个或两个 bean 定义。不过,自动装配可以显著减少需要指定的属性或构造器参数,但你应该在使用它们之前考虑到自动装配的局限性和缺点。
限制 | 描述 |
---|---|
重写的可能性 | 你可以使用总是重写自动装配的 <constructor-arg>和 <property> 设置来指定依赖关系。 |
原始数据类型 | 你不能自动装配所谓的简单类型包括基本类型,字符串和类。 |
混乱的本质 | 自动装配不如显式装配精确,所以如果可能的话尽可能使用显式装配。 |
网友评论