Home > database >  How to get all beans of specified interface which also implements other interfaces
How to get all beans of specified interface which also implements other interfaces

Time:05-24

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();
}
  • Related