NotSerializableException caused by AspectJPointcutAdvisor on trying to fabricate a bean in the client tier

Greetings!

I tried to achieve dynamically adjustable behaviour of a particular button (similar to the “Next” button in all kinds of wizards) by a combination of the Strategy and the Factory Method patterns, when came across this problem.

Here’s some simplified relevant code:

  • the core module
@Service(HandlerFactoryService.NAME)
public class HandlerFactoryServiceBean implements HandlerFactoryService {

    @Inject
    protected Map<String, Handler> handlers;

    @Override
    public Handler getHandler(String id) {
        return handlers.get(id);
    }
}
@Service("Impl")
public class ConcreteHandler implements Handler {

    @Override
    public Boolean doStuff(Map<String, String> params) {
        return params.containsKey("test");
    }
}
  • the web module
public class AbstractScreen extends AbstractWindow {

    @Inject
    protected Button theBtn;

    @Inject
    protected HandlerFactoryService factory;
}
public class ConcreteScreen extends AbstractScreen {

    @Override
    public void init(Map<String, Object> params) {
        super.init(params);
        theBtn.setAction(new AbstractAction("doStuff") {
            @Override
            public void actionPerform(Component component) {
                Handler handler = factory.getHandler("Impl"); //EXCEPTION OCCURS HERE
                //do stuff...   
            }
        });
    }
}

The factory itself was registered in web-spring.xml.
It didn’t matter, if concrete handlers had been registered in web-spring.xml or Handler interface had had the “Service”-postfix and extended Serializable, an attempt to obtain a Handler in the web module produced the following:

java.io.NotSerializableException: org.springframework.aop.aspectj.AspectJPointcutAdvisor
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1378) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1174) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178) ~[na:1.8.0_161]
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) ~[na:1.8.0_161]
        at com.haulmont.cuba.core.sys.serialization.StandardSerialization.serialize(StandardSerialization.java:34) ~[cuba-global-6.8.7.jar:6.8.7]
	... 62 common frames omitted

I’m not sure how did an AspectJPointcutAdvisor get queued for serialization.
Anyway, it was very wrong approach for a CUBA-app from the beginning. A LocalServiceProxy is basically an instruction for an invocation handler of some sort: which method of which bean to invoke and what arguments to use.
Thus you should only send data between tiers with a LocalServiceProxy, not dynamically adjust logic.

Here’s a working variant simplification, in which highly scalable architecture is preserved:

  • the core module
@Service(HandlerFactoryService.NAME)
public class HandlerFactoryServiceBean implements HandlerFactoryService {

    @Inject
    protected Map<String, Handler> handlers;

    @Override
    public Boolean doStuff(String id, Map<String, String> params) {
        return handlers.get(id).doStuff(params);
    }
}
  • the web module
public class ConcreteScreen extends AbstractScreen {

    @Override
    public void init(Map<String, Object> params) {
        super.init(params);
        theBtn.setAction(new AbstractAction("doStuff") {
            @Override
            public void actionPerform(Component component) {
                Map<String,String> map = new HashMap<>();
                //fill the map with params from the screen
                if (factory.doStuff("Impl", map)) { //WORKS PERFECTLY FINE
                         //do stuff...
                } 
            }
        });
    }
}

Please use back quote symbol for code blocks.

All your result types and parameter types used in service declaration must be serializable. It is required because service invocation is isolated from clients with Java serialization.

Thank you for editing, I’ll do it properly next time.

When I planned my structure, I hadn’t known about intermodular serialization in CUBA yet.
Anyway, the wrong approach I gave had Serializable implementations in all the result types and parameter types (including Handler), but it wasn’t a Handler that caused the exception there.
It was an AspectJPointcutAdvisor that somehow got queued for serialization without, indeed, being serializable.
Nonetheless, that was only a collateral exception. The real problem there was that behaviour can’t be serialized, what is exactly I was trying to do without knowing about intermodular serialization.
With this topic I tried to help other beginners, that may design a similar structure and thus face the the same exception, in finding out the causes of this problem.