0

I'm using the latest version of Spring and I'm getting startup errors when I attempt to inject the same generic type twice and the generic type's implementation uses caching.

Below is the simplest example I can create to duplicate the error.

// build.gradle dependencies
dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    compile 'org.springframework.boot:spring-boot-starter-web'
}
// MyApplication.java
@SpringBootApplication
@EnableCaching
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager();
    }
}
// HomeController.java
@RestController
@RequestMapping(value = "/home")
public class HomeController {

    @Autowired
    public HomeController(
        GenericService<String> s1,
        GenericService<String> s2, // <-- Notice GenericService<String> twice
        GenericService<Integer> s3
    ) {}
}
// GenericService.java
public interface GenericService<T> {
    public T aMethod();
}
// IntegerService.java
@Service
public class IntegerService implements GenericService<Integer> {

    @Override
    @Cacheable("IntegerMethod")
    public Integer aMethod() {
        return null;
    }
}
// StringService.java
@Service
public class StringService implements GenericService<String> {

    @Override
    @Cacheable("StringMethod")
    public String aMethod() {
        return null;
    }
}

This compiles fine, but when I run the application, I get the following error:

No qualifying bean of type [demo.GenericService] is defined: expected single matching bean but found 2: integerService,stringService

I have not tried using qualifiers yet, but I'm guessing that would be a work-around. I will try it after posting this. Ideally, I'd like the autowiring of generics and caching to integrate out-of-box. Am I doing something wrong, or is there anything I can do to get it working?

Thank you!

Andrew Ferk
  • 3,658
  • 3
  • 24
  • 25
  • 1
    I am able to workaround this issue by using @Qualifier on my services and the constructor params of the controller. – Andrew Ferk May 28 '15 at 21:04

1 Answers1

0

If you would like to not have to use the @Qualifier in the constructor and still use the interfaces, you could just add a value to the service declarations.

@Service(value = "integerService")
public class IntegerService implements GenericService<Integer> {

    @Override
    @Cacheable("IntegerMethod")
    public Integer aMethod() {
        return 42;
    }
}

@Service(value = "stringService")
public class StringService implements GenericService<String> {

    @Override
    @Cacheable("StringMethod")
    public Integer aMethod() {
        return 42;
    }
}

Just to be sure, I created a project with Spring-Boot, compiled and ran it. So the above should work. It's basically the same as what you're already doing, but with less typing.

My previous answer (before modifying) was to do something like this:

// HomeController.java
@RestController
@RequestMapping(value = "/home")
public class HomeController {

    @Autowired
    public HomeController(
        StringService s1,
        StringService s2, 
        IntegerService s3
    ) {}
}

But you would have to not implement the interfaces to make this work.

Pytry
  • 6,044
  • 2
  • 37
  • 56
  • Which would still not work without adding the @Qualifier since there are two different instances of StringService. – daniel.eichten May 29 '15 at 12:24
  • I'd rather couple my controller to interfaces and not an implementation, which is why I'm using generics. Spring's IoC allows generic types as an [implicit form of qualification](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-generics-as-qualifiers). You can [read more](https://spring.io/blog/2013/12/03/spring-framework-4-0-and-java-generics). If I were to remove the `@EnableCaching` annotation from MyApplication, everything works (but I no longer have caching). – Andrew Ferk May 29 '15 at 13:14
  • Either way, your example does not work, because Spring 4 IoC registers StringService and IntegerService to GenericService and GenericService, respectively. So, the IoC service locator cannot find registrations for StringService or IntegerService. – Andrew Ferk May 29 '15 at 13:18
  • You would need to not extend the interface to get my example to work, since spring-boot is creating a wrapper which implements the interface instead of extending the concrete class. Since you don't want to do that, I'll modify my answer. – Pytry May 29 '15 at 22:28
  • If you're interested in compiling this, I uploaded my changes to GitHub: https://github.com/Pytry/generic-cached-example.git – Pytry May 29 '15 at 22:44