Use Cases
To start with, why would we need to do inject in dependencies outside of a Spring container - I am aware of three use cases where I have instantiated objects outside of the Spring container and needed to inject in dependencies.Consider first the case of a series of tasks executed using a Spring TaskExecutor, the tasks highlighted below are instantiated outside of a Spring container:
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); } }
The second use case is with ActiveRecord pattern say with the samples that come with Spring Roo, consider the following method where a Pet class needs to persist itself and needs an entity manager to do this:
@Transactional public void Pet.persist() { if (this.entityManager == null) this.entityManager = entityManager(); this.entityManager.persist(this); }
The third use case is for a tag library which is instantiated by a web container, but needs some dependencies from Spring.
Solutions
1. The first approach is actually simple, to provide the dependencies at the point of object instantiation, through constructors or setters. This is what I have used with the first use case where the task has two dependencies which are being provided by the service instantiating the task:tasks.add(new ReportPartRequestCallable(reportRequestPart, reportPartGenerator));
2. The second approach is a to create a factory that is aware of the Spring container, declaring the beans that are required with a prototype scope within the container and getting the beans by a getBeans method of the application context,
Declaring the bean as a prototype scoped bean:
<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"/>
and the factory serving out the bean:
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. The third approach is a variation of the above approach is to instantiate the bean and then inject dependencies using AutoWireCapableBeanFactory.autowireBean(instance), this way:
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. The fourth approach is using @Configurable, the catch though is that it requires AspectJ to work. Spring essentially enhances the constructor of the class to inject in the dependencies along the lines of what is being explicitly done in the third approach above:
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; } }
The following is also required to configure the Aspect responsible for @Configurable weaving:
<context:spring-configured/>
With these changes in place, any dependency for a class annotated with @Configurable is handled by Spring even if the construction is done completely outside of the container:
@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); } .......
Conclusion
All of the above approaches are effective with injecting in dependencies in objects which are instantiated outside of a container. I personally prefer to use Approach 4 (using @Configurable) in cases where AspectJ support is available, else I would go with Approach 2(hiding behind a factory and using a prototype bean).
Very interesting article.
ReplyDeleteI was talking about this subject some months ago with a developer who also tried the approaches you mention. What he told me (but I didn't try) is that is never uses the 4th solution, since he tried it and encountered performance problems. Did you notice some performance issues ?