Spring Dependency Injection в сериализуемые бины - PullRequest
6 голосов
/ 14 октября 2011

У меня есть класс обслуживания, который не сериализуем, и компонент, который должен быть сериализуемым, но должен иметь доступ к этому классу обслуживания:

class SomeBean implements Serializable
{
    private StuffFactory factory;

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

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

Это, очевидно, не работает, потому что теперь класс SomeBeanбольше не сериализуем.Как правильно решить эту проблему весной?Когда я делаю factory поле переходным, тогда я теряю введенный экземпляр фабрики при десериализации, или нет?И когда я сделаю StuffFactory также сериализуемым, тогда этот класс больше не будет одноэлементным, потому что каждый экземпляр SomeBean будет иметь свою собственную фабрику после десериализации.

Ответы [ 4 ]

2 голосов
/ 14 октября 2011

Вам нужен какой-то контекст, чтобы эта магия заработала.

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

Но лучше всего, если бы вы смогли вывести логику, которая требует StuffFactory, для какого-то синглтона SomeBeanService, т. Е .:

public class SomeBeanService {
    @Autowired
    private StuffFactory stuffFactory;

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

Обновление:

Весь смысл вышеупомянутой альтернативы ThreadLocal состоит в том, чтобы полностью избавиться от зависимости от StuffFactory от SomeBean. Это должно быть возможно, но потребует изменений в архитектуре. разделение интересов (одно из основных правил, не только Spring) подразумевает, что было бы неплохо разрешить SomeBean быть простым объектом передачи данных и бизнес-логикой для перемещения на уровень обслуживания.

Если вам не удастся этого добиться, тогда единственный способ - использовать какой-то контекст static (как сказал Ральф). Реализация такого контекста может включать использование ThreadLocal. Это позволило бы получить доступ к ApplicationContext, чтобы получить StuffFactory, но это почти так же безобразно, как глобальные переменные, поэтому избегайте его, если это возможно.

Update2:

Я только что видел Ваш комментарий, что SomeBean хранится в сеансе HTTP, и, следовательно, проблема сериализации / десериализации. Теперь еще больше советую изменить Ваш дизайн и убрать зависимость. Сделайте SomeBean простым DTO, настолько маленьким, насколько это возможно, чтобы избежать перегруженных сессий. Не должно быть логики, требующей доступа к одноэлементным Spring-компонентам внутри SomeBean. Такая логика должна быть размещена на уровне контроллера или сервиса.

0 голосов
/ 20 ноября 2011

Пересмотр вашей архитектуры потенциально практичен в этом сценарии.Если вы можете изолировать данные, которые нужно сериализовать, в простой объект передачи данных (DTO), то должна быть возможность предоставить оболочку для этого DTO, которая сама может содержать зависимости от других bean-компонентов.

Например:

public interface IDataBean {
    void setSomething(String someData);
}

// This is your session bean - just a plain DTO
public class MyDataBean implements IDataBean, Serializable {

   private String someData;

   public void setSomething( String someData ) {
      this.someData = someData;
   }
}

// This is the wrapper that delegates calls to a wrapped MyDataBean.
public class MyDataBeanWithDependency() implements IDataBean {

    private SomeOtherService service;

    private MyDataBean dataBean;

    public SimpleDataBeanWithDependency(MyDataBean dataBean, SomeOtherService service) {
       this.dataBean = dataBean;
       this.service = service;
    }

    public void setSomething(String someData) {
       // Here we make a call to the service to perform some specific logic that may, for example, hit a DB or something.
       String transformedString = service.transformString(someData);
       dataBean.setSomething(transformedString);
    }
}

public class SomeService {

    // This is a Spring session scoped bean (configured using <aop:scoped-proxy/>)
    @Autowired
    private MyDataBean myDataBean;

    // Just a plain singleton Spring bean
    @Autowired
    private SomeOtherService someOtherService;

    public IDataBean getDataBean() {
         return new MyDataBeanWithDependency(myDataBean, someOtherService); 
    }
}

Извините за весь код!Однако позвольте мне попытаться объяснить, что я имею в виду здесь.Если вы всегда получаете сессионный компонент через службу (в данном случае SomeService), у вас есть возможность создать оболочку для сессионного компонента.Эта оболочка может содержать любые зависимости bean-компонента (автоматически подключенные к SomeService), которые вы можете использовать в качестве части выполнения логики вместе с сессионным компонентом.

Изящный аспект этого подхода заключается в том, что вы также можете программировать для интерфейса.(см. IDataBean).Это означает, что, если, например, у вас есть контроллер, который получает компонент данных от службы, это делает модульное тестирование / макетирование очень чистым.

Возможно, даже более чистый подход с точки зрения кода был быдля MyDataBeanWithDependency, чтобы быть зарегистрированным в контейнере пружины, используя область «запроса».Таким образом, вы можете просто подключить этот компонент напрямую к SomeService.Это, по сути, прозрачно решает проблему создания экземпляров, поэтому вам не нужно вручную создавать экземпляр MyDataBeanWithDependency из службы.

Надеюсь, я сделал достаточно, чтобы объяснить себя здесь!

0 голосов
/ 14 октября 2011

Java предоставляет способ управления сериализацией и десериализацией, реализуя два метода:

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

Таким образом, вы можете изменить запись, чтобы сохранить бин без ссылки на службу, а позже, при чтении объекта, снова «вставить» ссылку.

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

Вы ДОЛЖНЫ создать экземпляр в readObject как компонент. Если вы используете только простой new, он не станет бобом. Поэтому вам нужен доступ к Spring Context, например, через статический метод. Пусть лучший способ - использовать фабрику бобов для создания нового экземпляра.


Другим способом, которым вы можете попытаться создать экземпляр для пружинного боба, является использование @Configurable, но для этого требуется настоящий AspectJ.

0 голосов
/ 14 октября 2011

Может иметь SomeBeanFactory:

class SomeBeanFactory {
    @Autowired
    private StuffFactory stuffFactory;

    public SomeBean deserialize(...) {
        SomeBean someBean = ...;
        someBean.setStuffFactory(stuffFactory);
        return someBean;
    }
}

Очевидно, вам нужно создать сеттер для factory в SomeBean.

...