Here I have 3 Interfaces: InterfaceA
and InterfaceB
and SharedInterface
public interface InterfaceA {
/**
* print some message
*/
void printMsg();
}
public interface InterfaceB {
/**
* print some message
*/
void printMsg();
}
public interface SharedInterface {
/**
* print some message
*/
void printSharedMsg();
}
and there are 3 implementations of these interfaces:
public class ImplementA1 implements InterfaceA, SharedInterface {
@Override
public void printMsg() {
System.out.println("this is message of interfaceA1");
}
@Override
public void printSharedMsg() {
System.out.println("this is shared message from ImplementA1");
}
}
public class ImplementA2 implements InterfaceA, SharedInterface {
@Override
public void printMsg() {
System.out.println("this is message of interfaceA2");
}
@Override
public void printSharedMsg() {
System.out.println("this is shared message from ImplementA2");
}
}
public class ImplementB implements InterfaceB, SharedInterface {
@Override
public void printMsg() {
System.out.println("this is message of interfaceB");
}
@Override
public void printSharedMsg() {
System.out.println("this is shared message from ImplementB");
}
}
ImplementA1
and ImplementA2
are the same type of operation, ImplementB
is another type of operation. So I decided to develop 2 config class to register ImplementA1,ImplementA2 and ImplementB which is showing below.
@Configuration
public class InterfaceAConfig {
@Bean
public InterfaceA registerInterfaceA1(){
return new ImplementA1();
}
@Bean
public InterfaceA registerInterfaceA2(){
return new ImplementA2();
}
}
@Configuration
public class InterfaceBConfig {
@Bean
public InterfaceB registerInterfaceB(){
return new ImplementB();
}
}
Now I want to let all beans which implement SharedInterface
print their message in a component. And it works well,here is the code:
@Component
@AutoConfigureAfter(value = {
InterfaceAConfig.class,
InterfaceBConfig.class})
public class SharedInterfaceComponent implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
//print shared message after IOC container refreshed
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
usingContextGetBean();
}
private void usingContextGetBean() {
Map<String, SharedInterface> beans = this.applicationContext.getBeansOfType(SharedInterface.class);
System.out.println(beans.size());
for (SharedInterface bean : beans.values()) {
bean.printSharedMsg();
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
But I found another way of inject beans to component, using
@Autowired
List<TargetType> myListName
So I decided to change my SharedInterfaceComponent
to this for test, and it worked:
@Component
@AutoConfigureAfter(value = {
InterfaceAConfig.class,
InterfaceBConfig.class})
public class SharedInterfaceComponent implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
//todo why do spring failed due to this autowire?
@Autowired
private List<InterfaceA> autowiredList;
//print shared message after IOC container refreshed
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
usingAutowiredGerBean();
//usingContextGetBean();
}
private void usingAutowiredGerBean() {
for (InterfaceA interfaceA : autowiredList) {
if (SharedInterface.class.isAssignableFrom(interfaceA.getClass())){
((SharedInterface) interfaceA).printSharedMsg();
}
}
}
private void usingContextGetBean() {
Map<String, SharedInterface> beans = this.applicationContext.getBeansOfType(SharedInterface.class);
System.out.println(beans.size());
for (SharedInterface bean : beans.values()) {
bean.printSharedMsg();
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
But when I tried to use SharedInterface
instead of InerfaceA
to get beans from IOC , it goes wrong. The code is showing below:
@Component
@AutoConfigureAfter(value = {
InterfaceAConfig.class,
InterfaceBConfig.class})
public class SharedInterfaceComponent implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {
private ApplicationContext applicationContext;
//todo why do spring failed due to this autowire?
@Autowired
private List<SharedInterface> autowiredList;
//print shared message after IOC container refreshed
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
usingAutowiredGerBean();
//usingContextGetBean();
}
private void usingAutowiredGerBean() {
for (SharedInterface sharedInterface : autowiredList) {
if (SharedInterface.class.isAssignableFrom(sharedInterface.getClass())){
((SharedInterface) sharedInterface).printSharedMsg();
}
}
}
private void usingContextGetBean() {
Map<String, SharedInterface> beans = this.applicationContext.getBeansOfType(SharedInterface.class);
System.out.println(beans.size());
for (SharedInterface bean : beans.values()) {
bean.printSharedMsg();
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
In this Demo the application will fail and showing
***************************
APPLICATION FAILED TO START
***************************
Description:
Field autowiredList in com.wwstation.test.config.SharedInterfaceComponent required a bean of type 'java.util.List' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'java.util.List' in your configuration.
But in my other projects, the same situation will not lead to a crush, I can get SharedInterface
by using @Autowired
but there I can only get beans implement InterfaceA
or InterfaceB
but never all of them. I thought, the not crushing case may be caused by some of my dependencies in other projects.
Can anyone help me about how to get all the SharedInterface
more graceful? Thanks alot!
CodePudding user response:
The problem is your configuration.
@Bean
public InterfaceA registerInterfaceA1(){
return new ImplementA1();
}
The problem with this is that Spring will use the return type of the method to see if it fullfils injection points (in this case your list). As InterfaceA
isn't a SharedInterface
eventually it will fail as there are no beans that implement the SharedInterface
according to your configuration!.
What you should do with your own beans is to be as specific as possible in the return type. So instead of InterfaceA
make it return the actual class ImplementA1
and ImplementA2
. That way Spring, at configuration time, can determine that those implement SharedInterface
and use those to fill the list.
@Bean
public ImplementA1 registerInterfaceA1(){
return new ImplementA1();
}