Problem
In Post Instantiation with AspectJ, we described a solution that can inject dependencies into objects created outside of the container.
Typically such objects are a rich domain objects that are created by persistence layer. In the example we showed a simplified Customer class:
public class Customer{ private Service1 service1; private Service2 service2; public void setService1(Service1 service1){ this.service1 = service1; } public void setService1(Service2 service2){ this.service2 = service2; } public void doBusiness(){ service1.doSomething(); service1.doSomething(); } }
In reality, such domain object will have many more properties, such as name, social security number, age, gender etc. Most of them are filled out by the persistence layer such as Hibernate and should not be injected by the container.
This is not a problem when we use manual wiring because we just know the names of the properties that need to be injected.
But manual wiring may sometimes become tedious and unnecessary in the one-instance-per-type senario. By-type auto wiring will make configuration much easier in this case.
In order to do auto-wiring, we need to tell the container that which properties need to be injected and which ones don't. The most straight-forward way would be to specify the "property-names" attribute in the <bean> tag, for example:
<bean class="Customer" property-names="service1,service2" autowire="bytype"/>
This solution is simple and works fine. There are two problems though:
- we have to list the property names.
- In case the domain object is refactored, more dependencies are added or some dependencies are removed, the property names will have to be maintained consistently.
Lazy as we are, we want to be able to say:
<bean class="Customer" autowire="bytype"/>
And hopefully the <bean> tag knows automatically which properties need to be injected.
Solution
One possible solution is to use the Java 5 Annotation mechanism so that the source code itself can tell us "I need you to inject service1 and service2, leave everything else alone".
Before going into implementation detail, let me show the result first.
Domain object:
public class Customer{ private Service1 service1; private Service2 service2; public void setName(String name){...} public void setAge(int age){...} @Inject public void setService1(Service1 service1){ this.service1 = service1; } @Inject public void setService1(Service2 service2){ this.service2 = service2; } public void doBusiness(){ service1.doSomething(); service1.doSomething(); } }
Aspect:
public aspect DomainInjectionAspect extends BaseInjectionAspect{ /** * the creation of any object that is a client of the * DomainObject.new service */ pointcut clientCreation(Object aClient) : initialization(com.mycompany.domain.*+.new(..)) && this(aClient); }
Configuration:
<module name="test aspectj"> <!-- the injector tag is not pre-loaded, so we load it explicitly. --> <nut name="injector" class="jfun.yan.xml.nuts.optional.InjectorAspectNut"/> <!-- load the annotation aware bean tag and name it "abean" --> <nut name="abean" class="jfun.yan.nuts.annotations.AnnotationAwareBeanNut"/> <body> <bean id="service1" class="Service1Impl" .../> <bean id="service2" class="Service2Impl" .../> <function id="general_injection" params="domainobj"> <!-- use abean to auto-wire based on annotation. --> <abean component="$domainobj" autowire="bytype"/> </function> <injector class="DomainInjectionAspect" injection="$general_injection"/> </body> </module>
That's it. We have one piece of xml configuration code that handles all domain object types; Only one aspect is required.
Annotate the domain object
First step, we create a marker annotation that can be used to say "inject me".
@Retention(RetentionPolicy.RUNTIME) public @interface Inject{}
The Retention policy has to be RUNTIME for obvious reasons.
The Customer class can then be written as:
import jfun.yan.nuts.annotations.Inject; public class Customer{ private Service1 service1; private Service2 service2; public void setName(String name){...} public void setAge(int age){...} @Inject public void setService1(Service1 service1){ this.service1 = service1; } @Inject public void setService1(Service2 service2){ this.service2 = service2; } public void doBusiness(){ service1.doSomething(); service1.doSomething(); } }
Configuration
It is then the responsibility of the containe to provide support that recognizes the annoation and auto wire based on meta data.
Using Nuts' extensible tag, there's a tag class already built for this purpose: AnnotationAwareBeanNut.
Using this class, we can do auto-wiring for the post-instantiation example as:
<module name="test aspectj"> <!-- the injector tag is not pre-loaded, so we load it explicitly. --> <nut name="injector" class="jfun.yan.xml.nuts.optional.InjectorAspectNut"/> <!-- load the annotation aware bean tag and name it "abean" --> <nut name="abean" class="jfun.yan.nuts.annotations.AnnotationAwareBeanNut"/> <body> <bean id="service1" class="Service1Impl" .../> <bean id="service2" class="Service2Impl" .../> <!-- describe the injection logic for Customer. --> <function id="customer_injection" params="customer"> <!-- use abean to auto-wire based on annotation. --> <abean component="$customer" autowire="bytype"/> </function> <!-- the aspect that weaves the dependency injection --> <injector class="CustomerInjectionAspect" injection="$customer_injection"/> </body> </module>
- First we load the nut class using <nut>, and name it "abean".
- When configuring a customer object, we specify autowire mode as "bytype" and use <abean> to wire by annotation.
Even better, you may or may not have realized, with auto-wiring, we now don't really need a <function> for every different domain type. Instead, we can just use a general one. We also don't need to create one aspect per domain type either, a general aspect will probably suffice too:
... <function id="general_injection" params="domainobj"> <!-- use abean to auto-wire based on annotation. --> <abean component="$domainobj" autowire="bytype"/> </function> <injector class="DomainInjectionAspect" injection="$general_injection"/> ...
public aspect DomainInjectionAspect extends BaseInjectionAspect{ /** * the creation of any object that is a client of the * DomainObject.new service */ pointcut clientCreation(Object aClient) : initialization(com.mycompany.domain.*+.new(..)) && this(aClient); }
Let the meta data and auto-wiring take care of the differences, why do we care?
It is also possible to mix manual wiring and auto-wiring together, for example, we could explicitly specify service1 and leave the rest to auto-wiring:
<abean component="$customer" autowire="bytype" props="{service1=$service1}"/>
Custom Annotation
In the previous example, the annotation "Inject" is pre-defined in the container API. This means that the domain object still has dependency onto the container. Practically this is not too bad, because the domain object has to use an annotation anyway. But, in case this does become a problem, or when a domain object is already annotated by a different annotation type for the same purpose, Nuts offer the flexibility to use the custom annotation.
And that is easy, just pass <abean> the annotation type that you want to use. Let's say the client has a @InjectMe tag that serves the same purpose:
public class Customer{ private Service1 service1; private Service2 service2; public void setName(String name){...} public void setAge(int age){...} @InjectMe public void setService1(Service1 service1){ this.service1 = service1; } @InjectMe public void setService1(Service2 service2){ this.service2 = service2; } public void doBusiness(){ service1.doSomething(); service1.doSomething(); } }
The configuration will become:
<abean component="$customer" autowire="bytype" annotation="com.mycompany.InjectMe"/>
What if the "InjectMe" has an aweful long fully qualified name: "com.blahblahblah.blahblahblah.blahblahblah.InjectMe"? And what if we need to use <abean> 100 times? Isn't the need to specify the annotation type again and again too tedious?
Well, yes. And we have a simple solution for it. The following class extends from AnnotationAwareBeanNut by providing "InjectMe" as the default annotation type:
public class CustomBeanNut extends AnnotationAwareBeanNut{ CustomBeanNut(){ super(); super.setAnnotation(com.blahblahblah.blahblahblah.InjectMe.class); } }
Now, load this CustomBeanNut instead of AnnotationAwareBeanNut:
... <!-- load the the custom nut class instead --> <nut name="abean" class="com.blahblahblah.nuts.CustomBeanNut"/> ...
Everything else remain the same. Use <abean> for as many times as you want, your damn long annotation name don't ever have to show up in the configuration any more.
