Using dependency injection in an existing project

1.3k views Asked by At

We have an old application which uses modules. The main entry point (main() method) instantiates modules (classes) with reflection based on an XML configuration file, like:

<modules>
    <module class="com.example.moduleone.ModuleOne" />
    <module class="com.example.moduletwo.ModuleTwo" />
<modules>

A few modules have additional configuration in the modules.xml, for example:

<modules>
    <module class="com.example.modulethree.ConfigurableModule">
        <config>
            <keyOne>valueOne</keyOne>
            <keyTwo>valueTwo</keyTwo>
    </module>
<modules>

These kind of modules has a parametrized constructor which accepts an org.jdom.Element instance (parsed from the XML file):

public ConfigurableModule(Element moduleConfig) {
    ...
}

We would like to use CDI/Weld (or something else) for dependency injection. How can we instantiate our modules with the DI framework?

Would it be easier with Spring or Guice?

2

There are 2 answers

0
user3065913 On BEST ANSWER

I've figured out the answer with clay's eye opener answer. (Thank you!)

First, a bean which loads the modules.xml:

import static com.google.common.collect.Lists.newArrayList;

import java.util.List;

import javax.annotation.PreDestroy;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.inject.Singleton;

@Singleton
public class RequiredModuleConfigurationLoader {

    private final List<RequiredModuleConfiguration> configurationList = 
        newArrayList();

    public RequiredModuleConfigurationLoader() {
        // read the XML file here
        configurationList.add(new RequiredModuleConfiguration(
            "cdiproto.ModuleOne", "moduleConfig1"));
        configurationList.add(new RequiredModuleConfiguration(
            "cdiproto.ModuleTwo", "moduleConfig2"));
    }

    @Produces
    public List<RequiredModuleConfiguration> getRequiredModuleConfigurations() {
        return newArrayList(configurationList);
    }

    @Produces
    public ModuleConfiguration getModuleConfiguration(
            final InjectionPoint injectionPoint) {
        final String injectedClassName = injectionPoint.getMember()
            .getDeclaringClass().getName();
        for (final RequiredModuleConfiguration requiredModuleConfiguration: 
                configurationList) {
            final String moduleClassName = 
                requiredModuleConfiguration.getClassName();
            if (moduleClassName.equals(injectedClassName)) {
                final String option = 
                    requiredModuleConfiguration.getSubConfiguration();
                return new ModuleConfiguration(option);
            }
        }
        throw new IllegalStateException("Unknown module: " + 
            injectedClassName);
    }
}

It also produces the configuration instance for every module based on the InjectionPoint.

The Application class, which loads the modules based on the XML configuration is the following:

import static com.google.common.collect.Lists.newArrayList;

import java.lang.annotation.Annotation;
import java.util.List;

import javax.annotation.PreDestroy;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Inject;
import javax.inject.Singleton;

@Singleton
public class Application {

    private final Annotation qualifiers = new AnnotationLiteral<Any>() { };

    @Inject
    private List<RequiredModuleConfiguration> requiredModuleConfigurationInstance;

    @Inject
    private Instance<Module> moduleInstance;

    private final List<Module> modules = newArrayList();

    public void init() throws Exception {
        for (final RequiredModuleConfiguration requiredModuleConfiguration: 
                requiredModuleConfigurationInstance) {
            final String className = requiredModuleConfiguration.getClassName();
            final Class<Module> moduleClass = 
                (Class<Module>) Class.forName(className);

            final Instance<Module> currentModuleInstance = 
                moduleInstance.select(moduleClass, qualifiers);
            final Module module = currentModuleInstance.get();
            modules.add(module);
        }
        ...
    }

    ...
}

And a sample module:

import javax.inject.Inject;

public class ModuleOne implements Module {

    @Inject
    private ModuleConfiguration moduleConfiguration;

    @Inject
    ModuleTwo moduleTwo;

    public String getName() {
        return "moduleOne name";
    }
}
3
clay On

Yes, using Spring or Guice would make this much easier. There you can create an object in the configuration file, and inject that very object into another object during configuration.

The concept will look similar to what you have, but much better as you won't have to redesign from scratch and find all the nuances of DI.

An example from the Sping docs

  <!-- setter injection using the nested <ref/> element -->
  <property name="beanOne"><ref bean="anotherExampleBean"/></property>

  <!-- setter injection using the neater 'ref' attribute -->
  <property name="beanTwo" ref="yetAnotherBean"/>
  <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

The classes are called beans and an instance of one ("anotherExampleBean") is injected into another ("exampleBean"). (This example is using setter-injection, but you could also use constructor-injection so you don't have to re-write all your classes, if that helps)