Spring 注入多例 Bean

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

配置多例 Bean

public interface ProtoService {

	void test1();
	
	void test2();
}
//使用 @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

public interface ScopeService {

	void test();
}

错误示例

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

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

}

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

ProtoServiceImpl@390e6145
ProtoServiceImpl@390e6145

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

方式一 使用 @Lookup

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

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

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

}

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

ProtoServiceImpl@73dae6cf
ProtoServiceImpl@7102c834

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

方式二 使用 ObjectProvider

@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()方法,打印出了不同的对象

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)。

//使用 @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());
	}
}
@Service
public class ScopeServiceImpl implements ScopeService {
	//如果bean不存在,启动项目时会报错
	@Autowired
	private ProtoService protoService;

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

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

ProtoServiceImpl@4f36c34a
ProtoServiceImpl@7a7df4a1

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

@Lookup 的局限性

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

@Configuration
public class BeanConfig {

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

使用 @Lookup 注入

public class ScopeServiceImpl implements ScopeService {
	
	@Lookup
	ProtoService getProtoService() {
		return null;
	}

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

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

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

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

使用 ObjectProvider 注入

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()方法,打印出了不同的对象

ProtoServiceImpl@59d1047b
ProtoServiceImpl@4b0993d9

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

启用 ScopedProxyMode

@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()方法,打印出了不同的对象

ProtoServiceImpl@4f36c34a
ProtoServiceImpl@7a7df4a1

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

结语

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值