Timing Issue with Spring Boot Annotation Configuration

209 views Asked by At

We're having a curious issue with the timing of beans, repositories, and controllers in our spring boot application.

We have a NodeRepository that is backed by a Map. This Map object should be a Map that we create using an @Bean annotation, but it seems as though Spring is creating its own Map and injecting it on our NodeRepository. This is a problem because the keys in the Map are wrong due to Spring make assumptions of our key values.

Here is all the needed code:

org.xxx.Application

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {

        return application.sources(Application.class);
    }

    @Bean
    public Map<String, RemoteNode> getRemoteNodeMap() {

        HashMap<String, RemoteNode> remoteNodeMap = new HashMap<>();
        remoteNodeMap.put("NODE1", this.getRemoteNode1());
        remoteNodeMap.put("NODE2", this.getRemoteNode2());

        return remoteNodeMap;
    }

    @Bean(name = "remoteNode1")
    public RemoteNode getRemoteNode1() {

        return new DefaultRemoteNode("NODE1");
    }

    @Bean(name = "remoteNode2")
    public RemoteNode getRemoteNode2() {

        return new DefaultRemoteNode("NODE2");
    }
}

org.xxx.repository.RemoteNodeRepository

@Repository
public class RemoteNodeRepositoryImpl implements RemoteNodeRepository {

    private Map<String, RemoteNode> remoteNodeMap;

    @Autowired
    public RemoteNodeRepository(Map<String, RemoteNode> remoteNodeMap) {
        this.remoteNodeMap = remoteNodeMap;
    }
}

What I expect to be injected into the RemoteNodeRepsoitory is a Map that looks like the following:

"NODE1",RemoteNode("NODE1")

"NODE2",RemoteNode("NODE2")

HOWEVER, this is the Map that is being injected into the RemoteNodeRepository:

"remoteNode1",RemoteNode("NODE1")

"remoteNode2",RemoteNode("NODE2")

NOTE, the names of the keys are based on the name of the @Bean annotations in the Application class. If we remove the 'name' qualifier of the @Bean the keys are named after the methods.

If we add the @Qualifier annotation to the Map attribute of our RemoteNodeRepsoitory we get the following exception:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.chronopolis.remote.node.RemoteNode] found for dependency [map with value type org.chronopolis.remote.node.RemoteNode]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier(value=remoteNodeMap)}

Finally, when setting break points in the debugger we can clearly see that the RemoteNodeRepository is being instantiated prior to the getRemoteNodeMap() method call in our Application class.

What can we do to fix this timing issue? I'm very confused.

1

There are 1 answers

3
sodik On BEST ANSWER

I don't think it is timing issue. You are "in conflict" with this spring feature (from http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/)

Even typed Maps can be autowired as long as the expected key type is String. The Map values will contain all beans of the expected type, and the keys will contain the corresponding bean names:

@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
    this.movieCatalogs = movieCatalogs;
}

To be honest, I am not sure if you can "override" this easily, however simple workaround should work - just use some wrapper class to make your type special - something like (pseudo code):

class NodeMap {
   Map<String, RemoteNode> map;
}

and then used it in your @Bean definition and when autowiring. Or just don't create the map and use the spring feature (and maybe rename your node beans).