I've checked in the test project here: https://github.com/loesak/spring-aop-injection-bug
Given the following pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.loesoft</groupId>
<artifactId>spring-aop-injection-bug</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<spring.framework.bom.version>4.2.0.RELEASE</spring.framework.bom.version>
<spring.retry.version>1.1.2.RELEASE</spring.retry.version>
<aspectj.aspectjweaver>1.8.7</aspectj.aspectjweaver>
<java.version>1.7</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring.framework.bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>${spring.retry.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.aspectjweaver}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
</plugins>
</build>
</project>
and Spring configuration:
package com.loesoft.spring.aop.injection.bug;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Retryable;
@Configuration
@EnableRetry
public class Proof {
public static void main(String... args) {
final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Proof.class);
context.registerShutdownHook();
}
@Bean
public BeanThing beanThing() {
return new BeanThing();
}
@Bean
@Autowired
public BeanNeedy beanNeedy(BeanThing beanThing) {
return new BeanNeedy(beanThing);
}
public static interface BeanInterface {
public void doSomething();
}
public static class BeanThing implements BeanInterface {
@Retryable
public void doSomething() {
System.out.println("BeanNeedingDependencies doing something");
}
}
public static class BeanNeedy {
private final BeanThing beanThing;
public BeanNeedy(BeanThing beanThing) {
this.beanThing = beanThing;
}
}
}
The following error is thrown by Spring:
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'beanNeedy' defined in com.loesoft.spring.aop.injection.bug.Proof: Unsatisfied dependency expressed through constructor argument with index 0 of type [com.loesoft.spring.aop.injection.bug.Proof$BeanThing]: : No qualifying bean of type [com.loesoft.spring.aop.injection.bug.Proof$BeanThing] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.loesoft.spring.aop.injection.bug.Proof$BeanThing] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:464)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1123)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1018)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:305)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:301)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:196)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:834)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:537)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
at com.loesoft.spring.aop.injection.bug.Proof.main(Proof.java:15)
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.loesoft.spring.aop.injection.bug.Proof$BeanThing] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1326)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1072)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:967)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
... 14 more
I've tracked this down to the fact that the bean "beanThing" ends up being a JdkDynamicAopProxy because it has interfaces and when trying to find a matching bean as an autowired candidate when creating bean "beanNeedy", the code ends up in ResolvableType#isInstance which does not check the underlying type for the JDK proxy type and does not find the created bean "beanThing" of type "BeanThing".
I can get around this in two ways. The first by setting the @EnableRetry annotation field "proxyTargetClass" to "true" or by removing the interface on the class BeanThing.
Is this a bug or am I missing some information about Spring AOP that I am not aware of? In general, this doesn't seem right to me. I feel Spring should be able to determine the underlying type of the Proxy bean unless there is some technical reason as to why it cannot. And if there is a technical reason as to why the underlying type of the JDK proxy could not be determined, then maybe Spring should have some additional checks to help the developer figure out what is going on.
Keep in mind that this is not related to Spring Retry (thus not tagged) as I've been able to reproduce this problem with other annotations that require the underlying bean to be wrapped with an AOP proxy.
