如何在单例 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 功能相同)