Spring Dependency Injection into serializable beans

10,034

Solution 1

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.

Solution 2

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.

Share:
10,034
kayahr
Author by

kayahr

„People assume that a Closure is a function having access to the parent scope, even after the parent function has closed, but actually from a non-linear, non-subjective viewpoint - it's more like a big ball of wibbly wobbly... timey wimey... stuff.“ – The JavaScript Doctor

Updated on July 14, 2022

Comments

  • kayahr
    kayahr almost 2 years

    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.

  • kayahr
    kayahr over 12 years
    But how can I get the stuffFactory instance in the readObject method to reinitialize the dependency? This dependency is injected automatically by Spring when the bean is created. I have no application context available in the readObject method to get the bean manually from there. I hoped for some Spring magic to handle this.
  • kayahr
    kayahr over 12 years
    Serialization and Deserialization is done by the web container (The SomeBean instance is somewhere in the HTTP session) so I can't delegate deserialization to a factory.
  • Ralph
    Ralph over 12 years
    @kayahr: see my extended answer
  • kayahr
    kayahr over 12 years
    But then I have to inject this SomeBeanService instead. Or do you mean accessing it without spring? Then this is somewhat similar to the Service Delegator pattern which is normally used by non-managed beans. But my SomeBean is spring-managed so I'd hoped that Spring has some magic for handling transient dependencies. Well, if no better solution pops up I guess I have to accept your answer.
  • kayahr
    kayahr over 12 years
    But a Spring bean normally doesn't know spring at all so it's not a good idea to get the application context via a static method. When I do this then I simply can drop the whole dependency injection idea and access the StuffFactory via a static getInstance method or something like this.
  • Ralph
    Ralph over 12 years
    Sorry but storing a bean is not a good idea at all, so you need to do some compromise. -- Second: this statement "Spring bean normally doesn't know spring at all" is may true for business beans, but not for infrastructure components. And what you do here is mixing infrastructure in an normal business bean. -- Anyway you can mask that strong spring dependency/access by annotation the class with @Configurable
  • Roadrunner
    Roadrunner over 12 years
    @Ralph: For @Configured will load time weaving be enough? Or will it require compile time weaving? Will the SomeBean still be serializable when weaved?
  • Ralph
    Ralph over 12 years
    @Roadrunner: 1) "For @Configured will load time weaving be enough?" I am not sure, but I expect hat load time weaving is enough, but I am not sure. -- Simply ask a new question at stack overflow. 2) If you "override" writeObject and readObject then it should work.