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 功能相同)

相关推荐
云和数据.ChenGuang7 小时前
Python 重写(Override)深度解析:从 MRO 到 super() 的完整机制
java·开发语言·人工智能·python·langchain·1024程序员节
摇滚侠7 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 12
java·数据库·笔记
silvamux_10 小时前
10款模型写种草笔记:可发布性、返工来源与成本数据缺口复盘
java·数据库·笔记
步行cgn10 小时前
StringHttpMessageConverter 详解:Spring MVC 中字符串读写的默认转换器
java·spring·mvc
摇滚侠10 小时前
《On Java 中文版 基础卷》阅读笔记 实现隐藏 11
java·笔记·python
小蒜学长11 小时前
基于Spring Boot+Vue3的在线医疗咨询系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·在线问诊·在线医疗咨询系统
Deng桑11 小时前
SpringAI写第一个Agent-把工单循环跑起来
java·agent·工具调用·spring ai
步行cgn13 小时前
@ResponseBody 详解:Spring MVC 中把返回值直接写入响应体
java·spring·mvc
jason成都14 小时前
RTKLIB 的 Java 移植版来了:rtklib-java v3.3.0 功能全景
java·gnss·rtklib·rtklib_java
LazyHunter914 小时前
JDK 28:Java 下一步方向解析与新特性展望
java·jdk