`
jiangshuiy
  • 浏览: 339230 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Spring注入非单例bean以及scope的作用范围

 
阅读更多

一、 问题描述

       在大部分情况下,容器中的bean都是singleton类型的。

       如果一个singleton bean要引用另外一个singleton bean,或者一个非singleton bean要引用另外一个非singleton bean时,通常情况下将一个bean定义为另一个bean的property值就可以了。不过对于具有不同生命周期的bean来说这样做就会有问题了,比如在调用一个singleton类型bean A的某个方法时,需要引用另一个非singleton(prototype)类型的bean B,对于bean A来说,容器只会创建一次,这样就没法在需要的时候每次让容器为bean A提供一个新的的bean B实例



二、 解决方案

       对于上面的问题Spring提供了三种解决方案:

  • 放弃控制反转。

           通过实现ApplicationContextAware接口让bean A能够感知bean 容器,并且在需要的时候通过使用getBean("B")方式向容器请求一个新的bean B实例。

  • Lookup方法注入。

            Lookup方法注入利用了容器的覆盖受容器管理的bean方法的能力,从而返回指定名字的bean实例。

  • 自定义方法的替代方案。

            该注入能使用bean的另一个方法实现去替换自定义的方法。


三、 实现案例

3.1 放弃IOC

 

    接口类:

package learn.frame.spring.scope.dropioc;

public interface Command {
    public Object execute();
}

 

     实现类:

package learn.frame.spring.scope.dropioc;

public class AsyncCommand implements Command {

    @Override
    public Object execute() {
        return this;
    }

}
 

    业务类:

     ApplicationContextAware和BeanFactoryAware差不多,用法也差不多,实现了ApplicationContextAware接口的对象会拥有        一个ApplicationContext的引用,这样我们就可以已编程的方式操作ApplicationContext。看下面的例子。

public class CommandManager implements ApplicationContextAware {

    //用于保存ApplicationContext的引用,set方式注入   
    private ApplicationContext applicationContext;

    //模拟业务处理的方法   
    public Object process() {
        Command command = createCommand();
        return command.execute();
    }

    //获取一个命令   
    private Command createCommand() {
        return (Command) this.applicationContext.getBean("asyncCommand"); //   
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;//获得该ApplicationContext引用   
    }
}

 

     配置文件:beans-dropioc.xml

    单例Bean commandManager的process()方法需要引用一个prototype(非单例)的bean,所以在调用process的时候先通过            createCommand方法从容器中取得一个Command,然后在执行业务计算。

     scope="prototype"

<?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">
	<!-- 通过scope="prototype"界定该bean是多例的 -->
	<bean id="asyncCommand" class="learn.frame.spring.scope.dropioc.AsyncCommand"
		scope="prototype"></bean>
	
	<bean id="commandManager" class="learn.frame.spring.scope.dropioc.CommandManager">
	</bean> 
	
</beans> 

    测试类:

package org.shupeng.learn.frame.spring.scope;

import java.util.ArrayList;

import org.junit.Before;
import org.junit.Test;
import learn.frame.spring.scope.dropioc.CommandManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class TestCommandManagerDropIOC {
    private ApplicationContext context;

    @Before
    public void setUp() throws Exception {
        context = new ClassPathXmlApplicationContext("beans-dropioc.xml");
    }

    @Test
    public void testProcess() {
        CommandManager manager = (CommandManager) context.getBean("commandManager",
                CommandManager.class);
        System.out.println("第一执行process,Command的地址是:" + manager.process());
        System.out.println("第二执行process,Command的地址是:" + manager.process());
    }
}

 

 Test结果:

第一执行process,Command的地址是:learn.frame.spring.scope.dropioc.AsyncCommand@187c55c
第二执行process,Command的地址是:learn.frame.spring.scope.dropioc.AsyncCommand@ae3364

     通过控制台输出看到两次的输出借中的Command的地址是不一样的,因为我们为asyncCommand配置了scope="prototype"属性,这种方式就是使得每次从容器中取得的bean实例都不一样。

      业务代码和Spring Framework产生了耦合。

 

3.2 Look方法注入

    这种方式Spring已经为我们做了很大一部分工作,要做的就是bean配置和业务类。

 

    新的业务:

package learn.frame.spring.scope.lookup;

import learn.frame.spring.scope.dropioc.Command;

public abstract class CommandManager {

    //模拟业务处理的方法   
    public Object process() {
        Command command = createCommand();
        return command.execute();
    }

    //获取一个命令   
    protected abstract Command createCommand();

}

 

    配置文件:beans-lookup.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">
	<!-- 通过scope="prototype"界定该bean是多例的 -->
	<bean id="asyncCommand" class="learn.frame.spring.scope.dropioc.AsyncCommand"
		scope="prototype"></bean>
		
	<bean id="commandManager" class="learn.frame.spring.scope.lookup.CommandManager">  
            <lookup-method name="createCommand" bean="asyncCommand"/>  
        </bean>
</beans> 

 

    变化部分:

  • 修改CommandManager类为abstract的,修改createCommand方法也为abstract的。
  • 去掉ApplicationContextAware的实现及相关set方法和applicationContext变量定义
  • 修改bean配置文件,在commandManager Bean中增加<lookup-method name="createCommand" bean="asyncCommand"/>。

    测试类:

package learn.frame.spring.scope;

import org.junit.Before;
import org.junit.Test;
import learn.frame.spring.scope.lookup.CommandManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestCommandManagerLookup {
    private ApplicationContext context;

    @Before
    public void setUp() throws Exception {
        context = new ClassPathXmlApplicationContext("beans-lookup.xml");
    }

    @Test
    public void testProcess() {
        CommandManager manager = (CommandManager) context.getBean("commandManager",
                CommandManager.class);
        System.out.println("第一执行process,Command的地址是:" + manager.process());
        System.out.println("第二执行process,Command的地址是:" + manager.process());
    }
}

 

 测试结果:

第一执行process,Command的地址是:learn.frame.spring.scope.dropioc.AsyncCommand@5bb966
第二执行process,Command的地址是:learn.frame.spring.scope.dropioc.AsyncCommand@1e903d5

 控制台打印出的两个Command的地址不一样,说明实现了。

 

<lookup-method>标签中的name属性就是commandManager Bean的获取Command实例(AsyncCommand)的方法,也就createCommand方法,bean属性是要返回哪种类型的Command的,这里是AsyncCommand。

<public|protected> [abstract] <return-type> theMethodName(no-arguments)
  •       被注入方法不一定是抽象的,如果被注入方法是抽象的,动态生成的子类(这里就是动态生成的CommandManager的子类)会实现该方法。否则,动态生成的子类会覆盖类里的具体方法。
  • 为了让这个动态子类得以正常工作,需要把CGLIB的jar文件放在classpath里,这就是我们引用cglib包的原因。
  • Spring容器要子类化的类(CommandManager)不能是final的,要覆盖的方法(createCommand)也不能是final的。

Lookup方法注入干净整洁,易于扩展,更符合Ioc规则,所以尽量采用这种方式。


四、 原理分析(bean的scope属性范围)

       scope用来声明IOC容器中的对象应该处的限定场景或者说该对象的存活空间,即在IOC容器在对象进入相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象。

 

       Spring容器最初提供了两种bean的scope类型:singleton和prototype,但发布2.0之后,又引入了另外三种scope类型,即request,session和global session类型。不过这三种类型有所限制,只能在web应用中使用,也就是说,只有在支持web应用的ApplicationContext中使用这三个scope才是合理的。

       可以使用bean的singleton或scope属性来指定相应对象的scope,其中,scope属性只能在XSD格式的文档生命中使用,类似于如下代码所演示的形式:

 

DTD:
<bean id ="mockObject1" class="..." singleton="false" />
XSD:
<bean id ="mockObject1" class="..."   scope="prototype" />

       注意:这里的singleton和设计模式里面的单例模式不一样,标记为singleton的bean是由容器来保证这种类型的bean在同一个容器内只存在一个共享实例,而单例模式则是保证在同一个Classloader中只存在一个这种类型的实例。

 

4.1. singleton

      singleton类型的bean定义,在一个容器中只存在一个实例,所有对该类型bean的依赖都引用这一单一实例。singleton类型的bean定义,从容器启动,到他第一次被请求而实例化开始,只要容器不销毁或退出,该类型的bean的单一实例就会一直存活。

       通常情况下,如果你不指定bean的scope,singleton便是容器默认的scope,所以,下面三种配置,形式实际上达成的是同样的效果:

DTD or XSD:
<bean id ="mockObject1" class="..." />
DTD:
<bean id ="mockObject1" class="..." singleton="true" />
XSD:
<bean id ="mockObject1" class="..."   scope="singleton" />

 

4.2 prototype

       scope为prototype的bean,容器在接受到该类型的对象的请求的时候,会每次都重新生成一个新的对象给请求方。

       虽然这种类型的对象的实例化以及属性设置等工作都是由容器负责的,但是只要准备完毕,并且对象实例返回给请求方之后,容器就不在拥有当前对象的引用,请求方需要自己负责当前对象后继生命周期的管理工作,包括该对象的销毁。也就是说,容器每次返回请求方该对象的一个新的实例之后,就由这个对象“自生自灭”了。

    可以用以下方式定义prototype类型的bean:

DTD:
<bean id ="mockObject1" class="..." singleton="false" />
XSD:
<bean id ="mockObject1" class="..."   scope="prototype" />

 

4.3 request ,session和global session

      这三个类型是spring2.0之后新增的,他们不像singleton和prototype那么通用,因为他们只适用于web程序,通常是和XmlWebApplicationContext共同使用。

 

     request:

 

<bean id ="requestPrecessor" class="...RequestPrecessor"   scope="request" />

       Spring容器,即XmlWebApplicationContext 会为每个HTTP请求创建一个全新的RequestPrecessor对象,当请求结束后,该对象的生命周期即告结束。当同时有10个HTTP请求进来的时候,容器会分别针对这10个请求创建10个全新的RequestPrecessor实例,且他们相互之间互不干扰,从不是很严格的意义上说,request可以看做prototype的一种特例,除了场景更加具体之外,语意上差不多。

 

 

        session

       对于web应用来说,放到session中最普遍的就是用户的登录信息,对于这种放到session中的信息,我们我们可以使用如下形式的制定scope为session:

 

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

        Spring容器会为每个独立的session创建属于自己的全新的UserPreferences实例,他比request scope的bean会存活更长的时间,其他的方面真是没什么区别。

 

     global session:

 

<bean id ="userPreferences" class="...UserPreferences"   scope="globalsession" />

      global session只有应用在基于porlet的web应用程序中才有意义,他映射到porlet的global范围的session,如果普通的servlet的web 应用中使用了这个scope,容器会把它作为普通的session的scope对待。

(我只是听说过porlet这个词,好像是和servlet类似的一种java web技术,大家以后遇到的时候可以搜一下!)

 

五、 新的扩展(注解方式)

    自Spring3.x开始,增加了@Async这样一个注解,Spring 文档里是这样说的:

 

 The @Async annotation can be provided on a method so that invocation of that method will occur asynchronously. </br>
In other words, the caller will return immediately upon invocation and the actual execution of the method will </br>
occur in a task that has been submitted to a Spring TaskExecutor. 
 

 

就是说让方法异步执行。

 

 

参考文档:

spring配置文件中scope属性(转)

Spring方法注入非单例bean的调用

Spring向单例中注入非单例实例——方法注入【新增加注解方式】

分享到:
评论

相关推荐

    spring bean的生命周期

    - **销毁回调**:当Spring容器关闭时,或者单例Bean不再需要时,会调用`@PreDestroy`注解的方法或者在XML中指定的`destroy-method`属性的方法。 - ** Prototype Beans不保证销毁**:由于Prototype Bean可能有多个...

    Spring之核心容器bean

    单例bean在整个应用中只有一个实例,而原型bean每次请求都会创建新的实例。其他作用域主要适用于Web应用。 **生命周期管理** Spring容器对bean的生命周期进行管理,包括初始化、活跃期和销毁。你可以为bean定义...

    Spring容器中Bean的作用域编程开发技术共3页.pd

    在Spring框架中,Bean的作用域是其生命周期管理的关键部分,它决定了Bean的创建、共享以及销毁方式。本篇内容将深入探讨Spring容器中Bean的作用域编程开发技术,以帮助开发者更好地理解和利用这些特性来优化应用的...

    详解Spring中bean的作用域

    Singleton 作用域和 GOF 设计模式中的单例是完全不同的,单例设计模式表示一个 ClassLoader 中只有一个 class 存在,而这里的 singleton 则表示一个容器对应一个 bean,也就是说当一个 bean 被标识为 singleton 时候...

    spring bean life cycle

    `scope`属性可以设定Bean的作用域,如单例(singleton)或多例(prototype)。 总的来说,Spring Bean生命周期的理解和灵活运用,能帮助我们更好地控制Bean的行为,实现更高效、更可控的依赖管理和资源管理。通过...

    Spring之scope讲解测试示例代码

    在提供的示例代码中,你可能看到了如何在Spring配置文件中定义这些scope,以及如何在Java代码中注入和使用这些Bean。`main`函数可能用于非Web环境下的测试,而Web工程部分则展示了如何在Web应用中使用Bean。 在...

    spring自动扫描和管理Bean的示例

    - **@Scope**:定义Bean的作用域,如单例(singleton)、原型(prototype)等。 - **@PostConstruct** 和 **@PreDestroy**:标记初始化和销毁方法,Spring会在Bean创建后和销毁前调用这些方法。 在`spring_autozp_...

    SSH笔记-bean的作用域

    在Spring框架中,Bean的作用域是管理对象生命周期和作用范围的关键概念。Bean的定义通常包含在XML配置文件中,通过`&lt;bean&gt;`标签进行配置。当我们谈论"SSH笔记-bean的作用域"时,这里SSH指的是Spring、Struts和...

    Spring Bean 加载顺序 .

    默认情况下,Bean是单例(Singleton),但也可以配置为原型(Prototype)或其他作用域。 4. **Bean的依赖解析**: Spring会分析Bean之间的依赖关系,通过@Autowired、@Qualifier等方式找到合适的依赖注入。在这个...

    Spring中与Bean相关的接口

    默认情况下,Bean是单例(Singleton),但在`@Scope`注解的帮助下,我们可以创建原型(Prototype)、会话(Session)或请求(Request)作用域的Bean。 最后,`AutowireCandidateResolver`和`BeanFactoryAware`接口...

    Spring实战之Bean的作用域singleton和prototype用法分析

    在Spring框架中,Bean的作用域是决定如何管理和创建Bean实例的关键概念。本篇文章将深入探讨两种主要的作用域:singleton和prototype,并通过实例分析其用法和注意事项。 首先,`singleton`是Spring默认的作用域,...

    粗略实现spring创建bean

    Spring 框架是 Java 后端开发中的核心组件,它提供了一种强大的依赖注入(Dependency Injection,简称 DI)机制,使得开发者可以更加专注于业务逻辑,而非对象的创建与管理。"粗略实现spring创建bean"这个主题主要...

    spring 中特殊bean用法

    2. **Prototype Scope**: 默认情况下,Spring Bean是单例的,即容器只会创建一个实例。但有时我们需要每个请求都创建一个新的Bean实例,这时可以使用`@Scope("prototype")`注解将Bean声明为原型范围。这样每次请求时...

    Spring项目bean基础配置代码

    本文将深入探讨Spring项目中的Bean基础配置、别名配置以及作用范围配置。 首先,我们来理解Bean的基础配置。在Spring中,一个Bean的定义通常包含以下几个关键属性: 1. `id`:这是Bean的唯一标识符,用于在配置...

    Spring依赖注入检查.

    控制bean的作用域,如单例(singleton)、原型(prototype)等,影响依赖注入的生命周期。 9. **@Primary注解**: 当有多个候选bean时,标记为`@Primary`的bean会被优先选择。 理解并熟练运用Spring的依赖注入,...

    day38 16-Spring的Bean的装配:注解的方式

    5. `@Scope`: 定义Bean的作用域,如`prototype`(原型)和`singleton`(单例)。 6. `@PostConstruct` 和 `@PreDestroy`: 分别标记初始化和销毁方法,在Bean的生命周期中调用。 注解装配的步骤如下: 1. **启用...

    Spring Bean生命周期&BeanDefinitions1

    Spring的BeanDefinition是用来存储Bean的配置信息,包括Bean的类名、作用域、属性值、依赖的其他Bean、是否为单例、是否延迟加载等。这些信息在容器启动时解析并转化为BeanDefinition对象,后续的Bean实例化和管理都...

    Java开发学习(三)----Bean基础配置及其作用范围.doc

    - **scope属性**:bean的作用范围决定了Spring容器如何管理bean的生命周期和实例化策略。 - **单例(singleton)**:这是默认的bean作用范围。当设置为`singleton`时,Spring容器会在启动时创建bean的一个实例,并...

    spring注解开发--Bean加载控制.zip

    通过以上这些注解,开发者可以更精细地控制Spring容器中bean的创建、初始化、销毁以及依赖关系。理解并熟练运用这些工具,能够提高代码的可维护性和灵活性。在实际项目中,合理利用这些特性,可以有效地减少配置文件...

    浅谈spring中scope作用域

    spring 中的 scope 作用域是指在spring 框架中,bean 的实例化和生命周期的管理。Scope 作用域决定了 bean 的实例化方式、生命周期和作用域。 singleton 作用域 ----------------- 在 spring 中,默认的作用域是 ...

Global site tag (gtag.js) - Google Analytics