Статьи

Зависимость объекта Wire вне контейнера Spring

Есть несколько интересных способов установки свойств и зависимостей объекта, созданного вне контейнера Spring.

Сценарии использования Для начала, зачем нам нужно делать инъекцию в зависимостях вне контейнера Spring — мне известны три случая использования, когда я создавал экземпляры объектов вне контейнера Spring и мне нужно было внедрять зависимости.

Рассмотрим сначала случай серии задач, выполняемых с использованием Spring TaskExecutor, задачи, выделенные ниже, создаются вне контейнера Spring:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
List<Callable<ReportPart>> tasks = new ArrayList<Callable<ReportPart>>();
        List<ReportRequestPart> reportRequestParts = reportRequest.getRequestParts();
        for (ReportRequestPart reportRequestPart : reportRequestParts) {
            tasks.add(new ReportPartRequestCallable(reportRequestPart, reportPartGenerator));
        }
 
        List<Future<ReportPart>> responseForReportPartList;
        List<ReportPart> reportParts = new ArrayList<ReportPart>();
        try {
            responseForReportPartList = executors.invokeAll(tasks);
            for (Future<ReportPart> reportPartFuture : responseForReportPartList) {
                reportParts.add(reportPartFuture.get());
            }
 
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
 
public class ReportPartRequestCallable implements Callable<ReportPart> {
 private final ReportRequestPart reportRequestPart;
 private final ReportPartGenerator reportPartGenerator;
 
 public ReportPartRequestCallable(ReportRequestPart reportRequestPart, ReportPartGenerator reportPartGenerator) {
     this.reportRequestPart = reportRequestPart;
     this.reportPartGenerator = reportPartGenerator;
    }
 
 @Override
    public ReportPart call() {
    return this.reportPartGenerator.generateReportPart(reportRequestPart);
    }
}

Второй вариант использования с шаблоном ActiveRecord, скажем, с примерами, поставляемыми с Spring Roo, рассмотрим следующий метод, где класс Pet должен сохранять себя и для этого нужен менеджер сущностей:

1
2
3
4
5
@Transactional
    public void Pet.persist() {
        if (this.entityManager == null) this.entityManager = entityManager();
        this.entityManager.persist(this);
    }

Третий вариант использования — для библиотеки тегов, которая создается веб-контейнером, но требует некоторых зависимостей от Spring.

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

1
tasks.add(new ReportPartRequestCallable(reportRequestPart, reportPartGenerator));

2. Второй подход заключается в создании фабрики, которая знает о контейнере Spring, объявляя bean-компоненты, которые требуются с областью прототипа внутри контейнера, и получая bean-компоненты методом getBeans контекста приложения,

Объявление бина как боба прототипа

1
2
3
4
5
<bean name='reportPartRequestCallable' class='org.bk.sisample.taskexecutor.ReportPartRequestCallable' scope='prototype'>
 <property name='reportPartGenerator' ref='reportPartGenerator'></property>
</bean>
 
<bean name='reportPartRequestCallableFactory' class='org.bk.sisample.taskexecutor.ReportPartRequestCallableFactory'/>

и фабрика, раздающая бобы:

01
02
03
04
05
06
07
08
09
10
11
12
public class ReportPartRequestCallableFactory implements ApplicationContextAware{
 private ApplicationContext applicationContext;
 
 @Override
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  this.applicationContext = applicationContext;
 }
  
 public ReportPartRequestCallable getReportPartRequestCallable(){
  return this.applicationContext.getBean('reportPartRequestCallable', ReportPartRequestCallable.class);
 }
}

3. Третий подход — это вариант вышеописанного подхода, заключающийся в создании экземпляра компонента и последующем внедрении зависимостей с использованием AutoWireCapableBeanFactory.autowireBean (instance) следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public class ReportPartRequestCallableFactory implements ApplicationContextAware{
 private GenericApplicationContext applicationContext;
 
 @Override
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  this.applicationContext = (GenericApplicationContext)applicationContext;
 }
  
 public ReportPartRequestCallable getReportPartRequestCallable(){
  ReportPartRequestCallable reportPartRequestCallable = new ReportPartRequestCallable();
  applicationContext.getBeanFactory().autowireBean(reportPartRequestCallable);
  return reportPartRequestCallable;
 }
}

4. Четвертый подход — использование @Configurable , но главное в том, что для работы требуется AspectJ. Spring существенно расширяет возможности конструктора класса для внедрения в зависимости в соответствии с тем, что явно делается в третьем подходе выше:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.beans.factory.annotation.Configurable;
 
@Configurable('reportPartRequestCallable')
public class ReportPartRequestCallable implements Callable<ReportPart> {
    private ReportRequestPart reportRequestPart;
    @Autowired private ReportPartGenerator reportPartGenerator;
 
    public ReportPartRequestCallable() {
    }
 
    @Override
    public ReportPart call() {
       return this.reportPartGenerator.generateReportPart(reportRequestPart);
    }
 
    public void setReportRequestPart(ReportRequestPart reportRequestPart) {
        this.reportRequestPart = reportRequestPart;
    }
 
    public void setReportPartGenerator(ReportPartGenerator reportPartGenerator) {
        this.reportPartGenerator = reportPartGenerator;
    }
}

Следующее также необходимо для настройки Аспекта, ответственного за плетение @Configurable:

1
<context:spring-configured/>

С учетом этих изменений любая зависимость для класса, аннотированного @Configurable, обрабатывается Spring, даже если конструирование выполняется полностью вне контейнера:

01
02
03
04
05
06
07
08
09
10
@Override
public Report generateReport(ReportRequest reportRequest) {
    List<Callable<ReportPart>> tasks = new ArrayList<Callable<ReportPart>>();
    List<ReportRequestPart> reportRequestParts = reportRequest.getRequestParts();
    for (ReportRequestPart reportRequestPart : reportRequestParts) {
        ReportPartRequestCallable reportPartRequestCallable = new ReportPartRequestCallable();
        reportPartRequestCallable.setReportRequestPart(reportRequestPart);
        tasks.add(reportPartRequestCallable);
    }
.......

Вывод

Все вышеперечисленные подходы эффективны при внедрении зависимостей в объекты, которые создаются вне контейнера. Лично я предпочитаю использовать подход 4 (используя @Configurable) в тех случаях, когда доступна поддержка AspectJ, иначе я бы пошел с подходом 2 (скрытие за фабрикой и использование прототипа bean-компонента).

Приятного кодирования и не забудьте поделиться!

Ссылка: Способы связывания зависимостей для объекта вне Spring Container от нашего партнера JCG Биджу Кунджуммена из блога all and sundry.