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