Why Your Custom AOP Annotation Fails in Spring Boot and How to Fix It
This article explains why a custom AOP annotation like @MyMonitor may not trigger advice in Spring Boot, analyzes the proxy‑based limitation of Spring AOP, and provides practical solutions such as self‑injection or AspectJ mode to ensure the advice executes correctly.
In a Spring Boot project, a developer created a custom annotation MyMonitor expecting its associated advice to run whenever a method annotated with it is invoked, similar to how @Transactional works.
The annotation is defined as:
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyMonitor {}An aspect class registers a pointcut for this annotation and a @Before advice that logs method parameters:
@Component
@Aspect
public class MyAopAdviseDefine {
private Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.xttblog.MyMonitor)")
public void pointcut() {}
// define advise
@Before("pointcut()")
public void logMethodInvokeParam(JoinPoint joinPoint) {
logger.info("---Before method {} invoke, param: {}---", joinPoint.getSignature().toShortString(), joinPoint.getArgs());
}
}The service class uses the annotation on a test() method and calls it indirectly through hello():
@Service
public class SomeService {
private Logger logger = LoggerFactory.getLogger(getClass());
public void hello(String someParam) {
logger.info("---SomeService: hello invoked, param: {}---", someParam);
test();
}
@MyMonitor
public void test() {
logger.info("---SomeService: test invoked---");
}
}The main application enables proxy creation:
@EnableAspectJAutoProxy(proxyTargetClass = true)
@SpringBootApplication
public class MyAopDemo {
@Autowired
SomeService someService;
public static void main(String[] args) {
SpringApplication.run(MyAopDemo.class, args);
}
@PostConstruct
public void aopTest() {
someService.hello("abc");
}
}When the program runs, the logMethodInvokeParam advice is not triggered for test(). This is because Spring AOP creates a proxy bean for SomeService. Calls made from within the target object itself (i.e., this.test()) bypass the proxy, so the advice is never applied.
To make the advice execute, the method must be invoked on the proxy instance. One simple fix is to inject the proxy of the same service back into itself and call the method through that reference:
@Service
public class SomeService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private SomeService self; // proxy injected
public void hello(String someParam) {
logger.info("---SomeService: hello invoked, param: {}---", someParam);
self.test(); // call via proxy
}
@MyMonitor
public void test() {
logger.info("---SomeService: test invoked---");
}
}Spring allows this self‑injection because, during bean creation, an early reference to the bean is placed in a three‑level cache, enabling circular dependencies without errors.
The same proxy‑visibility problem appears with @Transactional. For example, a UserService that calls its own addUser() method from addUsers() will not start a transaction. Two remedies are:
Switch to AspectJ mode: <tx:annotation-driven mode="aspectj"/> Inject the proxy of UserService (e.g., a self field) and invoke transactional methods through that proxy.
By understanding that Spring AOP works on proxies and that internal method calls bypass those proxies, developers can avoid many common pitfalls and ensure their custom annotations and transaction management work as intended.
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.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
