英文:
Spring dependency injection infers unexpectly the right bean
问题
拥有这些接口/类:
interface BankService {}
interface TransferService {}
class BankTransferServiceImpl implements BankService, TransferService {}
class BankingClient {
public BankingClient(BankService bs) {}
}
如预期,此主方法将无法编译,因为BankingClient构造函数需要一个BankService类型,而不是TransferService类型:
public static void main(String[] args) {
TransferService ts = new BankTransferServiceImpl();
BankingClient bs = new BankingClient(ts);
}
然后我们有这个Spring配置文件:
@Configuration
public class MyConfig {
@Bean
TransferService transferService() {
return new BankTransferServiceImpl();
}
@Bean
BankingClient bankingService(BankService bs) {
return new BankingClient(bs);
}
}
类似地,我预期在bankingService方法中会出现错误,因为我们已经有的唯一bean是TransferService,而TransferService不扩展BankService。但是我的JUnit测试运行并成功实例化了bankingService。我漏掉了什么?
英文:
Having this interfaces/classes:
interface BankService {}
interface TransferService {}
class BankTransferServiceImpl implements BankService, TransferService {}
class BankingClient {
public BankingClient(BankService bs) {}
}
As expected this main method will not compiled, because BankingClient constructor expects a BankService, not a TransferService type:
public static void main(String[] args) {
TransferService ts = new BankTransferServiceImpl();
BankingClient bs = new BankingClient(ts);
}
Then we have this Spring configuration file:
@Configuration
public class MyConfig {
@Bean
TransferService transferService() {
return new BankTransferServiceImpl();
}
@Bean
BankingClient bankingService(BankService bs) {
return new BankingClient(bs);
}
}
Similarly, I was expecting to get an error in bankingService method, because the only bean we have already is the TransferService, and TransferService does not extends BankService. But my junit runs and instantiate bankingService successfully. What am I missing?
答案1
得分: 1
public static void main(String[] args) {
List<Object> objects = new ArrayList<>();
Map<Class, Supplier> suppliers = new HashMap<>();
suppliers.put(TransferService.class, BankTransferServiceImpl::new);
TransferService ts = new BankTransferServiceImpl();
objects.add(ts);
BankService bs = objects.stream()
.filter(BankService.class::isInstance)
.map(BankService.class::cast)
.reduce((a, b) -> {
throw new IllegalStateException("Multiple beans found");
})
.orElseGet(() -> {
Supplier<?> supplier = suppliers.get(BankService.class);
if (supplier == null) {
throw new IllegalStateException("No bean supplier found");
}
Object result = supplier.get();
objects.add(result);
return (BankService) result;
});
BankingClient client = new BankingClient(bs);
}
在您的情况下,bean初始化顺序实际上是有意义的:
- 首先,
Spring
创建了TransferService
bean - 随后,
Spring
"知道"TransferService
bean 也实现了BankService
接口,因此它可以构造BankingClient
实例。
然而,如果您更改 MyConfig
配置类中的bean定义顺序:
@Configuration
public class MyConfig {
@Bean
BankingClient bankingService(BankService bs) {
return new BankingClient(bs);
}
@Bean
TransferService transferService() {
return new BankTransferServiceImpl();
}
}
您可能会得到另一种行为:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'BankService' available:
expected at least 1 bean which qualifies as autowire candidate.
这是因为 Spring
知道如何创建 TransferService
bean,但不知道如何创建 BankService
bean,而 TransferService
bean 还没有被创建。
从工厂方法(使用 @Bean
注解的方法)切换到 @ComponentScan
/@Component
也会改变 Spring
的行为:
- 对于工厂方法,
Spring
最初从工厂方法的返回类型中推断出bean类型,因此它可能没有足够的信息来确定实际的bean类型。 - 对于
@ComponentScan
/@Component
,由于Spring
确实看到了实际的bean类型,因此它有关于要创建的bean的更多信息。
您还可以查看 AbstractBeanFactory#isTypeMatch 方法。
英文:
> java
> public static void main(String[] args) {
> TransferService ts = new BankTransferServiceImpl();
> BankingClient bs = new BankingClient(ts);
>}
>
Spring creates beans in a different way:
- it stores information about beans have been created
- it stores information about how to create particular bean
basically, that could be represented by the following oversimplified code:
public static void main(String[] args) {
List<Object> objects = new ArrayList<>();
Map<Class, Supplier> suppliers = new HashMap<>();
suppliers.put(TransferService.class, BankTransferServiceImpl::new);
TransferService ts = new BankTransferServiceImpl();
objects.add(ts);
BankService bs = objects.stream()
.filter(BankService.class::isInstance)
.map(BankService.class::cast)
.reduce((a, b) -> {
throw new IllegalStateException("Multiple beans found");
})
.orElseGet(() -> {
Supplier<?> supplier = suppliers.get(BankService.class);
if (supplier == null) {
throw new IllegalStateException("No bean supplier found");
}
Object result = supplier.get();
objects.add(result);
return (BankService) result;
});
BankingClient client = new BankingClient(bs);
}
In your case bean initialisation order actually makes sense:
- at first
spring
createsTransferService
bean - after that
spring
"does know" thatTransferService
bean also implementsBankService
interface, and thus it can constructBankingClient
instance
However, if you change definition order of beans in MyConfig
configuration class:
@Configuration
public class MyConfig {
@Bean
BankingClient bankingService(BankService bs) {
return new BankingClient(bs);
}
@Bean
TransferService transferService() {
return new BankTransferServiceImpl();
}
}
you may get another behaviour:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'BankService' available:
expected at least 1 bean which qualifies as autowire candidate.
just because spring
does know how to create TransferService
bean, but has no idea about how to create BankService
bean, and TransferService
bean has not been created yet.
Switching from factory methods (methods annotated by @Bean
) to @ComponentScan
/@Component
changes behaviour of spring
as well:
- in case of factory methods,
spring
initially infers bean type from factory method return type, and thus it may not have enough information about actual bean type - in case of
@ComponentScan
/@Component
, sincespring
does see the actual bean type it has more information about beans to be created.
You may also check AbstractBeanFactory#isTypeMatch method.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论