您现在的位置是:首页 >技术杂谈 >JAVA-6-[Spring框架]Bean的作用域和生命周期网站首页技术杂谈

JAVA-6-[Spring框架]Bean的作用域和生命周期

皮皮冰燃 2023-06-21 00:00:03
简介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的定义。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。