Oleksandr Gavenko's blog
2022-09-07 19:09 Invoking proxied bean methods from the same bean

It is reoccuring question how one could invoke a Spring proxied bean method from the bean itself preserving all side-effects of AOP assigned to the method.

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
java, spring, aop, bytecode

Feeds

all / emacs / java

Tags

adb(1), admin(1), android(1), anki(1), ansible(2), aop(1), blog(2), bytecode(1), c(1), css(2), cygwin(2), driver(1), emacs(3), fs(1), git(3), google(1), gradle(1), hardware(1), hg(2), html(1), interview(13), java(4), js(3), lang(2), lighttpd(1), markdown(1), mobile(1), naming(1), oracle(1), print(1), problem(5), python(1), quiz(6), rst(2), security(3), spring(2), sql(2), srs(1), style(1), tls(2), txt(1), unit(1), utils(1), vcs(3), web(2), win(2), windows(1)

Archive