在这之前首先了解一下Spring Bean的加载API接口
BeanFactory与Application的区别
BeanFactory 才是 Spring 容器中的顶层接口。
ApplicationContext 是它的子接口。
BeanFactory 和 ApplicationContext 的区别:
创建对象的时间点不一样。
ApplicationContext:他采用的是立即加载的策略,只要一读取配置文件,默认情况下就会创建对象。
BeanFactory:它是采取延迟加载的策略,也就是什么根据id获取对象了,什么时候创建对象
Application的三个实现
ClassPathXmlApplicationContext :
它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext :
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext:
当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
1. 概述
下面进入正题开始讲解Spring 中Bean,抛开一些细节处理和扩展功能,一个 Bean 的创建过程无非是:
获取完整定义 -> 实例化 -> 依赖注入 -> 初始化 -> 类型转换。
Spring 作为 Ioc 框架,实现了依赖注入,由一个中心化的 Bean 工厂来负责各个 Bean 的实例化和依赖管理。各个 Bean 可以不需要关心各自的复杂的创建过程,达到了很好的解耦效果。
我们对 Spring 的工作流进行一个粗略的概括,主要为三大环节:
创建Bean对象,有三种方法:
①采用默认无参构造函数
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
其中类的表现形式如下。
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
}
②spring 管理静态工厂- 使用静态工厂的方法创建对象
public class StaticFactory {
public static IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
此种方式是:
使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器
id 属性:指定 bean 的 id,用于从容器中获取
class 属性:指定静态工厂的全限定类名
factory-method 属性:指定生产对象的静态方法
<bean id="accountService"
class="com.itheima.factory.StaticFactory"
factory-method="createAccountService"></bean>
③spring 管理实例工厂- 使用实例工厂的方法创建对象
/**
* 模拟一个实例工厂,创建业务层实现类
* 此工厂创建对象,必须现有工厂实例对象,再调用方法
*/
public class InstanceFactory {
public IAccountService createAccountService(){
return new AccountServiceImpl();
}
}
此种方式是:
先把工厂的创建交给 spring 来管理。
然后在使用工厂的 bean 来调用里面的方法
factory-bean 属性:用于指定实例工厂 bean 的 id。
factory-method 属性:用于指定实例工厂中创建对象的方法。
<bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService"
factory-bean="instancFactory"
factory-method="createAccountService"></bean>
解析,读 xml 配置,扫描类文件,从配置或者注解中获取 Bean 的定义信息,注册一些扩展功能。
加载,通过解析完的定义信息获取 Bean 实例。
我们假设所有的配置和扩展类都已经装载到了 ApplicationContext 中,然后具体的分析一下 Bean 的加载流程。
思考一个问题,抛开 Spring 框架的实现,假设我们手头上已经有一套完整的 Bean Definition Map,然后指定一个 beanName 要进行实例化,需要关心什么?即使我们没有 Spring 框架,也需要了解这两方面的知识:
作用域。单例作用域或者原型作用域,单例的话需要全局实例化一次,原型每次创建都需要重新实例化。
依赖关系。一个 Bean 如果有依赖,我们需要初始化依赖,然后进行关联。如果多个 Bean 之间存在着循环依赖,A 依赖 B,B 依赖 C,C 又依赖 A,需要解这种循环依赖问题。
Spring 进行了抽象和封装,使得作用域和依赖关系的配置对开发者透明,我们只需要知道当初在配置里已经明确指定了它的生命周期和依赖了谁,至于是怎么实现的,依赖如何注入,托付给了 Spring 工厂来管理。
Spring 只暴露了很简单的接口给调用者,比如 getBean :
ApplicationContext context = new ClassPathXmlApplicationContext(“hello.xml”);
HelloBean helloBean = (HelloBean) context.getBean(“hello”);
helloBean.sayHello();
那我们就从 getBean 方法作为入口,去理解 Spring 加载的流程是怎样的,以及内部对创建信息、作用域、依赖关系等等的处理细节。
Bean的加载过程
一个 Bean 加载会经历这么几个阶段:
获取 BeanName,对传入的 name 进行解析,转化为可以从 Map 中获取到 BeanDefinition 的 bean name。
合并 Bean 定义,对父类的定义进行合并和覆盖,如果父类还有父类,会进行递归合并,以获取完整的 Bean 定义信息。
实例化,使用构造或者工厂方法创建 Bean 实例。
属性填充,寻找并且注入依赖,依赖的 Bean 还会递归调用 getBean 方法获取。
初始化,调用自定义的初始化方法。
获取最终的 Bean,如果是 FactoryBean 需要调用 getObject 方法,如果需要类型转换调用 TypeConverter 进行转化。
Bean的作用范围
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="prototype"></bean>
观察上面代码,需要注意到scope代表着bean的作用范围,它的值可为:
singleton:单例的
prototype:多例的
scope:指定对象的作用范围。
request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么
globalSession 相当于 session.
Ioc中Bean的生命周期
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"
init-method="init" destroy-method="destroy">
init-method:指定类中的初始化方法名称。
destroy-method:指定类中销毁方法名称。
在scope不同的时候生命周期是不同的,
单例对象:scope="singleton"
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:
对象出生:当应用加载,创建容器时,对象就被创建了。
对象活着:只要容器在,对象一直活着。
对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
多例对象:scope="prototype"
每次访问对象时,都会重新创建对象实例。
生命周期:
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就一直活着。
对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了