how to reattach singleton Spring beans upon deserialization

对着背影说爱祢 提交于 2020-01-01 09:10:10

问题


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:


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/




回答2:


I used this instead, without any proxy:

public class Process implements HttpSessionActivationListener {
    ...
    @Override
    public void sessionDidActivate(HttpSessionEvent e) {
        ServletContext sc = e.getSession().getServletContext();
        WebApplicationContext newContext = WebApplicationContextUtils
            .getRequiredWebApplicationContext(sc);
        newContext.getAutowireCapableBeanFactory().configureBean(this, beanName);
    }
}

The example is for a web environment when the application server serializes the session, but it should work for any ApplicationContext.




回答3:


Spring provides a solution for this problem.

Take a look at the spring documentation http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-atconfigurable.

7.8.1 Using AspectJ to dependency inject domain objects with Spring

...

The support is intended to be used for objects created outside of the control of any container. Domain objects often fall into this category because they are often created programmatically using the new operator, or by an ORM tool as a result of a database query.

The trick is to use load time weaving. Just start the jvm with -javaagent:path/to/org.springframework.instrument-{version}.jar. This agent will recognize every object that is instantiated and if it is annotated with @Configurable it will configure (inject @Autowired or @Resource dependencies) that object.

Just change the Process class to

@Configurable
class Process {

   @Autowired
   private transient Repository repository;
   // getters, setters, etc.
}

Whenever you create a new instance

Process process = new Process();

spring will automatically inject the dependencies. This also works if the Process object is deserialized.




回答4:


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);
   }
}



回答5:


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).




回答6:


The method applicationContext.getAutowireCapableBeanFactory().autowireBean(detachedBean); can be used to reconfigure a Spring-managed bean that was serialized and then de-serialized (whose @Autowired fields become null). See example below. The serialization details are omitted for simplicity.

public class DefaultFooService implements FooService {

    @Autowired
    private ApplicationContext ctx;

    @Override
    public SerializableBean bar() {
        SerializableBean detachedBean = performAction();
        ctx.getAutowireCapableBeanFactory().autowireBean(detachedBean);
        return detachedBean;
    }

    private SerializableBean performAction() {
        SerializableBean outcome = ... // Obtains a deserialized instance, whose @Autowired fields are detached.
        return outcome;
    }

}


public class SerializableBean {

    @Autowired
    private transient BarService barService;

    private int value;

    public void doSomething() {
        barService.doBar(value);
    }

}


来源:https://stackoverflow.com/questions/3471835/how-to-reattach-singleton-spring-beans-upon-deserialization

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!