Spring utilizes AOP to enhance methods with size effects. Typical size effects are SQL transactions, caching, logging, asynchronism.
It involves proxies or generated classes "around" existing classes or interfaces. So when a bean is created instead of an original type a new type is used and the implementation holds a reference to the original type/instance to deligate actions to and surrounding it with a custom and usually "magical" behavior.
Key point is that an enhanced bean is not of an original class type but of another type. This poses restrictions: in order to experience enhanced behavior you need to dispach calls from an enhanced proxy, not from an original class.
If you attempt to call a method marked for enhancement using the original instance (non-proxy
enhanced), liretally using this
, JVM dispatches a call directly to the original method not
entering the enhanced method so not executing advice code at all. In the following example the call
async()
will be blocking, the call caching()
won't utilize cache, the call
transactional()
won't work in an SQL transaction:
public void foo() { async(); // same as: this.async() caching(); transactional(); } @org.springframework.scheduling.annotation.Async public void async() { } @org.springframework.cache.annotation.Cacheable public void caching() { } @org.springframework.transaction.annotation.Transactional public void transactional() { }
To make the above code to work with advice side-effects you need to call methods on advices.
Spring Framework automatically injects them for you so you can redesign the code by moving enhanced methods to separate injectable beans:
@Service public class AsyncService { @org.springframework.scheduling.annotation.Async public void async() { } } @Service public class CachingService { @org.springframework.cache.annotation.Cacheable public void caching() { } } @Service public class TransactionalService { @org.springframework.transaction.annotation.Transactional public void transactional() { } } @Service public class BusinessLogic { @Autowired private AsyncService asyncSrv; @Autowired private CachingService cachingSrv; @Autowired private TransactionalService transactionalSrv; public void foo() { asyncSrv.async(); // now it is not the: this.async() cachingSrv.caching(); transactionalSrv.transactional(); } }
Alternatively you could attempt to inject a bean to itself. The constructor injection is out of the question (egg/chicken problem), but method or field injections are fine if you don't mind circular dependencies. The field injection:
@Service public class BusinessLogic { @Autowired private BusinessLogic self; public void foo() { self.async(); // it is not the: this.async() } @org.springframework.scheduling.annotation.Async public void async() { } }
or the method injection:
@Service public class BusinessLogic { private BusinessLogic self; @Autowired public setSelf(BusinessLogic self) { this.self = self; } public void foo() { self.async(); // it is not the: this.async() } @org.springframework.scheduling.annotation.Async public void async() { } }
Starting with Spring Boot 2.6.0 circular dependency injection is disabled by default, to enable legacy behavior you need to supply the configuration property:
spring.main.allow-circular-references=true
Another option is to obtain the enhanced bean wrapper from the Spring context in
@PostConstruct
(when all beans are arelady available):
@Service public class BusinessLogic { @Autowired BeanFactory appCtx; private BusinessLogic self; @PostConstruct public init() { this.self = appCtx.getBean(BusinessLogic.class); } public void foo() { self.async(); // it is not the: this.async() } @org.springframework.scheduling.annotation.Async public void async() { } }
https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch08s06.html
or even more concise with InitializingBean
, the trick here is that Spring will call
afterPropertiesSet
on a proxied object so this
is pointing to it:
@Service public class BusinessLogic implements org.springframework.beans.factory.InitializingBean { private BusinessLogic self; @Override public void afterPropertiesSet() { self = this; } public void foo() { self.async(); // it is not the: this.async() } @org.springframework.scheduling.annotation.Async public void async() { } } javac Bean.java javap -c -cp . Bean