spring ApplicationContextAware和@Scope("prototype")妙用

本文介绍在Spring framework项目中,利用ApplicationContextAware接口让Bean访问applicationcontext,以引用非单例Bean。通过@Scope(\prototype\)实现引用,还给出具体测试,包括准备app.properties文件、编写相关类代码,最后运行Edu05得出结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在spring framework的项目需在一个Bean引用另外一个Bean,且另外一个Bean需要新生成,所以在这里ApplicationContextAware接口就能启重要作用,因为这个有如下方法

  public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.ctx=ctx;
    }

这样可以让具体Bean来访问applicationcontext,从通过可以访问到系统中另外一个Bean且需要生新的Bean,在spring中可以引用非单例Bean,引用方法很单,通过@Scope("prototype")这个来的,以下是我进行的测试,完全没有问题,主要是通过java config类来实现

1.先准一个app.properties文件,有如下内容
students=王天天,张小明,tom,jeffery,张天宠
courses=java,c++,c#,计算机基础

2.其它相关类如下,具体的代码就不说明了,相信可以看明白

package com.edu05;

import java.util.ArrayList;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class Student {    
    
    private String name;    
    @Autowired
    private ArrayList<Course>  courses;
     

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }    
    
    public void getCourses() {
        //System.out.println("course count "+this.courses.size());
        for(Course course : courses) {
            System.out.println(this.name+"   选择了   "+course.getName()+" 课程 !");            
        }         
    }
}

package com.edu05;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class Course {    
    private String name;     

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

package com.edu05;

import java.util.ArrayList;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ChoseService {    
    
    @Autowired
    private ArrayList<Student>  sts; 


    
    public void getCoures() {
        for(Student st : sts) {
            st.getCourses();
        }
    }

    

}

package com.edu05;

import java.util.ArrayList;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

@Configuration
@ComponentScan
@PropertySource("app.properties")
public class AppConfig implements ApplicationContextAware  {
    
    private ApplicationContext ctx;
    
    @Autowired
    private Environment  env;
    
    @Bean
    public ArrayList<Course>  initCourse(){
        
        ArrayList<Course> crs=new ArrayList<Course>();
        
        for(String csString: env.getProperty("courses").split(",")) {
               Course cs=ctx.getBean(Course.class);
               cs.setName(csString);
               crs.add(cs);
        }
        
        return crs;
    }
    
    @Bean
    public ArrayList<Student> initStudents(){
        ArrayList<Student>  sts=new ArrayList<Student>();
        for(String stString: env.getProperty("students").split(",")) {
            Student st=(Student)ctx.getBean(Student.class);
            st.setName(stString);
            sts.add(st);
        }
        return sts;        
    }

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.ctx=ctx;
    }
}

package com.edu05;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Edu05 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
         
        ChoseService cs=ctx.getBean(ChoseService.class);
         
        cs.getCoures();
        //Student st=ctx.getBean(Student.class);

    }

}
 

3.运行Edu05会有如下结果

 


 

 

 

<think>好的,用户的问题是:使用@RabbitListener创建多个并发消费者时,如何调用具有作用域(scope)的Bean。我需要仔细分析这个问题,并给出合理的解决方案。 首先,我需要回忆一下Spring中的作用域。Spring默认的Bean作用域是单例(singleton),但还有其他的作用域,比如原型(prototype)、请求(request)、会话(session)等。当用户提到“有scope的bean”,可能指的是非单例作用域的Bean,比如原型作用域,每次获取时都是新的实例。或者是请求作用域,但如果在消息监听器中使用请求作用域可能有问题,因为监听器通常不在HTTP请求的上下文中运行。 接下来,用户的问题是关于@RabbitListener的并发消费者。@RabbitListener注解可以通过concurrency属性设置并发消费者的数量,比如concurrency = "5-10"会创建5个初始消费者,并根据需要扩展到10个。每个消费者线程会独立处理消息,这时候如果消费者Bean本身是有作用域的,比如原型作用域,可能需要每个消费者线程使用不同的实例。 但问题在于,默认情况下,Spring的监听器容器可能使用同一个Bean实例来处理所有消息,尤其是在单例作用域下。如果用户希望每个消费者线程使用不同的Bean实例,特别是当Bean具有作用域时,该如何处理? 这里需要考虑Spring的作用域代理。当Bean被定义为作用域代理时,每次从Spring容器中获取该Bean时,实际上会得到一个代理对象,该代理对象在方法调用时会委托给当前作用域的实际Bean实例。例如,对于原型作用域的Bean,每次获取都会是新的实例。 可能用户的情况是,他们的消费者Bean(比如使用@RabbitListener注解的类)本身需要具有某种作用域,比如每个消费者线程需要独立的实例,或者需要访问请求作用域的Bean,这时候需要确保在并发环境下,每个线程能正确获取到对应作用域的Bean实例。 可能的解决方案包括: 1. 将Bean的作用域设置为原型(prototype),并确保每次注入都是新的实例。但需要注意,如果@RabbitListener所在的类本身是单例的,那么注入的原型Bean可能仍然只被初始化一次,除非使用代理。 2. 使用作用域代理,为作用域Bean设置代理模式,例如@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS),这样在注入时,注入的是代理对象,每次方法调用时会解析到正确的Bean实例。 3. 在监听器方法中手动从应用上下文中获取Bean实例,这样每次处理消息时都获取新的实例。例如,使用ApplicationContextAware接口获取上下文,然后调用getBean方法。 但是,手动获取Bean可能不够优雅,且增加代码耦合。使用作用域代理可能是更合适的解决方案。 另外,需要考虑的是,监听器容器在创建消费者时,如何管理这些Bean的作用域。例如,如果监听器Bean本身是原型作用域,那么每个消费者实例都会是独立的,这可能满足需求。但需要确保@RabbitListener的并发设置与Bean的作用域正确配合。 例如,如果有一个MessageListener类,使用@RabbitListener,并且这个类被定义为原型作用域,同时配置了concurrency,那么每个消费者线程是否会获得该类的不同实例?Spring的监听器容器在创建监听器实例时,如果是单例的,那么所有消费者线程可能共享同一个实例,但如果监听器Bean是原型的,容器可能会为每个消费者线程创建一个实例。不过,这可能取决于具体的容器实现配置。 不过,可能用户的情况是,在监听器中需要注入其他有作用域的Bean,比如一个Service类被定义为请求作用域或会话作用域。这时候在并发消费者中,这些作用域可能无法正确工作,因为请求作用域依赖于HTTP请求的上下文,而消费者线程可能没有这样的上下文。 因此,可能需要针对不同的作用域类型进行分析。比如,对于原型作用域的Bean,在监听器中注入时,使用代理确保每次调用时获取新的实例;而对于请求或会话作用域,可能需要额外的配置,或者考虑是否适合在消息监听器中使用。 总结可能的步骤: 1. 确认Bean的作用域类型,例如原型作用域。 2. 使用作用域代理(Scoped Proxy)来注入Bean,确保每次方法调用时获取正确的实例。 3. 配置@RabbitListener的并发设置,确保每个消费者线程能正确使用作用域Bean的实例。 4. 测试验证,确保在并发环境下,各消费者线程使用的Bean实例符合预期。 另外,需要注意,如果作用域Bean是有状态的,并且需要在不同的消息处理之间保持独立,那么原型作用域加代理是正确的选择。否则,如果多个消费者线程共享同一个Bean实例,可能会导致状态混乱。 例如,假设有一个ScopedService类,被定义为原型作用域: @Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) public class ScopedService { // ... } 然后在@RabbitListener的类中注入该服务: @Autowired private ScopedService scopedService; 由于使用了TARGET_CLASS的代理模式,每次调用scopedService的方法时,Spring会创建一个新的实例。但是,如果在监听器的整个生命周期中,scopedService的代理只被注入一次,那么可能所有调用都使用同一个代理,但代理内部会委托给新的实例。或者,可能需要确保每个消费者线程都获得自己的代理实例。 这里可能需要更深入的理解。实际上,当使用作用域代理时,代理对象本身是单例的,但在每次方法调用时,它会解析当前作用域内的实际Bean实例。对于原型作用域,每次方法调用都会得到一个新的实例。但如果在类中注入ScopedService,并在多个地方调用它的方法,那么每次调用都会生成新的实例。但如果在监听器的方法中多次调用scopedService,每次调用都会创建新的实例,这可能不是用户期望的。也许用户希望每个消费者线程在处理消息时,使用同一个实例,但不同消费者线程使用不同的实例。这种情况下,可能需要自定义作用域,比如线程作用域,但这需要更多的配置。 或者,可能用户需要的是,每个消费者实例(即每个@RabbitListener的实例)拥有自己的依赖Bean实例。例如,如果MessageListener是原型作用域的,并且每个消费者线程都有一个MessageListener实例,那么其中注入的ScopedService也会是每个实例一个。这时候,如果ScopedService是原型作用域,那么每个MessageListener实例在创建时都会注入一个新的ScopedService实例。这可能满足需求。 因此,可能的解决方案是: 1. 将@RabbitListener的类设置为原型作用域,这样每个消费者线程会获得该类的不同实例。 2. 在该类中注入的其他Bean,如果需要不同的实例,可以设置为原型作用域,或依赖注入时的作用域代理。 例如: @Component @Scope("prototype") public class MyRabbitListener { @Autowired private ScopedService scopedService; @RabbitListener(queues = "myQueue", concurrency = "5-10") public void handleMessage(String message) { // 使用scopedService处理消息 } } 这样,每个消费者线程会有一个MyRabbitListener的实例,每个实例中的scopedService如果是原型作用域,则每个实例中的scopedService都是不同的实例。 但需要注意,Spring的RabbitListener在默认情况下是如何处理监听器Bean的作用域的。通常,@RabbitListener注解标注在@Bean方法上或@Component类的方法上,而容器默认可能使用单例的Bean。要创建多个监听器实例,可能需要将监听器类设置为原型作用域,并且确保容器为每个并发消费者创建不同的实例。 此外,Spring的SimpleMessageListenerContainer在创建监听器实例时,对于并发处理,会为每个消费者线程创建一个新的实例,如果监听器Bean是原型作用域的话。或者,可能是在容器启动时创建指定数量的实例,每个实例由一个线程使用。 需要查阅Spring AMQP的文档,确认SimpleMessageListenerContainer或DirectMessageListenerContainer在处理并发Bean作用域时的行为。 根据Spring AMQP的文档,当使用@RabbitListener时,concurrency设置的是消费者的数量,每个消费者线程会调用同一个监听器实例的onMessage方法,除非监听器Bean的作用域被配置为原型,此时容器会为每个消费者线程创建一个新的实例。 例如,在配置中,如果监听器Bean是原型作用域,那么每个消费者线程将获得一个独立的实例,这样每个线程中的依赖注入的Bean如果是原型作用域的,也会是独立的实例。 因此,解决方案可能是: - 将@RabbitListener标注的Bean设置为原型作用域,以确保每个消费者线程有独立的实例。 - 在该Bean中注入需要作用域的依赖,这些依赖也配置为适当的作用域(如原型)并使用代理。 这样,每个消费者线程的监听器实例将拥有自己的依赖实例,从而避免并发问题。 总结步骤: 1. 定义监听器类为原型作用域,使用@Component@Scope("prototype")。 2. 在监听器类中注入需要作用域的Bean,这些Bean同样配置为所需的作用域(如prototype)并启用代理。 3. 配置@RabbitListener的concurrency参数以启动多个消费者。 4. 确保容器正确地为每个消费者线程创建监听器实例,并注入各自的作用域Bean。 这样,每个消费者线程将使用独立的监听器实例及其依赖,从而正确调用有作用域的Bean。</think>在Spring AMQP中使用`@RabbitListener`实现多消费者并发时,若需调用具有**非单例作用域(如prototype)**的Bean,需通过以下步骤确保作用域Bean的正确注入: --- ### 一、问题核心 - **默认单例Bean的局限性**:若监听器类或注入的Bean是单例,多个消费者线程共享同一实例,导致状态冲突。 - **作用域Bean的需求**:希望每个消费者线程使用独立的Bean实例(如prototype作用域)。 --- ### 二、解决方案 #### 1. 配置监听器类为原型作用域 ```java @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS) public class MyRabbitListener { @Autowired private ScopedService scopedService; // 注入需作用域的Bean @RabbitListener(queues = "myQueue", concurrency = "5-10") public void handleMessage(String message) { scopedService.process(message); // 每个线程使用独立实例 } } ``` - **关键点**:`proxyMode = ScopedProxyMode.TARGET_CLASS` 为监听器类生成代理,确保每次注入时解析到新实例。 #### 2. 定义作用域Bean(以prototype为例) ```java @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS) public class ScopedService { public void process(String msg) { // 处理消息的逻辑 } } ``` #### 3. 验证容器行为 - **消费者线程与Bean实例关系**:每个线程会获得独立的`MyRabbitListener`实例,其内部注入的`ScopedService`也会是新的实例。 - **并发安全性**:若Bean无共享状态,原型作用域可避免线程竞争。 --- ### 三、高级场景:自定义作用域 若需更精细控制(如线程作用域),可自定义作用域并注册: ```java public class ThreadScope implements Scope { private final ThreadLocal<Map<String, Object>> threadLocal = ThreadLocal.withInitial(HashMap::new); @Override public Object get(String name, ObjectFactory<?> objectFactory) { return threadLocal.get().computeIfAbsent(name, k -> objectFactory.getObject()); } // 其他方法省略... } // 注册自定义作用域 @Configuration public class ScopeConfig implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { beanFactory.registerScope("thread", new ThreadScope()); } } // 使用自定义作用域 @Component @Scope("thread") public class ThreadScopedBean { /* ... */ } ``` --- ### 四、注意事项 1. **代理模式必须启用**:确保作用域Bean配置`proxyMode`,否则注入的仍是单例代理。 2. **避免请求/会话作用域**:消息监听通常无HTTP上下文,此类作用域需额外配置(如`RequestContextListener`)。 3. **性能权衡**:原型作用域会增加实例化开销,需评估是否必要。 --- ### 五、验证步骤 1. **日志调试**:在Bean构造方法中添加日志,观察实例化次数。 2. **状态验证**:在Bean中增加计数器,确认各消费者线程操作独立实例。 通过上述配置,可确保在多消费者并发场景下,正确调用具有作用域的Bean实例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值