I want to conditionally enable aspect like this:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
TestService testService = ctx.getBean(TestService.class);
testService.test();
}
@Bean
TestService testService() {
return new TestService();
}
@Bean
TestAspect testAspect() {
boolean enabled = false; // TODO
return enabled ? new TestAspect() : null;
}
@Aspect
public static class TestAspect {
@Around("execution(public * test(..))")
public Object test(ProceedingJoinPoint pjp) throws Throwable {
return pjp.proceed();
}
}
public static class TestService {
public void test() {
}
}
}
It will raise exception when proxied method invoked:
Exception in thread "main" org.springframework.aop.AopInvocationException: Mismatch on arguments to advice method [public java.lang.Object com.example.demo.DemoApplication$TestAspect.test(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable]; pointcut expression [org.aspectj.weaver.internal.tools.PointcutExpressionImpl@51e1e058]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:647)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:632)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728)
at com.example.demo.DemoApplication$TestService$$SpringCGLIB$$0.test(<generated>)
at com.example.demo.DemoApplication.main(DemoApplication.java:18)
Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.checkReceiver(DirectMethodHandleAccessor.java:197)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:99)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:642)
... 8 more
If the proposal is accepted, please note that application context failed to initialize if the aspect implements Ordered
:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testService' defined in com.example.demo.DemoApplication: class org.springframework.beans.factory.support.NullBean cannot be cast to class org.springframework.core.Ordered (org.springframework.beans.factory.support.NullBean and org.springframework.core.Ordered are in unnamed module of loader 'app')
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:614) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1222) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1188) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1123) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) ~[spring-context-6.2.7.jar:6.2.7]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) ~[spring-context-6.2.7.jar:6.2.7]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:753) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1362) ~[spring-boot-3.5.0.jar:3.5.0]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1351) ~[spring-boot-3.5.0.jar:3.5.0]
at com.example.demo.DemoApplication.main(DemoApplication.java:16) ~[main/:na]
Caused by: java.lang.ClassCastException: class org.springframework.beans.factory.support.NullBean cannot be cast to class org.springframework.core.Ordered (org.springframework.beans.factory.support.NullBean and org.springframework.core.Ordered are in unnamed module of loader 'app')
at org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory.getOrder(BeanFactoryAspectInstanceFactory.java:133) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.aop.aspectj.annotation.LazySingletonAspectInstanceFactoryDecorator.getOrder(LazySingletonAspectInstanceFactoryDecorator.java:95) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl.getOrder(InstantiationModelAwarePointcutAdvisorImpl.java:184) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.core.OrderComparator.findOrder(OrderComparator.java:145) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.core.annotation.AnnotationAwareOrderComparator.findOrder(AnnotationAwareOrderComparator.java:64) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.core.OrderComparator.getOrder(OrderComparator.java:128) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.core.OrderComparator.getOrder(OrderComparator.java:116) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.core.OrderComparator.doCompare(OrderComparator.java:86) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.core.OrderComparator.compare(OrderComparator.java:73) ~[spring-core-6.2.7.jar:6.2.7]
at org.springframework.aop.aspectj.autoproxy.AspectJPrecedenceComparator.compare(AspectJPrecedenceComparator.java:83) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.aop.aspectj.autoproxy.AspectJPrecedenceComparator.compare(AspectJPrecedenceComparator.java:51) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder.compareTo(AspectJAwareAdvisorAutoProxyCreator.java:143) ~[spring-aop-6.2.7.jar:6.2.7]
at org.aspectj.util.PartialOrder$SortObject.addDirectedLinks(PartialOrder.java:71) ~[aspectjweaver-1.9.24.jar:na]
at org.aspectj.util.PartialOrder.addNewPartialComparable(PartialOrder.java:92) ~[aspectjweaver-1.9.24.jar:na]
at org.aspectj.util.PartialOrder.sort(PartialOrder.java:128) ~[aspectjweaver-1.9.24.jar:na]
at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.sortAdvisors(AspectJAwareAdvisorAutoProxyCreator.java:79) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:103) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:80) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:367) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:319) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:445) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1829) ~[spring-beans-6.2.7.jar:6.2.7]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607) ~[spring-beans-6.2.7.jar:6.2.7]
... 17 common frames omitted
Comment From: sbrannen
I want to conditionally enable aspect like this:
Please note that returning null
from an @Bean
factory method is highly discouraged, even if it is tolerated.
Thus, why not use @Conditional
(or one of the @Conditional
variants from Spring Boot)?
Comment From: quaff
I want to conditionally enable aspect like this:
Please note that returning
null
from an@Bean
factory method is highly discouraged, even if it is tolerated.Thus, why not use
@Conditional
(or one of the@Conditional
variants from Spring Boot)?
This way is more straight forward than @Conditional
, I'm OK if the proposal is rejected.
Comment From: sbrannen
If you're not interested in using @Conditional
support, have you considered using the programmatic bean registration feature coming in 7.0?
That would allow you to programmatically decide whether or not you want to register the TestAspect
.
Comment From: quaff
If you're not interested in using
@Conditional
support, have you considered using the programmatic bean registration feature coming in 7.0?That would allow you to programmatically decide whether or not you want to register the
TestAspect
.
I like @Conditional
, and I'll try BeanRegistrar
later, maybe we should repurpose this issue to produce meaningful error if @Aspect
bean is null?
Comment From: sbrannen
maybe we should repurpose this issue to produce meaningful error if
@Aspect
bean is null?
This actually all gets triggered because AbstractAutowireCapableBeanFactory.initializeBean(...)
attempts to initialize a NullBean
.
If we were to avoid initialization of a NullBean
, AbstractAutoProxyCreator.postProcessAfterInitialization(...)
would not be invoked to begin with.
Note, however, that AbstractAutowireCapableBeanFactory.invokeInitMethods(...)
in fact has a bean.getClass() != NullBean.class
check.
So, I'm wondering if we shouldn't add a bean.getClass() != NullBean.class
check to AbstractAutowireCapableBeanFactory.initializeBean(...)
.
@jhoeller, thoughts?
Comment From: quaff
maybe we should repurpose this issue to produce meaningful error if
@Aspect
bean is null?This actually all gets triggered because
AbstractAutowireCapableBeanFactory.initializeBean(...)
attempts to initialize aNullBean
.If we were to avoid initialization of a
NullBean
,AbstractAutoProxyCreator.postProcessAfterInitialization(...)
would not be invoked to begin with.Note, however, that
AbstractAutowireCapableBeanFactory.invokeInitMethods(...)
in fact has abean.getClass() != NullBean.class
check.So, I'm wondering if we shouldn't add a
bean.getClass() != NullBean.class
check toAbstractAutowireCapableBeanFactory.initializeBean(...)
.@jhoeller, thoughts?
@sbrannen Not sure I'm following, there are two different issues here, object is not an instance of declaring class
and casting NullBean
to Ordered
if aspect is ordered, from my investigation, your suggestion doesn't tackle either one.
Comment From: sbrannen
Not sure I'm following, there are two different issues here,
object is not an instance of declaring class
and castingNullBean
toOrdered
if aspect is ordered, from my investigation, your suggestion doesn't tackle either one.
Indeed, you're correct, @quaff.
I took "too quick" a glance and barked up the wrong tree, so to speak.
The issue seems to be that the @Aspect
metadata is prepared before the @Aspect
bean is instantiated. Thus, the Aspect metadata for your TestAspect
is registered eagerly even though the instance is later an instance of NullBean
instead of TestAspect
.
BeanFactoryAspectInstanceFactory.getAspectInstance()
is responsible for looking up the actual instance at runtime, when invoking an advice method on the aspect (in AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs()
).
Thus, one option would be to throw a dedicated exception from BeanFactoryAspectInstanceFactory.getAspectInstance()
if the actual bean type is NullBean
.
I'll discuss our options with Juergen once he's back from vacation.
Comment From: quaff
Thus, one option would be to throw a dedicated exception from
BeanFactoryAspectInstanceFactory.getAspectInstance()
if the actual bean type isNullBean
.
This option doesn't tackle ClassCastException
, I created https://github.com/spring-projects/spring-framework/pull/35097 to improve it.
https://github.com/spring-projects/spring-framework/blob/12146c4a1b90d2c173c907fd9f898672b63bd338/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectInstanceFactory.java#L132
Comment From: jhoeller
It turns out to be straightforward enough to skip a null bean as aspect instance in AbstractAspectJAdvice
where we call getAspectInstance()
. In combination with a refined variant of getOrder()
, this leniently tolerates a null advice bean now.