как повторно присоединить одиночные бины Spring после десериализации

Я хочу повторно внедрить одноэлементные зависимости в прототип Spring beans после того, как они были десериализованы.

Скажем, у меня есть bean-компонент Process, который зависит от bean-компонента Repository. Bean-компонент Repository имеет область видимости как одноэлементный объект, но bean-компонент Process имеет область видимости прототипа. Периодически я сериализую процесс, а затем десериализую его.

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

Я не хочу сериализовать и десериализовать репозиторий. Я также не хочу помещать «временный» в переменную-член, которая содержит ссылку на нее в Process, ни ссылку на какой-либо прокси-сервер, или что-либо иное, кроме простой старой переменной-члена, объявленной как репозиторий.

Я думаю, что хочу, чтобы зависимость процесса была заполнена сериализуемым прокси-сервером, который указывает (с временной ссылкой) на репозиторий и после десериализации может снова найти репозиторий. Как я могу настроить Spring для этого?

Я полагаю, что мог бы использовать прокси-сервер для хранения ссылок на зависимости, как и. Хотел бы я использовать именно эту технику. Но прокси-сервер, который я видел в Spring, не сериализуем, и в документации говорится, что если я использую его с одноэлементным компонентом, я получу исключение.

Я мог бы использовать настраиваемую область видимости, возможно, для одноэлементных bean-компонентов, которая всегда будет предоставлять прокси при запросе bean-компонента с настраиваемой областью. Это хорошая идея? Другие идеи?


person Ladlestein    schedule 12.08.2010    source источник
comment
В каком контексте приложения находятся эти bean-компоненты? Контекст веб-приложения?   -  person skaffman    schedule 13.08.2010
comment
Прямо сейчас это не контекст веб-приложения. Позже, вероятно, будет контекст веб-приложения.   -  person Ladlestein    schedule 13.08.2010
comment
В таком случае, как происходит загрузка контекста? Это настольное приложение?   -  person skaffman    schedule 13.08.2010
comment
Это веб-приложение. Контекст загружается через ServletContextListener. У нас немного весны; Пытаюсь добавить еще. На нашем пути еще нет Spring MVC jar, поэтому я пока не использую контекст веб-приложения.   -  person Ladlestein    schedule 13.08.2010
comment
Я опубликовал ответ на свой вопрос, иллюстрирующий, как я решил проблему до сих пор.   -  person Ladlestein    schedule 06.10.2010


Ответы (6)


Как насчет добавления с использованием аспектов для добавления шага внедрения при десериализации объекта?

Для этого вам понадобится AspectJ или аналогичный. Это будет работать очень похоже на функцию @Configurable в Spring.

например добавить несколько советов по методу "private void readObject (ObjectInputStream in), который вызывает исключение IOException, ClassNotFoundException"

Эта статья также может помочь: http://java.sun.com/developer/technicalArticles/Programming/serialization/

person Pablojim    schedule 12.08.2010
comment
Я сопротивлялся аспектам, потому что не хочу раскачивать лодку здесь, если вы понимаете, что я имею в виду, но это исследовательский проект, так что, может быть, сейчас самое время. Тем не менее, мне интересно, как это сделать. - person Ladlestein; 13.08.2010

Вместо этого я использовал это без прокси:

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

Пример для веб-среды, когда сервер приложений сериализует сеанс, но он должен работать для любого ApplicationContext.

person nlebas    schedule 14.09.2011

Spring предлагает решение этой проблемы.

Взгляните на весеннюю документацию http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-atconfigurable.

7.8.1 Использование AspectJ для внедрения зависимостей объектов домена с помощью Spring

...

Поддержка предназначена для использования для объектов, созданных вне контроля какого-либо контейнера. Объекты домена часто попадают в эту категорию, потому что они часто создаются программно с использованием оператора new или инструментом ORM в результате запроса к базе данных.

Уловка состоит в том, чтобы использовать ткачество времени загрузки. Просто запустите jvm с -javaagent: path / to / org.springframework.instrument- {version} .jar. Этот агент распознает каждый объект, для которого создается экземпляр, и, если он помечен @Configurable, он настроит (вставит зависимости @Autowired или @Resource) этот объект.

Просто измените класс процесса на

@Configurable
class Process {

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

Всякий раз, когда вы создаете новый экземпляр

Process process = new Process();

spring автоматически добавит зависимости. Это также работает, если объект Process десериализован.

person René Link    schedule 07.02.2012
comment
Почему бы не добавить ключевое слово «transient» к переменной-члену репозитория? В противном случае ваш класс репозитория должен быть сериализуемым. , но я не понимаю, зачем вам сериализовать его. - person herman; 14.11.2012

Я думаю, что идея сериализации bean-компонента с последующим принудительным повторным внедрением зависимостей - не лучшая архитектура.

Как насчет того, чтобы вместо этого иметь какой-то bean-компонент ProcessWrapper, который мог бы быть синглтоном. Он будет внедрен вместе с репозиторием и либо управляет десериализацией процесса, либо имеет для него установщик. Когда в оболочке установлен новый процесс, он будет вызывать setRepository() для процесса. Компоненты, использующие процесс, могут быть либо установлены с помощью новой оболочки с помощью оболочки, либо вызвать ProcessWrapper, который будет делегировать процессу.

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);
   }
}
person Gray    schedule 12.08.2010
comment
Но как RepositoryFactory работать? Он должен быть сериализуемым и поэтому не может иметь ссылки на контекст приложения. Это просто решает проблему. - person skaffman; 13.08.2010
comment
Процесс и Репозиторий - это просто примеры (и Процесс - плохой пример; извините за это). Десериализуемые бины, как правило, представляют собой контроллеры, которые управляют определенным взаимодействием между пользователем и нашим веб-приложением. Их много, и всегда есть, что написать. Они потребляют много разных услуг. - person Ladlestein; 14.08.2010
comment
Все ли они могут реализовать один и тот же интерфейс? Можете ли вы написать прокси-оболочку, которая реализует этот интерфейс и делегирует каждому из них по очереди. Если все они разные, то я согласен, что это решение громоздкое. - person Gray; 21.08.2010
comment
Почему автоматическое повторное добавление утраченных зависимостей по времени десериализации не является хорошей идеей / архитектурой? - person Tuukka Mustonen; 06.05.2011
comment
Я имею в виду, что вам нужно продублировать множество функций, которые Spring уже делает в ручном коде. Как найти подходящие bean-компоненты для авто-инъекции? .. Кроме того, в Spring есть ряд функций, которые не будут дублироваться, поскольку этот bean-компонент не действительно находится в контексте. Я подумал, что обертка менеджера будет лучшей идеей. - person Gray; 06.05.2011

Отвечая на мой собственный вопрос: как я решил проблему до сих пор, это создать базовый класс, который сериализует и десериализует с помощью небольшого дешевого прокси. Прокси-сервер содержит только имя bean-компонента.

Вы заметите, что он использует глобальный объект для доступа к контексту Spring; более элегантное решение могло бы сохранить контекст в локальной переменной потока, что-то в этом роде.

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

Результирующий сериализованный объект составляет около 150 байт (если я правильно помню).

person Ladlestein    schedule 29.09.2010

Метод applicationContext.getAutowireCapableBeanFactory().autowireBean(detachedBean); можно использовать для перенастройки управляемого Spring bean-компонента, который был сериализован, а затем десериализован (чьи @Autowired поля становятся null). См. Пример ниже. Детали сериализации опущены для простоты.

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

}
person izilotti    schedule 07.06.2018