您现在的位置是:首页 >技术杂谈 >JAVA-6-[Spring框架]Bean的作用域和生命周期网站首页技术杂谈
JAVA-6-[Spring框架]Bean的作用域和生命周期
1 Spring Bean
1、Spring有两种类型bean,一种普通bean,另外一种工厂bean(FactoryBean)。
2、普通bean:在配置文件中定义的bean类型就是返回的类型。
3、工厂bean:在配置文件中定义的bean类型可以和返回类型不一样。
第一步 创建类,让这个类作为工厂bean,实现接口FactoryBean
第二步 实现接口里面的方法,在实现的方法中定义返回的bean类型
1.1 Bean名称由来
由Spring IoC容器管理的对象称为Bean,Bean根据Spring配置文件中的信息创建。
可以把Spring IoC容器看作是一个大工厂,Bean相当于工厂的产品,如果希望这个大工厂生产和管理Bean,则需要告诉容器需要哪些Bean,以及需要哪种方式装配Bean。
一、JAVA名称的由来
Java 起初在被Sun公司发明的时候,并不是叫Java,而是叫Oak(橡树)。Oak 这一名称是以Java的发明人之一James Gosling他办公室外的一颗橡树来命名的。可以看出,他们当时起名时多么随意。
不过很遗憾,不久他们就发现,Oak这一名称已经被其它公司捷足先登注册成商标了,因此不得不再另起一个名称。哪个名称比较好呢?他们当时争论了很久,最终从几个候选名称中选择了"Java"这一名称。
为什么选择Java作为名称呢?这就要提及笔者经常提到的,西方人很喜欢使用与饮食相关的内容来命名。除此之外,当时这些个程序员的脑回路非常的清奇: Java这一名称这是通过办公室中的热咖啡来联想到的。历史上,在1696年,当荷兰殖民印度尼西亚时,在印度尼西亚现在的首都 Jakarta开始传播咖啡苗。之后,人们在印度尼西亚疯狂种植咖啡苗,并向欧洲供应。后来,印度尼西亚的一个名叫Java的岛屿上盛产咖啡。由于在该岛屿生产的咖啡声名远扬,人们很容易从咖啡联想到Java一词。于是,编程语言Java这一名称便诞生了。
有趣的是,由于Java的这个命名,导致之后Java EE在改名时,将名称改为了Jakarta EE。而Jakarta是现在印度尼西亚的首都。所以,原来与印度尼西亚没有血缘关系的Jav 创始人们,使用了一系列与印度尼西亚相关的名称。
也由于这个名称,导致了很多与Java相关的事物中,使用了与咖啡豆相关的名称,如NetBeans、Spring中的Bean等。
二、Bean的由来
Bean是Spring框架中的一个很基础的概念,而单词bean在英语中是“豆子”的意思。从Bean在Spring框架中的实际使用来看,这两者看起来毫无关联,那么,为什么进行这样的命名呢?
Bean在Spring框架中代表一种基本单元,在Spring框架中的几乎所有的预置对象都是一种“Bean”,对数据的各种处理也是以Bean为单位来进行的,用户自定义数据也需要构建成Bean之后才能处理。
一方面,如果按照Bean在Spring框架中的实际意义来命名,应该以为“元素”、“单元”等词进行命名。但问题是,“元素”(element)、“单元”(unit)、“原子”(atom)、“组件”(component)等词在计算机相关领域已经或日后可能成为该领域的专有名词,如果使用这种命名,有可能发生“二义性”的冲突。
另一方面,西方人喜欢以与饮食有关的事物进行命名。“豆子”也可以算得上是餐饮中一个基本的食材。
1.2 Spring配置文件
Spring配置文件支持两种格式,即XML文件格式和Properties文件格式。
(1)Properties配置文件主要以key-value键值对的形式存在,只能赋值,不能进行其他操作,适用于简单的属性配置。
(2)XML配置文件是树形结构,相对于Properties文件来说更加灵活。XML 配置文件结构清晰,但是内容比较繁琐,适用于大型复杂的项目。
一、通常情况下,Spring的配置文件使用XML格式。
XML配置文件的根元素是<beans>,该元素包含了多个子元素<bean>。
每一个<bean>元素都定义了一个Bean,
并描述了该Bean如何被装配到Spring容器中。
二、Beans.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-3.0.xsd">
<bean id="helloWorld123" class="net.biancheng.HelloWorld">
<property name="message" value="Hello World! Testing" />
</bean>
</beans>
2 Bean的作用域
当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton。
Spring 框架支持以下五个作用域,分别为 singleton、prototype、request、session 和 global session,5种作用域说明如下所示,
注意,如果你使用 web-aware ApplicationContext 时,其中三个是可用的。
2.1 singleton作用域
singleton是默认的作用域,也就是说,当定义Bean时,如果没有指定作用域配置项,则Bean的作用域被默认为 singleton。
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
也就是说,当将一个bean定义设置为singleton作用域的时候,Spring IoC容器只会创建该bean定义的唯一实例。
Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,它都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。你可以在bean的配置文件中设置作用域的属性为singleton,如下所示:
<!-- A bean definition with singleton scope -->
<bean id="..." class="..." scope="singleton">
<!-- collaborators and configuration for this bean go here -->
</bean>
MainApp.java文件
package net.biancheng;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld objA = (HelloWorld) context.getBean("helloWorld123");
objA.setMessage("I am A");
objA.getMessage();
HelloWorld objB = (HelloWorld) context.getBean("helloWorld123");
objB.getMessage();
}
}
输出如下信息:
Your Message : I'm object A
Your Message : I'm object A
2.2 prototype作用域
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的 bean实例。
Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。
为了定义prototype作用域,你可以在bean的配置文件中设置作用域的属性为prototype,如下所示:
<!-- A bean definition with singleton scope -->
<bean id="..." class="..." scope="prototype">
<!-- collaborators and configuration for this bean go here -->
</bean>
Beans.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-3.0.xsd">
<bean id="helloWorld123" class="net.biancheng.HelloWorld" scope="prototype">
<property name="message" value="Hello World! Testing" />
</bean>
</beans>
输出如下:
message : I am A
message : Hello World! Testing
3 Bean的生命周期
当一个bean被实例化时,它可能需要执行一些初始化使它转换成可用状态。同样,当bean不再需要,并且从容器中移除时,可能需要做一些清除工作。
尽管还有一些在Bean实例化和销毁之间发生的活动,但是本次只讨论两个重要的生命周期回调方法,它们在bean的初始化和销毁的时候是必需的。
为了定义安装和拆卸一个 bean,我们只要声明带有init-method或destroy-method参数的 。
init-method属性指定一个方法,在实例化bean时,立即调用该方法。
destroy-method指定一个方法,只有从容器中移除bean之后,才能调用该方法。
Bean的生命周期可以表达为:Bean的定义——Bean的初始化——Bean的使用——Bean的销毁
4.3.1 初始化回调
(1)方式一
通过org.springframework.beans.factory.InitializingBean接口提供下面的方法:
void afterPropertiesSet() throws Exception;
可以简单地实现上述接口和初始化工作可以在afterPropertiesSet()方法中执行,如下所示:
public class ExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
(2)方式二
在基于XML的配置元数据的情况下,可以使用init-method属性来指定带有void无参数方法的名称。
例如:
<bean id="exampleBean" class="examples.ExampleBean" init-method="init"/>
下面是类的定义:
public class ExampleBean {
public void init() {
// do some initialization work
}
}
4.3.2 销毁回调
(1)方式一
org.springframework.beans.factory.DisposableBean 接口提供下面的方法:
void destroy() throws Exception;
因此,可以简单地实现上述接口并且结束工作可以在destroy()方法中执行,如下所示:
public class ExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work
}
}
(2)方式二
在基于XML的配置元数据的情况下,可以使用destroy-method属性来指定带有void无参数方法的名称。例如:
<bean id="exampleBean" class="examples.ExampleBean" destroy-method="destroy"/>
下面是类的定义:
public class ExampleBean {
public void destroy() {
// do some destruction work
}
}
注意:如果在非 web 应用程序环境中使用Spring的IoC容器;
例如在丰富的客户端桌面环境中;那么在JVM中你要注册关闭 hook。
这样做可以确保正常关闭,为了让所有的资源都被释放,可以在单个 beans 上调用 destroy 方法。
4.3.3 代码举例
(1)文件HelloWorld.java
package net.biancheng;
public class HelloWorld {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("message : " + message);
}
public void init(){
System.out.println("Bean is going through init.");
}
public void destroy(){
System.out.println("Bean will destroy now.");
}
}
(2)文件MainApp.java
注意:在这里,需要注册一个在AbstractApplicationContext类中声明的关闭hook的 registerShutdownHook()方法。它将确保正常关闭,并且调用相关的destroy方法。
package net.biancheng;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld123");
obj.getMessage();
context.registerShutdownHook();
}
}
(3)文件Beans.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-3.0.xsd">
<bean id="helloWorld123" class="net.biancheng.HelloWorld"
init-method="init" destroy-method="destroy">
<property name="message" value="Hello World! Testing" />
</bean>
</beans>
运行后输出
Bean is going through init.
message : Hello World! Testing
Bean will destroy now.
4.3.4 默认的初始化和销毁方法
如果你有太多具有相同名称的初始化或者销毁方法的Bean,那么你不需要在每一个bean上声明初始化方法和销毁方法。
框架使用元素中的default-init-method和default-destroy-method属性提供了灵活地配置这种情况,如下所示:
<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">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
</beans>
4.4 后置处理器
Bean后置处理器允许在调用初始化方法前后对Bean进行额外的处理。
BeanPostProcessor接口定义回调方法,可以实现该方法来提供自己的实例化逻辑,依赖解析逻辑等。也可以在Spring容器通过插入一个或多个BeanPostProcessor的实现来完成实例化,配置和初始化一个bean之后实现一些自定义逻辑回调方法。
可以配置多个BeanPostProcessor接口,通过设置BeanPostProcessor实现的Ordered接口提供的order属性来控制这些BeanPostProcessor接口的执行顺序。
BeanPostProcessor可以对bean(或对象)实例进行操作,这意味着Spring IoC容器实例化一个bean实例,然后BeanPostProcessor接口进行它们的工作。
注意:
ApplicationContext会自动检测由BeanPostProcessor接口的实现定义的bean,注册这些bean为后置处理器,然后通过在容器中创建bean,在适当的时候调用它。
在你自定义的BeanPostProcessor接口实现类中,要实现以下的两个抽象方法 BeanPostProcessor.postProcessBeforeInitialization(Object, String)和 BeanPostProcessor.postProcessAfterInitialization(Object, String),注意命名要准确。
否则会出现:“ The type InitHelloWorld must implement the inherited abstract method BeanPostProcessor.postProcessBeforeInitialization(Object, String) ”之类的错误。
(1)文件HelloWorld.java
package net.biancheng;
public class HelloWorld {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("message : " + message);
}
public void init(){
System.out.println("Bean is going through init.");
}
public void destroy(){
System.out.println("Bean will destroy now.");
}
}
(2)文件InitHelloWorld.java
这是实现BeanPostProcessor的非常简单的例子,它在任何bean的初始化的之前和之后输入该bean的名称。你可以在初始化bean的之前和之后实现更复杂的逻辑,因为你有两个访问内置bean对象的后置处理程序的方法。
package net.biancheng;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
public class InitHelloWorld implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeforeInitialization : " + beanName);
return bean; // you can return any other object as well
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("AfterInitialization : " + beanName);
return bean; // you can return any other object as well
}
}
(3)文件MainApp.java
注意:在这里,需要注册一个在AbstractApplicationContext类中声明的关闭hook的 registerShutdownHook()方法。它将确保正常关闭,并且调用相关的destroy方法。
package net.biancheng;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld123");
obj.getMessage();
context.registerShutdownHook();
}
}
(4)文件Beans.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-3.0.xsd">
<bean id="helloWorld123" class="net.biancheng.HelloWorld"
init-method="init" destroy-method="destroy">
<property name="message" value="Hello World! Testing" />
</bean>
<bean class="net.biancheng.InitHelloWorld" />
</beans>
运行后输出
BeforeInitialization : helloWorld123
Bean is going through init.
AfterInitialization : helloWorld123
message : Hello World! Testing
Bean will destroy now.
4.5 定义继承
bean定义可以包含很多的配置信息,包括构造函数的参数,属性值,容器的具体信息例如初始化方法,静态工厂方法名,等等。
子bean的定义继承父定义的配置数据。子定义可以根据需要重写一些值,或者添加其他值。
Spring Bean定义的继承与Java类的继承无关,但是继承的概念是一样的。你可以定义一个父bean的定义作为模板和其他子bean就可以从父bean中继承所需的配置。
当你使用基于XML的配置元数据时,通过使用父属性,指定父bean作为该属性的值来表明子bean的定义。