Spring 与组件注入相关的注解之 @Scope 和 @Lazy

前言:

通过上一个demo,又进一步体会到了如何实现 spring 组件的注入。下面对注入到 IOC 容器中的组件的作用域作进一步说明。另外,这里还涉及到注入到容器中的 bean 的生命周期的问题。在后续博客中还会针对 bean 的生命周期作相关介绍。

实验:
  1. 准备配置类,注入 Person 组件:
package com.uestc.auto.xiaoxie.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.uestc.auto.xiaoxie.bean.Person;

@Configuration // 配置类
public class SpringConfig1 {
   @Bean
   public Person person(){
   	// System.out.println("向容器中添加Person组件");
   	return new Person("kristina", 24);
   }
}
  1. 编写测试方法:为简化代码,将 ApplicationContext 的创建和容器的关闭分别放在 junit 的 before() 和 after() 方法中,这样不必在每一个单元测试方法中写重复的代码。
private AnnotationConfigApplicationContext context;
	
	@Before
	public void beforeMethod(){
		context = new AnnotationConfigApplicationContext(SpringConfig1.class);
	}
	@Test
	public void testScope() {
		Person person = (Person)context.getBean("person");
		Person person2 = (Person)context.getBean("person");
		System.out.println(person == person2); // true,即默认创建的 bean 是单例的
	}

	@After
	public void afterMethod(){
		context.close();
	}
  1. 修改配置类:在注入 Person 对象的时候,通过 @Scope 注解的 scopeName 属性指定 bean 的作用域为多例。再次进行测试,发现两次获取的 person 并非同一个对象了。
	@Bean
   @Scope(scopeName=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
   public Person person(){
   		System.out.println("向容器中添加Person组件");
   		return new Person("kristina", 24);
   }

由下图可以看出,scopeName 四种不同的取值对应了四种不同的作用域:

scopeName的取值对应的含义
ConfigurableBeanFactory.SCOPE_PROTOTYPE多例
ConfigurableBeanFactory.SCOPE_SINGLETON默认值,单例
WebApplicationContext.SCOPE_REQUEST如果设置为此值的话,在web应用中一个请求会是一个对象。
WebApplicationContext.SCOPE_SESSION如果设置为此值的话,在web应用中一个session会话会是一个对象。
  1. 下面对单实例和多实例对象的创建时机进行测试:为更好地看到实验效果,在配置类的 public Person person() 方法体中多添加一句 sysout 语句:System.out.println(“向容器中添加Person组件”)。然后,将单元测试方法(testScope)的方法体注释掉,即只创建 ApplicationContext 对象。
    在单例模式下,重新运行,发现在创建 context 对象的时候就会调用 person() 方法,注入 Person 组件;
    改为多例模式,重新运行,发现创建 context 对象的时候并不会去调用 person() 方法,而是在真正 context.getBean() 的时候才会注入 Person 组件。
即在单例模式下,在IOC容器初始化的时候就会创建对象。后面用的时候,拿到的也都是同一个对象;
而在多例模式下,IOC容器初始化的时候并不会创建对象,在 getBean() 的时候才会创建相应的对象。
  1. @Lazy 注解:
    上面也提到了,在单例模式下,IOC容器初始化的时候就会创建相应的bean;懒加载(@Lazy)可以实现的效果:用的时候(getBean())才去创建相应的bean,而不是容器初始化的时候就创建。
    @Lazy
//	@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
   	@Bean 
   	public Person person(){
   		System.out.println("向容器中添加Person组件");
   		return new Person("Arianna", 28);
   	}
<think>好的,我现在需要帮助用户理解如何在Spring Framework中正确地将@Scope注解@Autowired一起使用。首先,我得回顾一下用户提供的引用内容,特别是引用1引用2,因为它们提到了@Autowired@Scope相关使用情况。 用户的问题是关于@Scope@Autowired联合使用的场景配置方式。我需要先明确这两个注解各自的作用。@Autowired用于自动装配依赖,而@Scope用于定义Bean的作用域,比如单例(singleton)、原型(prototype)等。接下来,我需要考虑它们一起使用时可能出现的问题,比如作用域不同导致的依赖注入问题。 根据引用1,@Autowired默认按类型匹配,如果有多个同类型Bean,需要用@Qualifier来指定名称。但这里的问题是关于作用域的,比如当原型作用域的Bean注入到单例Bean中时,可能不会每次获取新的实例,导致问题。这时候可能需要结合@Scope@Lookup或者Provider来确保每次获取新实例。 接下来,我需要查看用户提供的引用内容。引用1提到@Autowired通过类型匹配,如果同类型多个Bean需要@Qualifier,这可能作用域无关,但需要确认。引用2中的示例展示了@Autowired在属性、构造方法setter方法上的使用,但没有涉及@Scope。引用3提到在@Configuration中使用@Autowired报错,可能涉及作用域或Bean的初始化顺序问题。 现在,我需要整理常见的场景:比如单例Bean注入原型Bean时,如何确保每次获取新实例。这时候可能需要将原型Bean的作用域设置为proxyMode,或者使用Provider。另外,配置类中使用@Bean方法时,如何结合@Scope@Autowired。 然后,我需要给出具体的配置方式,例如在定义Bean时使用@Scope注解,并说明如何正确注入。比如,使用@Scope("prototype")注解在类上,然后在需要注入的地方使用@Autowired,并可能需要结合@Lazy或者Provider来避免单例Bean中的早期初始化问题。 还需要注意可能出现的错误,比如在配置类中直接@Autowired注入一个作用域为原型的Bean,可能会导致问题,这时候需要用方法参数来传递依赖,或者使用其他方式。 最后,总结使用时的注意事项,比如作用域的生命周期管理、代理模式的选择,以及如何测试验证作用域是否生效。同时,可能需要提到在单元测试中如何验证不同作用域Bean的行为是否正确。 现在,我需要将这些思考整理成一个结构清晰的回答,分点说明场景、配置方式注意事项,并确保引用相关的引用内容。例如,在提到@Autowired的类型匹配时引用[^1],在提到原型作用域注入问题时引用[^2]或[^3]中的相关内容。同时,生成的相关问题需要围绕作用域自动装配的深入应用展开。</think>### @Scope注解@Autowired联合使用详解 #### 一、核心概念说明 1. **@Scope注解**:定义Bean的作用域,例如: - `singleton`:单例模式(默认) - `prototype`:每次请求新实例 - `request`/`session`:Web应用中的作用域 2. **@Autowired注解**:通过类型自动注入依赖,若存在多个同类型Bean需配合`@Qualifier`。 #### 二、典型使用场景配置方式 **场景1:单例Bean注入原型Bean** 当单例Bean依赖原型Bean时,默认情况下原型Bean仅初始化一次(单例Bean生命周期绑定)。 **解决方案**: - 使用`@Scope`的代理模式(`proxyMode`): ```java @Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) public class PrototypeBean { /*...*/ } @Component public class SingletonBean { @Autowired private PrototypeBean prototypeBean; // 每次调用会生成新实例 } ``` - 或通过`Provider`延迟获取: ```java @Component public class SingletonBean { @Autowired private Provider<PrototypeBean> prototypeBeanProvider; public void doSomething() { PrototypeBean bean = prototypeBeanProvider.get(); } } ``` **场景2:动态配置Bean作用域** 在`@Configuration`类中定义Bean时,结合`@Scope``@Autowired`注入依赖: ```java @Configuration public class AppConfig { @Autowired private Environment env; @Bean @Scope("prototype") public DataSource dataSource() { // 根据环境变量动态配置数据源 return new DriverManagerDataSource(env.getProperty("db.url")); } } ``` 注意:若在`@Configuration`类中直接`@Autowired`注入作用域为`prototype`的Bean,可能因生命周期差异导致问题,建议通过方法参数传递依赖[^3]。 #### 三、注意事项 1. **代理模式选择**: - `ScopedProxyMode.TARGET_CLASS`:使用CGLIB代理(适用于类) - `ScopedProxyMode.INTERFACES`:使用JDK动态代理(适用于接口) 2. **循环依赖风险**:复杂作用域组合可能引发循环依赖,需通过`@Lazy`延迟加载解决。 3. **测试验证**:使用`ApplicationContext.getBean()`验证作用域是否生效: ```java PrototypeBean bean1 = context.getBean(PrototypeBean.class); PrototypeBean bean2 = context.getBean(PrototypeBean.class); assertNotSame(bean1, bean2); // 原型作用域应返回不同实例 ``` #### 四、常见错误示例 **错误:单例Bean直接注入原型Bean** ```java @Component public class SingletonService { @Autowired private PrototypeService prototypeService; // 原型Bean被单例持有,仅初始化一次 } ``` **修复方式**:按场景1中方案添加代理或使用`Provider`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值