`

Spring(09)——bean作用范围

阅读更多

9 bean作用范围(scope)

在Spring中使用Scope来表示一个bean定义对应产生实例的类型,也可以说是对应实例的作用范围。Spring内置支持的scope严格来说默认是有五种,分别是:

  • singleton:这是默认Scope,表示在整个bean容器中或者说是整个应用中只会有一个实例。
  • prototype:多例类型,表示每次从bean容器中都会获取到一个对应bean定义全新的实例。
  • request:仅适用于Web环境下的ApplicationContext,表示每一个HttpRequest生命周期内会有一个单独的实例,即每一个Http请求都会拥有一个单独的实例。
  • session:仅适用于Web环境下的ApplicationContext,表示每一个HttpSession生命周期内会有一个单独的实例,即每一个HttpSession下都会拥有一个单独的实例,即每一个用户都将拥有一个单独的实例。
  • globalSession:仅适用于Web环境下的ApplicationContext,一般来说是Portlet环境下。表示每一个全局的Http Session下都会拥有一个单独的实例。
  • application:仅适用于Web环境下的ApplicationContext,表示在ServletContext生命周期内会拥有一个单独的实例,即在整个ServletContext环境下只会拥有一个实例。

9.1 指定scope

scope的指定主要有两种方式,一种是通过XML配置的方式进行指定,一种是通过注解的形式进行指定。通过XML配置进行指定时是通过在bean元素上通过scope属性进行指定的。

	<bean id="hello" class="com.app.Hello" scope="prototype"/>

而通过注解的形式进行指定时则是通过注解@Scope进行指定的。

@Component
@Scope("prototype")
public class Hello {
	
}

使用注解的形式来指定bean的Scope时,需要我们同时启用Spring通过扫描注解来添加对应的bean定义,即需要定义<context:component-scan/>,并在对应的bean上使用@Component等注解进行标注以表示其需要被作为一个bean定义添加到对应的bean容器中。

	<context:component-scan base-package="com.app"/>

9.2 singleton

singleton即单例,是Spring中bean的默认作用范围。当一个bean的Scope定义为singleton时表示每次从对应的bean容器中获取对应的bean时都会是同一个bean。这里需要说明的一点是Spring中singleton的bean与我们所使用的单例模式中的单例还是有一点区别的,在单例模式中基本上我们在整个JVM中都只会拥有一个对应的对象,而Spring中singleton类型的bean则是表示在对应的bean容器中只会拥有一个对应的bean实例,如果我们在多个不同的bean容器中定义同一个singleton类型的bean,则该bean在我们的应用中就会拥有多个实例,但是在对应的bean容器中还是只会拥有一个实例,又或者我们在同一个bean容器中将同一个类型的bean定义多次,其也会拥有多个不同的实例。Spring中singleton类型的bean表示定义的bean容器中的每一个bean定义,当定义为singleton类型时,对应的bean定义在当前bean容器中只会拥有一个实例。如下示例中我们将同一类型的Class定义了两个bean,即两个不同的bean定义,且它们都是定义为单例类型的,那么在对应的bean容器中将拥有两个类型为Hello的实例,但是对应于每一个bean定义来讲,它们都只拥有一个实例,即id为hello的bean定义只拥有一个实例,id为hello1的bean定义也只拥有一个实例。

	<bean id="hello" class="com.elim.learn.spring.bean.Hello"/>
	<bean id="hello1" class="com.elim.learn.spring.bean.Hello" scope="singleton"/>

在Spring内部,当我们把一个bean定义singleton时,Spring在实例化对应的bean后会将其缓存起来,然后在之后每次需要从bean容器中获取对应的bean时都会从缓存中获取,这也就是为什么定义为singleton的bean每次从bean容器中获取的都是同一个的原因。

9.3 prototype

当我们将一个bean的Scope定义为prototype时,即表示我们每次从bean容器中获取对应bean实例时都将获取一个全新的实例。这包括Spring内部获取对应的bean实例注入给其它bean,也包括我们自己通过getBean()方法获取对应bean的实例。如下就是将一个bean的scope定义为prototype的示例。

	<bean id="hello" class="com.app.Hello" scope="prototype"/>

9.3.1 生命周期

当我们将一个bean的Scope定义为prototype时,有一点需要注意的是Spring将只会回调对应bean定义的初始化方法,而对于销毁方法,Spring是不会进行回调的。根据之前对我们对生命周期回调方法的介绍,我们知道初始化方法的回调是在Spring将对应的bean实例化之后进行回调的,不管我们将bean的Scope设置为何种类型,对应bean的实例化都是由Spring完成的,所以对于bean定义的初始化方法是可以被Spring回调的,这点没有问题。相对应的是bean的销毁方法是在Spring决定销毁bean容器之前进行回调的,这个时候Spring需要拿到对应的bean实例才能进行对应销毁方法的回调,但是对于prototype类型的bean定义而言,Spring是不会保留对应的bean实例的,所以它拿不到,也就不会进行回调了。而对于之前我们介绍的singleton类型的bean定义,由于其实例都由Spring缓存起来了,以确保每次请求的都是同一个实例,所以在bean容器销毁前,Spring还是可以拿到原来实例化的bean实例,所以就可以进行对应销毁方法的回调。所以,就如下类而言,当我们将类Hello定义为一个对应Scope为prototype的bean时,该bean产生的实例对应的init()方法可以被Spring回调,但是对应的destroy()方法将无法被Spring回调。

public class Hello {
	
	@PostConstruct
	public void init() {
		System.out.println("init..........");
	}
	
	@PreDestroy
	public void destroy() {
		System.out.println("destroy.........");
	}
	
}

9.3.2 prototype类型的实例注入给singleton类型

假设我们在bean容器中定义了如下两个bean,其中id为hello的bean定义为singleton类型,而id为world的bean定义为prototype类型,且给hello注入了一个world。

	<bean id="hello" class="com.app.Hello" p:world-ref="world"/>
	<bean id="world" class="com.app.World" scope="prototype"/>

另外,类Hello的代码如下所示:

public class Hello {
	
	private World world;
	
	public World getWorld() {
		return this.world;
	}
	
	public void setWorld(World world) {
		this.world = world;
	}
	
}

这样会不会有什么问题呢?或者说这样会有什么问题呢?对应的问题就是我们的hello是定义为单例形式,而我们的world是定义为多例形式,且我们给单例形式的hello注入了一个多例形式的world,导致的最终结果就是我们通过hello去获取其中所持有的World时将每次都取到同一个World。即下面测试代码的结果会是true。

	@org.junit.Test
	public void test() {
		Hello hello = context.getBean("hello", Hello.class);
		World w1 = hello.getWorld();
		World w2 = hello.getWorld();
		System.out.println(w1 == w2);
	}

产生这种结果的原因是单例形式的hello只会被Spring初始化一次,而在初始化的时候Spring会根据定义好的注入内容将实例化一个全新的World,然后将其注入给hello,此后都不再从bean容器中获取新的World对象注入给单例类型的hello了。所以才会出现下述测试代码中将输出true和false。

	@org.junit.Test
	public void test() {
		Hello hello = context.getBean("hello", Hello.class);
		World w1 = hello.getWorld();
		World w2 = hello.getWorld();
		World world = context.getBean("world", World.class);
		System.out.println(w1 == w2);//true
		System.out.println(w1 == world);//false
	}

这通常应该不是我们想要的结果。通常当我们指定一个bean的scope为prototype时,我们就希望每次获取到的bean都是一个全新的实例。对于这种应用场景,根据之前的内容而言,主要有两种解决思路,它们都是在运行时获取一个全新的bean实例。
第一种方式是给singleton类型的Hello注入一个ApplicationContextBeanFactory,然后在需要使用prototype类型的World时每次都从注入的ApplicationContext或者BeanFactory获取一次全新的World实例。根据这种思路可以将我们的Hello定义为如下形式,其每次需要使用World实例时都可以通过getWorld()方法进行获取,而getWorld()方法将每次从BeanFactory中进行获取,所以如果对应的World是定义为prototype类型的,则每次从BeanFactory中获取的都会是一个新的World实例。

public class Hello implements BeanFactoryAware{
	
	private BeanFactory beanFactory;
	
	/**
	 * 通过注入的BeanFactory获取一个全新的World实例
	 * @return
	 */
	public World getWorld() {
		return this.beanFactory.getBean("world", World.class);
	}
	
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}
	
}

对于BeanFactoryApplicationContext的注入可以通过实现对应的接口进行,如BeanFactoryAware接口和ApplicationContextAware接口,如果是使用注解进行配置,也可以直接通过注解进行注入。当然,改变了Hello获取World实例的方式后,对应的bean定义中对World实例的注入也需要去除。

	<bean id="hello" class="com.app.Hello"/>

第二种方式是通过之前介绍的lookup-method的形式让Spring在我们调用bean的某个方法时直接获取bean容器中的另一个bean定义的实例作为返回值。按照这种方式,我们可以将Hello的代码修改成如下形式,开放getWorld()方法让Spring来提供对应的返回值,这样在以后每次需要获取一个全新的World实例时都可以通过调用getWorld()方法来获取。

public class Hello {
	
	/**
	 * 让Spring来提供对应的返回值
	 * @return
	 */
	public World getWorld() {
		return null;
	}
	
}

当然,要实现这样的逻辑,那么对应的bean定义应该改成如下这样,以告诉Spring在调用id为hello的bean的getWorld()方法时将从bean容器中获取一个id为world的bean实例作为返回值。

	<bean id="hello" class="com.elim.learn.spring.bean.Hello">
		<lookup-method bean="world" name="getWorld"/>
	</bean>
	<bean id="world" class="com.elim.learn.spring.bean.World" scope="prototype"/>

这个时候再执行如下测试代码时将会输出false。

	@org.junit.Test
	public void test() {
		Hello hello = context.getBean("hello", Hello.class);
		World w1 = hello.getWorld();
		World w2 = hello.getWorld();
		System.out.println(w1 == w2);//false
	}

9.4 request

当我们将一个bean的Scope定义为request时,表示在每一个HttpRequest生命周期内从bean容器获取到的对应bean定义的实例都是同一个实例,而不同的HttpRequest所获取到的实例是不一样的。该类型的Scope只允许在Web环境下使用,包括后续介绍的session和application。当我们使用的是SpringMVC时则无需做任何配置就可以使用request等Web环境下的Scope,因为SpringMVC已经为我们封装好了。如果是非SpringMVC环境的话,则需要我们在web.xml中配置一个RequestContextListener

	<listener>
		<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
	</listener>

根据情况的不同,也有可能还需要配置一个RequestContextFilter

	<filter>
		<filter-name>requestContextFilter</filter-name>
		<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>requestContextFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

不管是SpringMVC、RequestContextListener,还是RequestContextFilter,它们都是做着同一个事情,即将当前请求的request放入当前的线程变量中。

	<bean id="hello" class="com.app.Hello" scope="request"/>

在上述bean定义中,我们就定义了其scope为request。对于此种类型的bean有一个问题需要注意,那就是如果我们拥有一个singleton类型的 beanA,然后其需要被注入一个request类型的beanB时,如果我们在对beanA进行定义时就定义好了其对beanB的依赖。则由于Spring默认会在初始化bean容器后立即对单例类型的bean进行实例化,进而导致会实例化其所依赖的其它bean,也就是说在实例化beanA的时候,会进而实例化beanB。但此时是没有HttpRequest请求的,也就是说没有Web环境的,那么Spring将无法实例化beanB,其会抛出异常。
针对上述情形,可以有两种处理方法,一是指定beanA为懒初始化,这样Spring在bean容器初始化完成后默认不会对其进行实例化,只有在其第一次需要被使用的情况下才会被初始化,所以此时beanA不能作为其它会被Spring在bean容器初始化后进行实例化的bean的依赖关系链上的一员存在。

	<bean id="beanA" class="com.app.BeanA" p:beanB-ref="beanB" lazy-init="true"/>

对于使用注解的方式进行定义的情况,则可以使用@Lazy进行指定。

@Component
@Lazy
public class BeanA {
	
}

第二种处理方式是可以在beanB的bean定义下定义<aop:scoped-proxy/>以告诉Spring需要为对应的bean生成一个代理。这样在将beanB注入给beanA时实际上注入的只是一个代理,然后在我们真正使用beanB的时候,Spring会拿一个真正的beanB实例来进行对应的操作。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

	<bean id="beanA" class="com.app.BeanA" p:beanB-ref="beanB"/>
	<bean id="beanB" class="com.app.BeanB" scope="request">
		<aop:scoped-proxy/>
	</bean>

</beans>

使用<aop:scoped-proxy/>时记住需要引入aop的命名空间。<aop:scoped-proxy/>默认将使用CGLIB来动态的生成Class进行代理,如果需要使用接口进行代理,则可以指定<aop:scoped-proxy/>proxy-target-class=”false”,该属性值默认是true。

另外一点需要注意的是即使使用了代理,我们的beanB也只能在有HttpRequest请求的环境下使用,你不能在其它非HttpRequest环境下使用。

对于使用注解进行配置的情况,我们可以通过@Scope注解的value属性来指定对应的Scope为request,然后通过其proxyMode属性来指定代理方式,默认为无,我们可以根据自己的需要指定其为TARGET_CLASS或INTERFACES。

@Component
@Scope(value="request", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class BeanB {
	
}

9.5 session

当将一个bean的Scope定义为session时即表示该bean的实例将与session保持一致,即每一个不同的session保持一个不同的实例,同一个session则拥有相同的实例。或者换句话来说就是在同一session环境下,不管你从bean容器中获取对应bean定义的实例多少次,你取到的总是一个相同的实例。以下是将一个bean的scope定义为session的示例。

	<bean id="hello" class="com.app.Hello" scope="session"/>

对于此种bean的定义也会有scope为request的bean定义相同的问题,一种比较好的方式是指定其需要被代理。

	<bean id="hello" class="com.app.Hello" scope="session">
		<aop:scoped-proxy/>
	</bean>

9.6 application

指定Scope为application也只能在Web环境下使用。当定义一个bean的scope为application时表示对应的bean实例是跟ServletContext绑定在一起的,即在整个ServletContext环境下都只会拥有一个对应的实例。

 

	<bean id="hello" class="com.app.Hello" scope="application">
		<aop:scoped-proxy/>
	</bean>

 

 

本文转自:https://elim.iteye.com/blog/2386218

 

分享到:
评论

相关推荐

    Spring————面试题库

    在Spring框架中,通常会把应用程序对象定义在XML配置文件中,Spring IoC容器会解析这些XML文件,并根据文件中定义的bean的配置信息来创建和管理这些对象。举例来说,使用ClassPathXmlApplicationContext可以从...

    Spring MVC Ibatis Bean 根据mysql数据表——代码生成工具

    标题中的“Spring MVC Ibatis Bean 根据mysql数据表——代码生成工具”就是这样的一个工具,它能够根据MySQL数据库中的表结构,快速生成符合Spring MVC和Ibatis框架的代码。 Rapid-generator-0.2v可能是这个工具的...

    Spring特性——Aware感知特性

    在Spring框架中,Aware接口系列是其核心特性之一,它为Spring容器提供了向bean注入上下文信息的能力。这些接口让bean能够感知到Spring容器的存在,从而获取必要的服务或配置信息。下面我们将深入探讨Spring的Aware...

    征服Spring AOP—— Schema

    本文将深入探讨“Spring AOP——Schema”,这是Spring AOP的一种配置方式,通过XML schema定义切面和通知。 首先,我们需要理解AOP的基本概念。面向切面编程是一种编程范式,旨在提高软件的模块化程度,将关注点...

    Quartz Spring整合——附带webservice Demo

    在"Quartz Spring整合——附带webservice Demo"的项目中,我们可以看到如何将这两个强大的工具结合在一起。这个Demo可能包含了一个使用Quartz调度器来触发Web服务调用的示例。Web服务(Webservice)是一种基于标准的...

    SpringCloud——Zookeeper(注册中心)

    @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } ``` 然后,使用`loadBalancer.choose(serviceId)`选择一个服务实例,并通过`RestTemplate`进行请求。 **服务健康检查**...

    二、Spring源码分析——BeanFactory

    《Spring源码分析——BeanFactory》 在Java的IoC(Inversion of Control)和DI(Dependency Injection)领域,Spring框架扮演着至关重要的角色。BeanFactory是Spring的核心组件之一,它是容器的基石,负责管理应用...

    Spring特性——事件驱动模型

    本篇文章将深入探讨Spring框架的一个重要特性——事件驱动模型。通过理解这一特性,开发者可以更好地利用Spring来实现应用间的通信和协调,提高系统的灵活性和可扩展性。 事件驱动模型是一种设计模式,它基于发布/...

    Spring boot——@DeclareParents例子

    当我们在一个切面类中使用`@DeclareParents`时,Spring会为匹配到的bean创建代理,并将指定的接口实现注入到这些bean中。 首先,我们需要了解`@Aspect`的使用。在Spring Boot项目中,创建一个带有`@Aspect`注解的类...

    Spring从入门到入土——Bean的作用域

    在Spring框架中,Bean的作用域是管理Bean实例生命周期的关键概念,它决定了Bean如何在应用程序中创建、使用和销毁。Bean的作用域主要有四种:Singleton、Prototype、Request和Session,每种都有其特定的适用场景和...

    普元eos-springbean开发

    此外,通过两个具体的案例——HelloWorld案例和报销单维护案例,我们更加深入地理解了SpringBean的实际应用场景及其带来的价值。 最后,如果在开发过程中遇到任何问题,可以通过官方网站([www.primeton.com]...

    MyBatis与Spring整合——通过官方文档进行最简单的整合

    &lt;bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"&gt; &lt;/bean&gt; &lt;!-- SqlSessionFactory --&gt; &lt;bean id="sqlSessionFactory" class="org.mybatis.spring...

    Spring学习笔记(9)----让Spring自动扫描和管理Bean

    除了基本的`@Component`家族,Spring还提供了一些高级注解,如`@Scope`用于定义Bean的作用域,`@Lazy`用于延迟初始化Bean,`@Qualifier`用于在多个相同类型的Bean中指定特定的一个。 ### **总结** Spring的自动...

    Spring开发指南——中文版

    《Spring开发指南——中文版》是由夏昕编著的一本针对Spring框架的中文教程,旨在帮助开发者更好地理解和应用Spring框架。Spring是Java平台上的一个核心框架,广泛应用于企业级应用开发,提供了一种全面的编程和配置...

    实例化Spring bean的两种工厂方法

    本篇将详细探讨两种工厂方法——实例工厂方法和静态工厂方法,用于创建Spring Bean。 首先,我们要理解Spring Bean的概念。Spring Bean是Spring IoC容器管理的对象,这些对象的生命周期、依赖关系以及初始化行为由...

    普通类调用Spring bean对象

    首先,理解Spring的核心概念——依赖注入(Dependency Injection,简称DI)。Spring通过DI管理bean,使得对象之间的依赖关系由Spring负责建立和维护,而非对象自身。这增强了代码的可测试性和可维护性。在非Spring...

    Java EE 框架整合开发⼊⻔到实战——Spring+Spring MVC+MyBatis(微课版)课后习题答案.pdf

    这份文档名为《Java EE 框架整合开发入门到实战——Spring+Spring MVC+MyBatis(微课版)课后习题答案.pdf》,它显然是关于Java EE中流行的三个框架整合使用的教程。这三个框架分别是Spring、Spring MVC和MyBatis,...

Global site tag (gtag.js) - Google Analytics