views:

70

answers:

3

I want to reinject singleton-scoped dependencies into prototype Spring beans, after they have been deserialized.

Say I've got a Process bean, which depends on a Repository bean. The Repository bean is a scoped as a singleton, but the Process bean is prototype-scoped. Periodically I serialize the Process, and then later deserialize it.

class Process {
   private Repository repository;
   // getters, setters, etc.
}

I don't want to serialize and deserialize the Repository. Nor do I want to put "transient" on the member variable that holds a reference to it in Process, nor a reference to some kind of proxy, or anything other than a plain old member variable declared as a Repository.

What I think I want is for the Process to have its dependency filled with a serializable proxy that points (with a transient reference) to the Repository, and, upon deserialization, can find the Repository again. How could I customize Spring to do that?

I figure I could use a proxy to hold the dependency references, much like . I wish I could use that exact technique. But the proxy I've seen Spring generate isn't serializable, and the docs say that if I use it with a singleton bean, I'll get an exception.

I could use a custom scope, perhaps, on the singleton beans, that would always supply a proxy when asked for a custom-scoped bean. Is that a good idea? Other ideas?

+1  A: 

I think the idea of serializing a bean and then forcing a reinjection of dependencies is not the best architecture.

How about having some sort of ProcessWrapper bean instead which could be a singleton. It would be injected with the Repository and either manages the deserialization of the Process or has a setter for it. When a new Process is set in the wrapper, it would call setRepository() on the Process. The beans that use the Process could either be set with the new one by the wrapper or call the ProcessWrapper which would delegate to the Process.

class ProcessWrapper {
   private Repository repository;
   private Process process;
   // getters, setters, etc.

   public void do() {
      process.do();
   }

   public void setProcess(Process process) {
      this.process = process;
      this.process.setRepository(repository);
   }
}
Gray
But how would `RepositoryFactory` work? It has to be serializable, and so cannot have a reference to the application context. It just pushes the problem around.
skaffman
Sorry, right. I've heavily edited my response.
Gray
Process and Repository are just examples (and Process is a bad example; sorry about that). The beans that are being deserialized, in general, are controllers that manage a particular interaction between the user and our webapp. There are many of them, and there are always more to write. They consume many different services.
Ladlestein
Do/can they all implement the same interface? Can you then write a proxy wrapper which implements that interface and delegates to each of them in turn. If they are all different then I agree that this solution is cumbersome.
Gray
+1  A: 

How about added using aspects to add an injection step when you deserialize the object?

You would need AspectJ or similar for this. It would work very similarly to the @Configurable function in Spring.

e.g. add some advice around the a "private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException" method

This article may also help: http://java.sun.com/developer/technicalArticles/Programming/serialization/

Pablojim
I've been resisting aspects because I don't want to rock the boat here, if you know what I mean, but this is an exploratory project, so maybe now's the time.Still, I'm wondering about other ways to do it.
Ladlestein
A: 

Answering my own question: how I've solved the problem so far is to create a base class which serializes and deserializes using a cheap little proxy. The proxy contains only the name of the bean.

You'll note that it uses a global to access the Spring context; a more elegant solution might store the context in a thread-local variable, something like that.

public abstract class CheaplySerializableBase 
   implements Serializable, BeanNameAware {

    private String name;

    private static class SerializationProxy implements Serializable {

        private final String name;

        public SerializationProxy(CheaplySerializableBase target) {
            this.name = target.name;
        }

        Object readResolve() throws ObjectStreamException {
            return ContextLoader.globalEvilSpringContext.getBean(name);
        }

    }

    @Override
    public void setBeanName(String name) {
        this.name = name;
    }

    protected Object writeReplace() throws ObjectStreamException {
        if (name != null) {
            return new SerializationProxy(this);
        }
        return this;
    }
}

The resulting serialized object is 150 bytes or so (if I remember correctly).

Ladlestein