Spring 注入多例 Bean

如何在单例 bean 中注入多例 Bean ?

配置多例 Bean

java 复制代码
public interface ProtoService {

	void test1();
	
	void test2();
}
java 复制代码
//使用 @Service 注册
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ProtoServiceImpl implements ProtoService {
	private static final Logger log = LoggerFactory.getLogger(ProtoServiceImpl.class);

	@Override
	public void test1() {
		log.info(this.toString());
	}

	@Override
	public void test2() {
		log.info(this.toString());
	}
}

//或者使用 @Bean 注册

public class ProtoServiceImpl implements ProtoService {
	private static final Logger log = LoggerFactory.getLogger(ProtoServiceImpl.class);

	@Override
	public void test1() {
		log.info(this.toString());
	}

	@Override
	public void test2() {
		log.info(this.toString());
	}
}

@Configuration
public class BeanConfig {

	@Bean
	@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
	public ProtoService protoService() {
		return new ProtoServiceImpl();
	}
}

再来个单例 Bean

在单例 bean 中注入多例 bean

java 复制代码
public interface ScopeService {

	void test();
}

错误示例

java 复制代码
@Service
public class ScopeServiceImpl implements ScopeService {
	
	//如果bean不存在,启动项目时会报错
	@Autowired
	private ProtoService protoService;

	@Override
	public void test() {
		protoService.test1();
		protoService.test2();
	}

}

执行test()方法,打印出了同相的对象

java 复制代码
ProtoServiceImpl@390e6145
ProtoServiceImpl@390e6145

可见,直接在单例bean中注入的方式是没用的

方式一 使用 @Lookup

使用的@Lookup的方法需要符合如下的签名: <public|protected> abstract theMethodName(no-arguments); 访问修饰符不能用private

java 复制代码
@Service
public class ScopeServiceImpl implements ScopeService {
	
	//如果bean不存在,启动项目时不会报错
	@Lookup
	ProtoService getProtoService() {
		//返回 null 即可
		return null;
	}

	@Override
	public void test() {
		getProtoService().test1();
		getProtoService().test2();
	}

}

执行test()方法,打印出了不同的对象

java 复制代码
ProtoServiceImpl@73dae6cf
ProtoServiceImpl@7102c834

可见,直接在单例bean中用 @Lookup 注入的方式是可行的

方式二 使用 ObjectProvider

java 复制代码
@Service
public class ScopeServiceImpl implements ScopeService {
	
	//如果bean不存在,启动项目时不会报错
	@Autowired
	private ObjectProvider<ProtoService> protoService;

	@Override
	public void test() {
//		protoService.getIfAvailable().test1();
//		protoService.getIfUnique().test1();
		protoService.getObject().test1();
		protoService.getObject().test2();
	}

}

执行test()方法,打印出了不同的对象

java 复制代码
ProtoServiceImpl@fe132f2
ProtoServiceImpl@22ebe5dc

可见,直接在单例bean中用 ObjectProvider 注入的方式是可行的

方式三 使用 ScopedProxyMode

声明多例 Bean 的 @Scope 注解,还有一个 proxyMode 属性,proxyMode 属性的可选值分别为 DEFAULT,NO,INTERFACES,TARGET_CLASS,其中 DEFAULT 为默认值,而 DEFAULT 等同于 NO。 NO 表示:不创建范围代理。 当与非单例的实例一起使用时,此代理模式通常没有用,如果要将其用作依赖项,应使用 INTERFACES 或 TARGET_CLASS 代理模式。 INTERFACES 表示:创建一个实现目标对象的类公开的所有接口的 JDK 动态代理。 TARGET_CLASS 表示:创建一个基于类的代理(使用 CGLIB)。

java 复制代码
//使用 @Service 注册
//由于ProtoServiceImpl是实现接口的,所以也可以使用 INTERFACES
//@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.INTERFACES)
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Service
public class ProtoServiceImpl implements ProtoService {
	private static final Logger log = LoggerFactory.getLogger(ProtoServiceImpl.class);

	@Override
	public void test1() {
		log.info(this.toString());

	}

	@Override
	public void test2() {
		log.info(this.toString());
	}
}

//或者使用 @Bean 注册

@Configuration
public class BeanConfig {

	@Bean
	//由于ProtoServiceImpl是实现接口的,所以也可以使用 INTERFACES
	//@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.INTERFACES)
	@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
	public ProtoService protoService() {
		return new ProtoServiceImpl();
	}
}

public class ProtoServiceImpl implements ProtoService {
	private static final Logger log = LoggerFactory.getLogger(ProtoServiceImpl.class);

	@Override
	public void test1() {
		log.info(this.toString());

	}

	@Override
	public void test2() {
		log.info(this.toString());
	}
}
java 复制代码
@Service
public class ScopeServiceImpl implements ScopeService {
	//如果bean不存在,启动项目时会报错
	@Autowired
	private ProtoService protoService;

	@Override
	public void test() {
		protoService.test1();
		protoService.test2();
	}
}

执行test()方法,打印出了不同的对象

java 复制代码
ProtoServiceImpl@4f36c34a
ProtoServiceImpl@7a7df4a1

可见,启用 @Scope 的 proxyMode 属性的方式是可行的,而且配置更简单,项目启动时即检查 Bean 的注入情况,比 @Lookup 和 ObjectProvider 的功能更强。

@Lookup 的局限性

前面的单例 Bean 都是使用 @Service 注册的,下面使用@Bean注册

java 复制代码
@Configuration
public class BeanConfig {

	@Bean
	@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
	public ProtoService protoService() {
		return new ProtoServiceImpl();
	}
	
	@Bean
	public ScopeService scopeService() {
		return new ScopeServiceImpl();
	}
}

使用 @Lookup 注入

java 复制代码
public class ScopeServiceImpl implements ScopeService {
	
	@Lookup
	ProtoService getProtoService() {
		return null;
	}

	@Override
	public void test() {
		getProtoService().test1();
		getProtoService().test2();
	}

执行test()方法,结果报错了

java 复制代码
java.lang.NullPointerException: null
	at ScopeServiceImpl.test(ScopeServiceImpl.java:29) ~[classes/:na]

可见, @Lookup 不能将多例 Bean 注入进使用 @Bean 注册的 bean中

使用 ObjectProvider 注入

java 复制代码
public class ScopeServiceImpl implements ScopeService {
	
	@Autowired
	private ObjectProvider<ProtoService> protoService;

	@Override
	public void test() {
//		protoService.getIfAvailable().test1();
//		protoService.getIfUnique().test1();
		protoService.getObject().test1();
		protoService.getObject().test2();
	}

}

执行test()方法,打印出了不同的对象

java 复制代码
ProtoServiceImpl@59d1047b
ProtoServiceImpl@4b0993d9

可见,ObjectProvider 注入的方式也适用这种情况,太强大了

启用 ScopedProxyMode

java 复制代码
@Configuration
public class BeanConfig {

	@Bean
	//由于ProtoServiceImpl是实现接口的,所以也可以使用 INTERFACES
	//@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.INTERFACES)
	@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
	public ProtoService protoService() {
		return new ProtoServiceImpl();
	}
	
	@Bean
	public ScopeService scopeService() {
		return new ScopeServiceImpl();
	}
}

public class ScopeServiceImpl implements ScopeService {
	//如果bean不存在,启动项目时会报错
	@Autowired
	private ProtoService protoService;

	@Override
	public void test() {
		protoService.test1();
		protoService.test2();
	}
}

执行test()方法,打印出了不同的对象

java 复制代码
ProtoServiceImpl@4f36c34a
ProtoServiceImpl@7a7df4a1

可见,启用 ScopedProxyMode 的方式也适用这种情况,功能最强大

结语

当然,注入多例 Bean 的方式还有很多 比如:使用底层方法 ApplicationContext.getBean() 比如:使用xml配置lookup-method(与 @Lookup 功能相同) 比如:使用xml配置replaced-method(功能太强大了,同时使用也太复杂) 比如:使用xml配置<aop:scoped-proxy/>(与启动 ScopedProxyMode 功能相同)

相关推荐
智慧物业老杨3 小时前
物业数字化落地思考:真正的转型,是底层数据秩序的重构
java·大数据·人工智能·微服务·系统架构
星空6 小时前
金蝶苍穹build.gradle配置
java·build.gradle
考虑考虑7 小时前
Springboot环境变量占位符语法
spring boot·后端·spring
步行cgn9 小时前
Spring 基于 XML 的自动装配:byName 详解
java·后端·spring
不会c+9 小时前
Day02 - 需求分析与用例建模
spring
大帅点兵9 小时前
Apache Ant 1.9.9 完整讲解
java
LuTshoes9 小时前
spring ai 实战 手搓 PlaneExecuteAgent
java·人工智能·spring·ai
这料鬼有毒10 小时前
二刷hot100-73.矩阵置零
java
caoerzhong10 小时前
JeeWMS 开源仓库管理系统二次开发与接口对接实战:Java WMS 如何与 ERP、MES 和自动化设备打通
java·开源
长谷深风11110 小时前
评测AI Agent:三种裁判各司其职
java·大数据·开发语言·人工智能·ai agent