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 proxyBeanMethods attribute 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 @Import registers the specified class, e.g. @Import({RWImportSelector.class}). The selector can implement ImportSelector and EnvironmentAware to conditionally import configuration based on properties.
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;
}
}ImportSelector can implement corresponding *Aware interfaces.
The classes returned by selectImports also respect @ConditionalOn* annotations such as @ConditionalOnProperty.
3. Custom Advisor not taking effect
The requirement is to intercept every method annotated with @DS. A custom PointcutAdvisor is defined, but the advice is never invoked because Spring creates a JDK dynamic proxy for beans that implement an interface. The proxy’s Method object points to the interface method, which lacks the @DS annotation, so the pointcut does not match.
@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; }
};
}
}When the bean implements an interface, Spring uses JdkDynamicAopProxy. The core line that retrieves the interceptor chain is:
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);If the chain is empty, the original method is invoked directly, which is why only the System.out.println inside update() appears.
4. How to solve
Two approaches:
Add @DS on the interface method.
Force CGLIB proxying by enabling proxyTargetClass=true in @EnableAspectJAutoProxy:
@EnableAspectJAutoProxy(proxyTargetClass = true)The proxyTargetClass flag is processed in AspectJAutoProxyRegistrar, which registers AnnotationAwareAspectJAutoProxyCreator and, 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.
@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);
}
}The proxy only applies to Apple, granting it the FruitDAO capability.
… (article continues)
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
