[Home] [Index] [Previous] [Next]

Dependency Injection and JSR 330

Automatic dependency injection.

Introduction

The Arooa framework also supports the automatic injection of properites by using the @Inject annotation specified by JSR 330.

Support for the standard is not complete within Oddjob. In particular only the @Named qualifier is supported. Other @Qualifiers will be ignored.

During Arooa's configuration cycle, when it finds a property requiring automatic dependency injection, it interrogates all components in the configuration that implement the ServiceProvider interface for a matching Object to inject. If none is found Arooa moves on without a warning.

Now I hope that so far you're impressed by how much you can achieve with Oddjob while keeping your code independent of Oddjob and we don't want to spoil this by having your service beans implement some Oddjob interface. Well we don't, because Oddjob has a job for that.

The Services Job

The services job takes the objects registered with it, and provides them to Arooa as potential canidates for automatic dependency injection. This job is so named because within Oddjob the injected objects will typically be services that other jobs require to perform their tasks.

The easist way to learn about the service job is via example. Here's a simple example - a hungry job requires a snack to do some work.

Here's the hungry job. Note the use of @inject

    public static class HungryJob implements Runnable {
        
        private SnackProvider snackProvider;
        
        @Override
        public void run() {
            Snack snack = snackProvider.provideSnack();
            System.out.println("Snack eaten: " + snack.toString() + ".");
        }
        
        @Inject
        public void setSnackProvider(SnackProvider snackProvider) {
            this.snackProvider = snackProvider;
        }
    }

The hungry job uses a SnackProvider to retrieve it's snack.

    interface SnackProvider {
        
        public Snack provideSnack();
    }

A snack is just:

    interface Snack {
        // A marker interface only.     
    }

And here's our SnackProvider, A cafe.

    public static class Cafe implements SnackProvider {
            
        @Override
        public Snack provideSnack() {
            return new Snack() {
                @Override
                public String toString() {
                    return "Green Eggs and Ham";
                }
            };
        }
    }

Here's the Oddjob configuration that ties it all together. Our cafe is registered with the services job. When the hungry job is configured, our services job provides the cafe as the service for injection.

<oddjob>
    <job>
        <sequential>
            <jobs>
                <services>
                    <registeredServices>
                        <is>
                            <service>
                                <bean class="org.oddjob.devguide.ServicesJobExamplesTest$Cafe"/>
                            </service>
                        </is>
                    </registeredServices>
                </services>
                <bean class="org.oddjob.devguide.ServicesJobExamplesTest$HungryJob"/>
            </jobs>
        </sequential>
    </job>
</oddjob>

Here's the output:

Snack eaten: Green Eggs and Ham.

For when a job requires a more specialised service there is the @Named qualifier. Here our hungry Job is vegetarian.

    public static class HungryVegetarianJob implements Runnable {
        
        private SnackProvider snackProvider;
        
        @Override
        public void run() {
            Snack snack = snackProvider.provideSnack();
            System.out.println("Snack eaten: " + snack.toString() + ".");
        }
        
        @Inject @Named("Vegetarian")
        public void setSnackProvider(SnackProvider snackProvider) {
            this.snackProvider = snackProvider;
        }
    }

But fortunately a vegetarian cafe is available.

    public static class VegetarianCafe implements SnackProvider {
        
        public Snack provideSnack() {
            return new Snack() {
                @Override
                public String toString() {
                    return "Salad";
                }
            };
        }
    }

Here's the modified configuration with both types of hungry job, and the output.

<oddjob>
    <job>
        <sequential>
            <jobs>
                <services>
                    <registeredServices>
                        <is>
                            <service>
                                <bean class="org.oddjob.devguide.ServicesJobExamplesTest$Cafe"/>
                            </service>
                        </is>
                        <is>
                            <service>
                                <bean class="org.oddjob.devguide.ServicesJobExamplesTest$VegetarianCafe"/>
                            </service>
                            <qualifier>
                                <value value="Vegetarian"/>
                            </qualifier>
                        </is>
                    </registeredServices>
                </services>
                <bean class="org.oddjob.devguide.ServicesJobExamplesTest$HungryJob"/>
                <bean class="org.oddjob.devguide.ServicesJobExamplesTest$HungryVegetarianJob"/>
            </jobs>
        </sequential>
    </job>
</oddjob>
Snack eaten: Green Eggs and Ham.
Snack eaten: Salad.

If there is no service that matches the qualifier, as here:

<oddjob>
    <job>
        <sequential>
            <jobs>
                <services>
                    <registeredServices>
                        <is>
                            <service>
                                <bean class="org.oddjob.devguide.ServicesJobExamplesTest$Cafe"/>
                            </service>
                        </is>
                    </registeredServices>
                </services>
                <bean class="org.oddjob.devguide.ServicesJobExamplesTest$HungryVegetarianJob"/>
            </jobs>
        </sequential>
    </job>
</oddjob>

By default, if no service matching the qualifier can be provided, the first service of the correct type is used. As in this example, this can result in our vegetarian being given something it shouldn't eat.

Snack eaten: Green Eggs and Ham.

The intransigent property can be used to ensure only an exact match is chosen.

<oddjob>
    <job>
        <sequential>
            <jobs>
                <services>
                    <registeredServices>
                        <is intransigent="true">
                            <service>
                                <bean class="org.oddjob.devguide.ServicesJobExamplesTest$Cafe"/>
                            </service>
                        </is>
                    </registeredServices>
                </services>
                <bean class="org.oddjob.devguide.ServicesJobExamplesTest$ResilientVegetarianJob"/>
            </jobs>
        </sequential>
    </job>
</oddjob>

The default cafe is not automatically injected. Our Hungry vegetarian must be prepared to not get a snack.

    public static class ResilientVegetarianJob implements Runnable {
        
        private SnackProvider snackProvider;
        
        @Override
        public void run() {
            if (snackProvider == null) {
                System.out.println("No Snack!");
            }
            else {
                Snack snack = snackProvider.provideSnack();
                System.out.println("Snack eaten: " + snack.toString() + ".");
            }
        }
        
        @Inject @Named("Vegetarian")
        public void setSnackProvider(SnackProvider snackProvider) {
            this.snackProvider = snackProvider;
        }
    }

Our resilient hungry job handles the lack of a snack provider with only minimal fuss!

No Snack!

And that is how to get started with automatically injecting your services into your jobs.


[Home] [Index] [Previous] [Next]