Search code examples
javaspringaspectj

Why is it not possible to have a generic bean when i have also an Aspect targeting the bean in Spring?


Hi i have a rather specific question regarding Spring Aspects that leaves me puzzled. I played around with Springs and Apsects and and tried out a very simple Example to see how it works:

@Component
public class Comment {
    private String text;
    private String author;

    public String get() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}
@Aspect
public class LoggingAspect {
    @Around("execution(* aop.beans.*.*(..))")
    public void log(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Start Aspect for method: " + joinPoint.getSignature());
        joinPoint.proceed();
    }
}

My configuration file:

@Configuration
@ComponentScan("aop.beans")
@EnableAspectJAutoProxy
public class ProjectConfig {
    @Bean
    public LoggingAspect aspect() {
        return new LoggingAspect();
    }
}

With that i used a simple Main method to play around with the concept a little:

public class Main {
    public static void main(String[] args) {
        @SuppressWarnings("resource")
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProjectConfig.class);
        context.registerShutdownHook();

        CommentService service = context.getBean(CommentService.class);

        Comment comment = context.getBean(Comment.class);
        comment.setAuthor("Andreas");
        comment.setText("Hallo.");

        service.publishComment(comment);
        System.out.println(service.getClass());
    }
}

This worked fine but something strange happens when ich change the hirarchy of the comment class. I wanted to see what happens if the class implements a generic interface so i changed it as follows

public class Comment implements Supplier<String>

I immediately get an error with the following stack trace:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'aop.beans.Comment' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
    at aop.Main.main(Main.java:17)

This left me wondering why that is? If i remove the generic superinterface or the aspect bean everthing works fine but both things together do not seem to fare well. Can someone provide an explanation? Is Spring not able to create a Proxy object if the class has a generic superclass?


Solution

  • This happens because of the way Spring implements AOP.

    When proxied class (Comment in your case) doesn't implement any interfaces, CGLib proxy is used. Basically, it generates a class which is subclass of proxied class. Thus, you can getBean by parent's class.

    When there are implemented interfaces, Spring uses JDK dynamic proxies which don't extend the proxy class, but implement all its interfaces, thus you can't find a bean by it's class.

    That's why it's always a good practice to autowire your beans by interface rather then by class.

    Solution

    • You can force Spring to use CGLib proxies for AspectJ by annotating you configuration class @EnableAspectJAutoProxy(proxyTargetClass = true).
    • If you want to use JDK Dynamic proxies, create interface for Comment and then use context.getBean(YourInterfaceName.class).