Why Your Custom Spring AOP Advisor Isn’t Triggered and How to Fix It
This article explains the behavior of Spring Boot’s @Configuration and @Import annotations, the reasons a custom PointcutAdvisor may not be applied when using JDK proxies, and provides two solutions—adding the @DS annotation to interface methods or forcing CGLIB proxying with proxyTargetClass=true—to ensure the advice executes correctly.
Environment: Spring Boot 2.3.10.
1. @Configuration annotation
The annotation has a
proxyBeanMethodsattribute defaulting to
true. When true, all
@Bean-annotated methods are CGLIB‑proxied, allowing direct calls within the class or external classes to return the same shared bean instance.
2. @Import annotation
Using
@Importregisters the specified class, e.g.
@Import({RWImportSelector.class}). The selector can implement
ImportSelectorand
EnvironmentAwareto conditionally import configuration based on properties.
<code>public class RWImportSelector implements ImportSelector, EnvironmentAware {
private static final String RW_CONFIG_ENABLED = "rw.config.enabled";
private static final Boolean RW_CONFIG_ENABLED_DEFAULE = Boolean.TRUE;
private Environment env;
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// ... logic omitted for brevity
}
@Override
public void setEnvironment(Environment environment) {
this.env = environment;
}
}
</code>ImportSelector can implement corresponding *Aware interfaces.
The classes returned by
selectImportsalso respect
@ConditionalOn*annotations such as
@ConditionalOnProperty.
3. Custom Advisor not taking effect
The requirement is to intercept every method annotated with
@DS. A custom
PointcutAdvisoris defined, but the advice is never invoked because Spring creates a JDK dynamic proxy for beans that implement an interface. The proxy’s
Methodobject points to the interface method, which lacks the
@DSannotation, so the pointcut does not match.
<code>@Component
public class CustomAdvisor implements PointcutAdvisor {
@Override
public Advice getAdvice() {
return new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("I am called...");
return invocation.proceed();
}
};
}
@Override
public boolean isPerInstance() { return true; }
@Override
public Pointcut getPointcut() {
return new Pointcut() {
@Override
public MethodMatcher getMethodMatcher() {
return new MethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
return method.isAnnotationPresent(DS.class);
}
@Override
public boolean isRuntime() { return false; }
};
}
@Override
public ClassFilter getClassFilter() { return ClassFilter.TRUE; }
};
}
}
</code>When the bean implements an interface, Spring uses
JdkDynamicAopProxy. The core line that retrieves the interceptor chain is:
<code>List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
</code>If the chain is empty, the original method is invoked directly, which is why only the
System.out.printlninside
update()appears.
4. How to solve
Two approaches:
Add
@DSon the interface method.
Force CGLIB proxying by enabling
proxyTargetClass=truein
@EnableAspectJAutoProxy:
<code>@EnableAspectJAutoProxy(proxyTargetClass = true)
</code>The
proxyTargetClassflag is processed in
AspectJAutoProxyRegistrar, which registers
AnnotationAwareAspectJAutoProxyCreatorand, if the flag is true, forces the auto‑proxy creator to use class‑based (CGLIB) proxies.
5. Introduction advice example
Introduction advice allows a class that does not implement an interface to gain that interface’s behavior without modifying its source.
<code>@Component
public class Apple {
public void color() { System.out.println("red color..."); }
}
public interface FruitDAO { void eat(); }
public class CustomIntroductionInterceptor implements IntroductionInterceptor, FruitDAO {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (implementsInterface(invocation.getMethod().getDeclaringClass())) {
System.out.println("I am Introduction enhancement...");
return invocation.getMethod().invoke(this, invocation.getArguments());
}
return invocation.proceed();
}
@Override
public boolean implementsInterface(Class<?> intf) {
return FruitDAO.class.isAssignableFrom(intf);
}
@Override
public void eat() { System.out.println("This is a qualified fruit"); }
}
public class IntroductionAopProxy extends AbstractAutoProxyCreator {
@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) {
return new Object[] { new DefaultIntroductionAdvisor(new CustomIntroductionInterceptor(), FruitDAO.class) };
}
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
return !Apple.class.isAssignableFrom(beanClass);
}
}
</code>The proxy only applies to
Apple, granting it the
FruitDAOcapability.
… (article continues)
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.