Dodge Dodge - 1 year ago 76
Java Question

Method with @Transactional called on target not on proxy instance

I'm currently migrating one of my projects form "self configured spring" to spring boot. while most of the stuff is already working I have a problem with a

method where when it is called the context is not present as set before due to a call to the "target" instance instead of the "proxy" instance (I'll try to elaborate below).

First a stripped down view of my class hierarchy:

public class Config {
// fields and stuff

public interface Exporter {

int startExport() throws ExporterException;

void setConfig(Config config);

public abstract class ExporterImpl implements Exporter {
protected Config config;

public final void setConfig(Config config) {
this.config = config;
// this.config is a valid config instance here

@Transactional(readOnly = true)
public int startExport() throws ExporterException {
// this.config is NULL here

// other methods including abstract one for subclass

public class Cars2ExporterImpl extends ExporterImpl {

// override abstract methods and some other
// not touching startExport()

// there are other implementations of ExporterImpl too
// in all implementations the problem occurs

the calling code is like this:

private Provider<Exporter> cars2Exporter;

public void scheduleExport(Config config) {
Exporter exporter = cars2Exporter.get();
// actually I'm wrapping it here in a class implementing runnable
// and put it in the queue of a `TaskExecutor` but the issue happens
// on direct call too. :(

What exactly is the issue?

In the call to
the field
is null although it has been set right before.

What I found so far:
With a breakpoint at
I checked the id of the exporter instance shown by eclipse debugger. In the debbug round while writing this post it is
. Continuing execution into the call/first line of
where i checked the id again (of
this time) expecting it to be the same but realizing that it is not. It is
here... so the call to
is done on another instance of the class... in a previous debug round i checked to wich instance/id the call to
is going... to the first on (16585 in this case). This explains why the config field is null in the 16606 instance.

To understand what happens between the line where i call
and the actuall first line of
i clicked into the steps between those both lines in eclipse debugger.

There i came to line 655 in CglibAopProxy that looks like this:

retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();

checking the arguments here i found that
is the instance with id 16585 and
the one with 16606.

unfortunately I'm not that deep into springs aop stuff to know if that is how it should be...

I just wonder why there are two instances that get called on diffrent methods. the call to
goes to the proxy instance and the call do
reaches the target instance and thus does not have access to the config previously set...

As mentioned the project has been migrated to spring boot but we where before already using the
version of spring platform bom. From what i can tell there where no special AOP configurations before the migration and no explicitly set values after the migration.

To get this problem fixed (or at least somehow working) i already tried multiple things:

  • remove @Scope from the sub class

  • move @Transactional from method level to class

  • override startExport() in subclass and put @Transactional here

  • add @EnableAspectJAutoProxy to application class (i wasn't even able to log in - no error message)

  • set spring.aop.proxy-target-class to true

  • the above in diffrent combinations...

Currently I'm out of clues on how to get this back working...

Thanks in advance

*hopes someone can help*

Answer Source

Spring Boot tries to create a cglib proxy, which is a class based proxy, before you probably had an interface based (JDK Dynamic Proxy).

Due to this a subclass of your Cars2ExporterImpl is created and all methods are overridden and the advices will be applied. However as your setConfig method is final that cannot be overridden and as a result that method will be actually called on the proxy instead on the proxied instance.

So either remove the final keyword so that CgLib proxy can be created or explicitly disable class based proxies for transactions. Add @EnableTransationManagement(proxy-target-class=false) should also do the trick. Unless there is something else triggering class based proxies that is.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download