|
|||||
|
|||||
Openness Compared
Light weight containers were born when people frown at the intrusion mandated by heavy weight solutions such as EJB. We like to write pojo's; we don't want to implement any funny interfaces or super classes. Yet, it is a pity if a container still mandates certain programming styles. None of these are universally superior to the others. That's why an open container better not simply say no to any of these injection methods. In the Injection Methods Compared section, we did a comparison of the injection methods supported by these containers. Dependency Injection for objects not created by containerOther than injection methods, there may still be other assumptions that could become restrictions. For example: all components are managed by the container. Once this assumption is made, the container is not so open because it refuses to manage dependencies for objects not created by the container. As an example, let's think about domain objects constructed by a persistence framework such as Hibernate. It is very possible that the persistence framework has no knowledge of the container (and should not) and simply created these instances by calling the constructor. Nonetheless, there may still be dependencies that need to be injected if these domain objects are rich domain objects as described here. A naive solution may just obtain a reference to the container and set the dependencies manually: pubic class BankService{
public BankAccount getBankAccount(...){
BankAccount acct = (BankAccount)session.getById(...);
Bank bank = (Bank)session.getById(...);
acct.setProperty1((ABC)container.getInstance("abc"));
acct.setProperty2((DEF)container.getInsance("def"));
bank.setProperty1((XYZ)container.getInstance("xyz"));
acct.setBank(bank);
return acct;
}
Works, but not scalable and not configurable. It is natural to feel bad about having to do this since we already have a dependency injection framework - the container, why do we still have to do injection in a different place manually? Plus, this solution effectively locks you in with whatever the container framework you use. An unnecessary dependency on the container is introduced. The worst thing about this solution, IMHO, is that the Java code and the container have to agree on all these "abc", "def", "xyz" names. That clutters up the global namespace of the container and mandates too strong a coupling between the Java code and the container. SolutionSpring gives a solution for this senarios using the "AbstractAutowireCapableBeanFactory.applyBeanPropertyValues()" method. It is better than not having it, but this solution also locks you into the Spring framework. The Java code has to introduce dependency on Spring's API, which doesn't 100% follow the Dependency Injection phylosophy. Ideally, our business objects should be able to just wait for everything they need to be "push"ed to them, rather than going out "pull"ing them using various API's such as "applyBeanPropertyValue". The following describes a solution in Nuts. First, let's forget about container crap and abstract the problem we face. Since we want to externalize the "dependency injection" for the BankAccount object, we need an interface that models this concept: public interface BankAccountInjector{ void inject(Bank bank, BankAccount acct); } We include both Bank and BankAccount as parameters because these two are inter-coupled in the dependency injection. With this interface, the code becomes: pubic class BankService{
private final BankAccountInjector injector;
public BankAccountService(BankAccountInjector injector){
this.injector = injector;
}
public BankAccount getBankAccount(...){
BankAccount acct = (BankAccount)session.getById(...);
Bank bank = (Bank)session.getById(...);
injector.inject(bank, acct);
return acct;
}
}
The code now is cleaner. All dependency injection logic are encapsulated in the BankAccountInjector interface. The only problem left is how we can inject the BankAccountInjector object into BankService. And that can be done in Nuts. First, we will use the <function> tag to spell out the whole injection logic: <function id="injection" params="bank, acct"> <sequence> <bean component="$bank"> <prop key="xyz"> <ctor class="XYZ" .../> </prop> </bean> <bean component="$acct"> <prop key="abc"> <ctor class="ABC" .../> </prop> <prop key="def"> <ctor class="DEF" .../> </prop> <prop key="bank" val="$bank"/> </bean> </sequence> </function> This function takes bank and bank account as two parameters. It then uses a <sequence> tag to sequentially execute two steps to set dependencies for bank and bank account. Once we have the function defined, we can use the <factory> tag to adapt a function to the BankAccountInjector interface: <factory id="injector" type="BankAccountInjector" component="$injection"/> The <factory> tag internally creates a dynamic proxy that implements this interface and uses the function as the implementation. When wiring up the BankService component, we can say: <ctor id="bankservice" class="BankService" args="$injector"/> Therefore, even for objects not created by Nuts, Nuts can still inject dependency for it without any intrusion to the business object design. We believe this feature is unique and makes Nuts an extremely open container. |
|||||
|
Copyright 2003-2006 - The Codehaus. All rights reserved unless otherwise noted.
Powered by Atlassian Confluence
|
|||||