PROBLEM HAS BEEN IDENTIFIED, POST UPDATED (Scroll to bottom)
I am developing a desktop application currently using Spring (spring-context
, 4.1.6.RELEASE
) for IoC and dependency injection. I am using an annotation configuration, using @ComponentScan
. The issue I am experiencing is supposed to be implemented as a feature in 4.X.X
, as it states here and here, but I am getting the old 3.X.X
exception.
I have a parameterised interface that represents a generic repository:
public interface DomainRepository<T> {
T add(T entity) throws ServiceException, IllegalArgumentException;
// ...etc
}
I then have two concrete implementations of this, ChunkRepositoryImpl
and ProjectRepositoryImpl
, which are parameterised accordingly. They share some common implementation from an abstract class, but are declared like so:
@Repository
public class ChunkRepositoryImpl extends AbstractRepositoryImpl<Chunk> implements DomainRepository<Chunk> {
// ...+ various method implementations
}
@Repository
public class ProjectRepositoryImpl extends AbstractRepositoryImpl<Project> implements DomainRepository<Project> {
// ...+ various method implementations
}
My understanding of the above links leads me to believe that I should be able to autowire these without needing to manually specify the beans via @Qualifier
. However, when I do so:
@Autowired
private DomainRepository<Project> repository;
I get the following exception (preceded by a long stack trace of course):
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.foo.bar.repositories.DomainRepository] is defined: expected single matching bean but found 2: chunkRepositoryImpl,projectRepositoryImpl
Can anybody shine a light as to why this might be happening? I would expect this exception in 3.X.X
, but it should not happen in 4.X.X
. What is the difference between my situation, and the one described here?
UPDATE
I have discovered the source of the problem. One of the methods in my DomainRepository<T>
interface is marked as @Async
, and makes use of Spring's asynchronous capabilities. Removing this means that the beans are correctly qualified. I hypothesize that Spring transforms classes with @Async
methods under the hood into some other class, and this process strips the type information, meaning that it can't tell the beans apart.
This means I now have two questions:
Here is a project demonstrating the problem. Simply remove the @Async
annotation from the DomainRepository<T>
interface, and the problem dissappears.
I hypothesize that Spring transforms classes with @Async methods under the hood into some other class, and this process strips the type information, meaning that it can't tell the beans apart.
Yes. That's exactly what happens.
Spring 4 supports injecting beans by their full generic signature. Given the injection target
@Autowired
private DomainRepository<Project> repository;
and a bean of type ProjectRepositoryImpl
, Spring will properly resolve and inject that bean into the field (or method argument, or constructor argument).
However, in your code, you don't actually have a bean of type ProjectRepositoryImpl
, not even of type DomainRepository<Project>
. You actually have a bean of type java.lang.Proxy
(actually a dynamic subclass of it) that implements DomainRepository
, org.springframework.aop.SpringProxy
, and org.springframework.aop.framework.Advised
.
With @Async
, Spring needs to proxy your bean to add the asynchronous dispatching behavior. This proxy, by default, is a JDK proxy. JDK proxies can only inherit the interfaces of the target type. JDK proxies are produced with the factory method Proxy#newProxyInstance(...)
. Notice how it only accepts Class
arguments, not Type
. So it can only receive a type descriptor for DomainRepository
, not for DomainRepository<Chunk>
.
Therefore, you have no bean that implements your parameterized target type DocumentRepository<Project>
. Spring will fall back to the raw type DocumentRepository
and find two candidate beans. It's an ambiguous match so it fails.
The solution is to use CGLIB proxies with
@EnableAsync(proxyTargetClass = true)
CGLIB proxies allow Spring to get the full type information, not just interfaces. So your proxy will actually have a type that is a subtype of ProjectRepositoryImpl
, for example, which carries with it the DocumentRepository<Project>
type information.
A lot of the above are implementation details and defined in many separate places, official documentation, javadoc, comments, etc. Use them carefully.