详细解释什么是 Spring Bean(示例详解)

 更新时间:2023年09月12日 09:04:02   作者:都手打  
Spring Bean 是由 Spring IoC 容器管理的对象实例,也是 Spring 框架的基本组件之一,本文通过示例代码介绍Spring Bean 的作用域(Bean Scope)的相关使用方法,感兴趣的朋友一起看看吧

详细解释什么是 Spring Bean?

什么是 Spring Bean

Spring Bean 是由 Spring IoC 容器管理的对象实例,也是 Spring 框架的基本组件之一。Bean 可以是任何一个普通的 Java 对象,也可以是第三方库中的对象,例如 Hibernate SessionFactory 或 MyBatis SqlSessionFactory。

Spring Bean 的创建、组装和管理是由 Spring IoC 容器负责的。在容器中注册一个 Bean 后,容器负责创建 Bean 的实例、管理 Bean 的生命周期,以及处理 Bean 之间的依赖关系。通过 Spring 容器,可以实现对象之间的松耦合,便于测试、模块化开发、重用等。

在 Spring 中,Bean 是通过配置文件或注解来定义的。配置文件通常是 XML 或 Java 配置类,通过声明 Bean 的类名、作用域、依赖关系、属性值等信息来定义 Bean。注解则是通过在 Bean 类上添加特定的注解来定义 Bean。无论是 XML 配置文件还是注解,都需要被 Spring IoC 容器加载和解析,以创建 Bean 的实例并放入容器中。

Spring 注册Bean的方式

在 Spring 中,有多种注册 Bean 的方式,包括 XML 配置文件、Java 配置类和注解。

1.XML 配置文件方式:通过在 XML 配置文件中定义 Bean,然后由 Spring IoC 容器加载和解析,以创建 Bean 的实例并放入容器中。

示例:

<!-- 在 XML 配置文件中定义一个 Bean -->
<bean id="exampleBean" class="com.example.ExampleBean">
   <property name="message" value="Hello, Spring!"/>
</bean>

2.Java 配置类方式:通过编写 Java 配置类来定义 Bean,然后在 Spring IoC 容器中注册配置类,由容器加载和解析配置类,以创建 Bean 的实例并放入容器中。

示例:

@Configuration
public class AppConfig {
    @Bean
    public ExampleBean exampleBean() {
        ExampleBean bean = new ExampleBean();
        bean.setMessage("Hello, Spring!");
        return bean;
    }
}

3.注解方式:通过在 Bean 类上添加特定的注解来定义 Bean,然后由 Spring IoC 容器扫描和解析注解,以创建 Bean 的实例并放入容器中。

示例:

@Component
public class ExampleBean {
    @Value("Hello, Spring!")
    private String message;
    // getter and setter methods
}

需要注意的是,无论采用哪种方式注册 Bean,Bean 的作用域和依赖关系都需要在注册时进行指定。同时,为了让 Spring IoC 容器正确地扫描和解析 Bean,需要在 Spring 配置文件或 Java 配置类中添加相应的配置信息。

spring bean的生命周期

Spring Bean 的生命周期可以分为以下阶段:

  • 实例化:当容器接收到创建 Bean 的请求时,它会通过调用 Bean 的构造函数来实例化一个新的 Bean 对象。
  • 设置属性:一旦 Bean 被实例化,容器将通过 Bean 的 setter 方法来设置 Bean 的属性值。
  • 初始化:在所有的属性都被设置完之后,容器会调用 Bean 的初始化方法(如果有的话)。Bean 的初始化方法可以是实现了 InitializingBean 接口的 afterPropertiesSet() 方法,或者是使用 init-method 属性指定的方法。
  • 使用:一旦 Bean 初始化完成,它就可以被容器使用了。
  • 销毁:当容器关闭时,它会调用 Bean 的销毁方法(如果有的话)。Bean 的销毁方法可以是实现了 DisposableBean 接口的 destroy() 方法,或者是使用 destroy-method 属性指定的方法。

需要注意的是,Bean 的初始化和销毁方法是可选的,而且并不是所有的 Bean 都需要这些方法。另外,如果 Bean 实现了 DisposableBean 和 InitializingBean 接口,那么这些方法将会在对应的接口方法之前调用。

spring bean的作用域

Spring Bean 的作用域指的是在应用程序中使用该 Bean 实例的生命周期和可见性。Spring 容器支持以下五种常见的 Bean 作用域:

  • singleton:每个 Spring 容器中只存在一个 Bean 实例。该实例在容器启动时被创建,并在容器关闭时被销毁。这是 Spring 默认的 Bean 作用域。
  • prototype:每次从容器中获取 Bean 实例时都会创建一个新的实例。容器不会管理 Bean 实例的生命周期,也不会在容器关闭时销毁该实例。
  • request:在一个 HTTP 请求中,容器会为每个请求创建一个 Bean 实例。该实例在请求处理完成后被销毁。
  • session:在一个 HTTP 会话中,容器会为每个会话创建一个 Bean 实例。该实例在会话结束后被销毁。
  • global session:在一个全局 HTTP 会话中,容器会为每个会话创建一个 Bean 实例。只适用于基于 Portlet 的 Web 应用。

除了上述五种常见的 Bean 作用域,Spring 还支持自定义的 Bean 作用域,可以通过实现 Scope 接口来实现。可以根据具体的业务需求来定义 Bean 作用域,以达到更好的性能和灵活性。

在实际开发中,通常将单例作为默认的 Bean 作用域,因为它可以节省资源并提高性能。然而,对于需要状态保持或线程安全的 Bean,如 Web 应用中的 session 和 request 作用域的 Bean,或者在多线程环境中的 Bean,就需要使用其他作用域来保证正确性。

Spring Bean 的作用域(Bean Scope)示例详解

前言

大家好,我是 god23bin,今天我们来聊一聊 Spring 框架中的 Bean 作用域(Scope)。

什么是 Bean 的作用域?

我们在以 XML 作为配置元数据的情况下,进行 Bean 的定义,是这样的:

<bean id="vehicle" class="cn.god23bin.demo.domain.model.Vehicle">
	<!-- 协作者写在这里... -->
</bean>

我们写了一个 Bean 定义(Bean Definition),就是用于创建所定义的类的实例的。

一个 Bean 定义,我们可以类比一个类的定义,你定义了一个类,你可以根据这个类创建出许多实例对象。同理,Bean 定义也是,也是可以根据这个定义创建许多实例对象的,只不过这里是 Spring 帮我们创建,而不是我们手动 new 。 这些 Bean 对象实例,我们可以理解为 Spring IoC 容器中的对象。

在写 Bean 定义的过程中,我们可以控制各种 Bean 的依赖项和相应的值,将这些依赖项和值注入到 Bean 定义所创建的对象中。同理,这个过程也可以控制 Bean 定义创建的对象的 Scope(作用域)。Bean 的作用域定义了在容器中创建的 Bean 实例的生命周期以及在应用程序中的可见性。

6 种 Bean 的作用域

Spring 支持 6 种 Bean 的作用域,其中有 4 种是在 Web 应用下才能感知到的,如下表所示:

Scope说明
singleton(默认情况下)每个 Spring IoC 容器将单个 Bean 定义的 Scope 指定为单个对象实例。
prototype将单个 Bean 定义的 Scope 扩大到任意数量的对象实例。
request将单个 Bean 定义的 Scope 扩大到单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 Bean 实例,该实例是在单个 Bean 定义的基础上创建的。只在 Web 感知的 Spring ApplicationContext 的上下文中有效。
session将单个 Bean 定义的 Scope 扩大到一个 HTTP 会话的生命周期。只在 Web 感知的 Spring ApplicationContext 的上下文中有效。
application将单个 Bean 定义的 Scope 扩大到 ServletContext 的生命周期中。只在 Web 感知的 Spring ApplicationContext 的上下文中有效。
websocket将单个 Bean 定义的 Scope 扩大到 WebSocket 的生命周期。只在 Web 感知的 Spring ApplicationContext 的上下文中有效。

1. Singleton Scope

singleton 作用域的 Bean,在 Spring IoC 容器中就有且仅有一个该类型的实例对象,也就是单例的。

默认情况下,我们在写 Bean 定义的时候,不指定作用域的话,那么这个 Bean 对象就是单例的。

<!-- 不写 Bean 的作用域,默认作用域为单例 -->
<bean id="accountService" class="cn.god23bin.demo.service.DefaultAccountService"/>
<!-- 写上作用域,这里是冗余的写法,使用 scope 属性 -->
<bean id="accountService" class="cn.god23bin.demo.service.DefaultAccountService" scope="singleton"/>

这个单例对象是存储在一个缓存区域中的,在后续的请求或者引用中,Spring 就会返回这个缓存的对象。

实际上,Spring 中的单例的 Bean 对象是不同于 Gang of Four 设计模式中的所定义的单例模式的。

设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。

1995 年,GoF(Gang of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式,从此树立了软件设计模式领域的里程碑,人称「GoF设计模式」。

设计模式中的单例模式是硬编码的方式,以便每个 ClassLoader 只创建一个特定类的一个实例。

而 Spring 单例的范围是指每个 IoC 容器的,不同 IoC 容器维护自己的 Bean 的单例对象

2. Prototype Scope

Bean 的作用域是 prototype,中文意思是原型,实际上这里是省略了 non-singleton,这个作用域的全称是 non-singleton prototype scope,即「非单例原型的作用域」。

顾名思义,这个作用域下的 Bean 不是单例的,意思就是说 Bean 是多例的,每一次的请求或者引用,都会创建一个新的 Bean 对象。

当然这里的请求或者引用的意思是指,非单例原型的 Bean 被注入到另一个 Bean 中的时候(Bean 作为属性被引用),或者我们直接通过容器的 getBean() 方法调用来请求它的时候,就会创建一个新的对象。

在 XML 中指定了这个 Bean 的作用域为 prototype

<bean id="accountService" class="cn.god23bin.demo.service.DefaultAccountService" scope="prototype"/>

在 prototype 作用域下的 Bean,Spring 是不会负责该 Bean 的销毁周期中回调的方法的,如果该 Bean 拥有一些重要的资源,想在该 Bean 对象销毁时释放这些资源,那么需要自定义 BeanPostProcessor(Bean 的后置处理器),它持有我们需要清理的 Bean 的引用。

在某些方面来说,在 prototype 作用域下的 Bean 的作用是代替 new 操作的。

其余 4 种作用域

requestsessionapplication 和 websocket scope 只有在使用 Web 感知的 Spring ApplicationContext 实现(如 XmlWebApplicationContext)时才可用。

简而言之,一般是在 Web 应用下,借助 Spring 的 Web 模块,就能使用这 4 种作用域。

如果你将这些 scope 与常规的 Spring IoC 容器(如 ClassPathXmlApplicationContext)一起使用,就会抛出一个 IllegalStateException,提示有未知的 Bean scope。

3. Request Scope

<bean id="loginController" class="cn.god23bin.demo.controller.LoginController" scope="request"/>

Spring IoC 容器为每一个 HTTP 请求使用 loginController Bean 定义来创建 LoginController Bean 的新实例,从而实现这种 request 作用域。

你可以随心所欲地改变被创建的实例的内部状态,因为从同一个 loginController Bean 定义中创建的其他实例不会看到这些状态的变化。它们是针对单个请求的,当请求完成处理时,该请求所涉及的 Bean 会被丢弃。

4. Session Scope

<bean id="userPreferences" class="cn.god23bin.demo.UserPreferences" scope="session"/>

Spring IoC 容器通过使用 userPreferences Bean 定义,在单个HTTP Session 的生命周期内创建一个新的 UserPreferences Bean 实例。

与 request scope 的 Bean 一样,你可以随心所欲地改变被创建的实例的内部状态,要知道其他 HTTP Session 实例也在使用从同一个 userPreferences Bean定义中创建的实例,它们不会看到这些状态的变化,因为它们是特定于单个HTTP Session。当HTTP Session 最终被丢弃时,作用于该特定HTTP Session 的 Bean 也被丢弃。

5. Application Scope

<bean id="appPreferences" class="cn.god23bin.demo.AppPreferences" scope="application"/>

Spring 容器通过为整个Web应用程序使用一次 appPreferences Bean 定义来创建 AppPreferences Bean的新实例。

这有点类似于Spring的 singleton Bean,但在两个重要方面有所不同。

它是每个 ServletContext 的单例,而不是每个 Spring ApplicationContext(在任何给定的Web应用程序中可能有几个)。

6. WebSocket Scope

这里就涉及到 WebSocket 了,目前先不讨论。后面再来填坑~

不同作用域的 Bean 之间的依赖关系

这里讨论的,一般就是单例作用域的 Bean 和原型作用域的 Bean 之间的依赖关系。

现在举个例子,假设有两个 Java 类交给了 Spring IoC 容器管理,分别是 SingletonBean 类和 PrototypeBean 类。

其中 SingletonBean 是单例作用域的 Bean,而 PrototypeBean 是原型作用域的 Bean。

那么当:

  • SingletonBean 的依赖项是 PrototypeBean 时,PrototypeBean 对象只会初始化一次并注入到 SingletonBean,这样 PrototypeBean 就起不到原型作用域的效果。
  • PrototypeBean 的依赖项是 SingletonBean 时,每次 PrototypeBean 对象都会创建,这些对象都依赖于一个单例对象,此时没任何问题。

方法注入

Spring 提供了一种称为方法注入(Method Injection)的机制来解决原型作用域的 Bean 在被注入到单例作用域的 Bean 中时只创建一个实例的问题。

方法注入允许每次调用方法时都获取一个新的原型作用域的 Bean 实例

方法注入是通过在 SingletonBean 中定义一个返回 PrototypeBean 实例的方法来实现的。这样,在每次需要使用 PrototypeBean 的地方,可以通过调用该方法获取一个新的实例。

以下是使用方法注入解决 Prototype Bean 作用域的示例:

public abstract class SingletonBean {
    public abstract PrototypeBean getPrototypeBean();
    public void doSomething() {
        PrototypeBean prototypeBean = getPrototypeBean();
        // 使用 Prototype Bean 进行操作
    }
}
public class PrototypeBean {
    // Prototype Bean 的定义
}

在上述示例中,SingletonBean 是一个抽象类,其中声明了一个抽象方法 getPrototypeBean(),该方法返回一个 PrototypeBean 实例。在 doSomething() 方法中,通过调用 getPrototypeBean() 方法获取一个新的 PrototypeBean 实例,以便在每次调用 doSomething() 时使用不同的实例。

然后,可以通过具体的子类来实现 SingletonBean,并实现 getPrototypeBean() 方法以返回相应的 PrototypeBean 实例。

通过方法注入,每次调用 doSomething() 方法时都会获取一个新的 PrototypeBean 实例,从而解决了在 Singleton Bean 中注入 Prototype Bean 时只创建一个实例的问题。

需要注意的是,方法注入需要在配置文件或使用注解时进行特殊的配置,具体的配置方式基本如下。

1. XML 配置方式

当然,上面举例是一个抽象类,不是抽象类也是可以的,比如:

public class SingletonBean {
    // 方法注入,Spring 会帮我们返回这个对象,这里写成 null 即可
    public PrototypeBean getPrototypeBean() {
        return null;
    }
    public void doSomething() {
        PrototypeBean prototypeBean = getPrototypeBean();
        // 使用 Prototype Bean 进行操作
    }
}
public class PrototypeBean {
    // Prototype Bean 的定义
}

接着,单独上面是没有实现不了方法注入的,还需要结合配置元数据,现在在 XML 配置文件中使用 <lookup-method /> 标签来实现方法注入。

<bean id="singletonBean" class="cn.god23bin.demo.domain.model.SingletonBean">
    <lookup-method name="getPrototypeBean" bean="prototypeBean"/>
</bean>
<bean id="prototypeBean" class="cn.god23bin.demo.domain.model.PrototypeBean" scope="prototype"/>

上面的配置示例中,singletonBean 是一个单例 Bean,通过 <lookup-method /> 标签指定了一个名为 getPrototypeBean 的方法,并引用了一个原型 Bean prototypeBean

在运行时,每次调用 getPrototypeBean 方法时,都会返回一个新的 prototypeBean 实例。

2. 注解配置方式

使用 @Lookup 注解来实现方法注入。

@Component
public class SingletonBean {
    private PrototypeBean prototypeBean;
    @Lookup
    public PrototypeBean getPrototypeBean() {
        return null; // 实际上会由 Spring 生成具体实现
    }
    // 其他代码...
}
@Component
@Scope("prototype")
public class PrototypeBean {
    // 具体的原型 Bean 实现
}

在上面的示例中,SingletonBean 使用了 @Lookup 注解标记了一个名为 getPrototypeBean 的方法。在运行时,Spring 会为这个方法生成具体的实现,以实现方法注入。

总结

简单总结下:

Bean 的作用域在 Bean 定义的时候可以进行指定,默认是单例的,多例的 Bean 就是所谓的原型作用域。

一共 6 种作用域需要熟悉,其中 4 种是在具有 Web 感知能力的 Spring IoC (应用上下文)下才有的作用域。

对于单例 Bean 依赖原型 Bean 的问题,可以通过方法注入解决,两种写法实现方法注入,一种是 XML,另一种是注解的方式。

到此这篇关于Spring Bean 的作用域(Bean Scope)的文章就介绍到这了,更多相关Spring Bean 的作用域内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring事务失效场景原理及解决方案

    Spring事务失效场景原理及解决方案

    这篇文章主要介绍了Spring事务失效场景原理及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • 分析jackjson的安全漏洞CVE-2019-14379

    分析jackjson的安全漏洞CVE-2019-14379

    这篇文章主要介绍了jackjson的使用及CVE-2019-14379漏洞分析,ackson知识点序列化和反序列化,setName和getName调用顺序,通过实例代码讲解的很详细,需要的朋友可以参考下
    2021-06-06
  • IDEA中如何查找jar包之间的依赖关系并忽略依赖的某个包

    IDEA中如何查找jar包之间的依赖关系并忽略依赖的某个包

    这篇文章主要介绍了IDEA中如何查找jar包之间的依赖关系并忽略依赖的某个包?本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • 微服务和分布式的区别详解

    微服务和分布式的区别详解

    在本篇文章里小编给各位整理了关于微服务和分布式的区别以及相关知识点总结,有兴趣的朋友们学习下。
    2019-07-07
  • JavaWeb简单文件上传流程的实战记录

    JavaWeb简单文件上传流程的实战记录

    在Web应用系统开发中,文件上传和下载功能是非常常用的功能,下面这篇文章主要给大家介绍了关于JavaWeb实现简单文件上传流程的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • SpringMvc请求处理参数 和 响应数据处理的示例详解

    SpringMvc请求处理参数 和 响应数据处理的示例详解

    这篇文章主要介绍了SpringMvc请求处理参数和响应数据处理,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-09-09
  • 深入了解JAVA 虚引用

    深入了解JAVA 虚引用

    这篇文章主要介绍了JAVA 虚引用的相关资料,帮助大家更好的理解和学习JAVA,感兴趣的朋友可以了解下
    2020-08-08
  • Java连接mysql数据库以及mysql驱动jar包下载和使用方法

    Java连接mysql数据库以及mysql驱动jar包下载和使用方法

    这篇文章主要给大家介绍了关于Java连接mysql数据库以及mysql驱动jar包下载和使用方法,MySQL是一款常用的关系型数据库,它的JDBC驱动程序使得我们可以通过Java程序连接MySQL数据库进行数据操作,需要的朋友可以参考下
    2023-11-11
  • java 实现获取指定位置后的第一个数字

    java 实现获取指定位置后的第一个数字

    这篇文章主要介绍了java 实现获取指定位置后的第一个数字,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • 使用hutool进行ftp文件下载和上传详细代码示例

    使用hutool进行ftp文件下载和上传详细代码示例

    在开发Java项目时,FTP客户端是经常需要使用的工具,因为FTP协议在文件传输方面有着广泛的应用,这篇文章主要给大家介绍了关于使用hutool进行ftp文件下载和上传的相关资料,需要的朋友可以参考下
    2024-02-02

最新评论