Сценарии использования Для начала, зачем нам нужно делать инъекцию в зависимостях вне контейнера 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.