Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Dependency Injection into serializable beans

I have a service class which is not serializable and a bean which must be serializable but must have access to this service class:

class SomeBean implements Serializable
{
    private StuffFactory factory;

    @Autowired
    public SomeBean(StuffFactory factory)
    {
        this.factory = factory;
    }

    public getOther()
    {
        return this.factory.getSomeOtherStuff();
    }
}

This obviously doesn't work because now the SomeBean class is no longer serializable. What is the correct way to solve this in Spring? When I make the factory field transient then I loose the injected factory instance on deserialization, or not? And when I make the StuffFactory also serializable then this class will no longer be a singleton because each SomeBean instance will have it's own factory after deserialization then.

like image 370
kayahr Avatar asked Oct 14 '11 05:10

kayahr


2 Answers

You need some kind of context for this magic to work.

One ugly way I can think of is a static ThreadLocal holding the ApplicationContext - it's how the spring-security works using SecurityContextHolder.

But the best if You'd be able to externalize the logic that requires the StuffFactory to some kind of singleton SomeBeanService, i.e.:

public class SomeBeanService {
    @Autowired
    private StuffFactory stuffFactory;

    public void doWithSomeBean(SomeBean bean) {
        // do the stuff using stuffFactory here
    }
}

Update:

The whole point in above alternative to ThreadLocal is to get rid of the dependency on StuffFactory from SomeBean completely. This should be possible, but will require changes in architecture. The separation of concerns (one of, not only Spring, basic rules) implies that it might be a good idea to let SomeBean be a simple data transfer object and the business logic to be moved to service layer.

If You'll be unable to achieve this, than the only way is using some kind of static context (as Ralph also said). The implementation of such context may involve using a ThreadLocal. This would allow to access the ApplicationContext to get the StuffFactory, but it's almost as ugly as global variables, so avoid it if possible.

Update2:

I just saw Your comment, that SomeBean is stored in HTTP session, and hence the serialization/deserialization issue. Now even more I advice to change Your design and remove the dependency. Make SomeBean a simple DTO, as small as possible to avoid overloaded sessions. There should be no logic requiring access to singleton Spring beans inside the SomeBean. Such logic should be placed in the controller or service layer.

like image 107
Roadrunner Avatar answered Nov 09 '22 15:11

Roadrunner


Java provide a way to control the serialization and deserialization by implementing the two methods:

  • private void writeObject(ObjectOutputStream out) throws IOException;
  • private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;

So you can change the write to store the bean without the reference to the service and later one while reading the object "inject" the reference again.

@see http://java.sun.com/developer/technicalArticles/Programming/serialization/

You MUST create the instance in readObject as bean. If you only use a simple new it will not become a bean. Therefore you need access to the Spring Context, for example via a static method. May the best way is to use a bean factory to create the new instance.


An other way you can try to make the instance a spring bean is using @Configurable but that requires real AspectJ.

like image 38
Ralph Avatar answered Nov 09 '22 14:11

Ralph