/javaDependencyInjector

Let us discover how dependency injection works, by creating out our own dependency injection for java

Primary LanguageJavaMIT LicenseMIT

ENSET-M II-BDCC2 Architecture distribuée et Middlewares ELAAMIRI essadeq

javaDependencyInjector

Into

The project

This project represent the basic concept of dependency injection by creating a basic dependency injection framework, based on xml and annotations configuration.

Objective

The aim from this project is to learn what is dependency injection and how it works.

Dependency injection

Project structure

Coding

Used Dependencies

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.4.0-b180830.0359</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.activation/activation -->
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.0-b170127.1453</version>
        </dependency>

    </dependencies>

Code

Configuration file

depinjectior.config.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<beans>
    <Bean name="employeeDaoImpl1" className="me.elaamiri.testSample.dao.EmployeeDaoImpl1">
    </Bean>
    <Bean name="employeeServiceImpl" className="me.elaamiri.testSample.service.EmployeeServiceImpl">
        <bean-field name="employeeDao" value="employeeDaoImpl1" injectInto="FIELD"/>
    </Bean>
</beans>

In this file, we specify the classes to be injected, where to be injected and how (FIELD, SETTER). In this example I want to inject EmployeeDaoImpl1 object in EmployeeServiceImpl object, which is depending on the first to do some actions. I choose the injection to be via a field in EmployeeServiceImpl, called employeeDao

Here is code example:

EmployeeDao.java

public interface EmployeeDao {
    public String getDAOAction();
}

EmployeeDaoImpl1.java

public class EmployeeDaoImpl1 implements EmployeeDao{
    @Override
    public String getDAOAction() {
        return "Impl 1";
    }
}

EmployeeDaoImpl2.java

public class EmployeeDaoImpl2 implements EmployeeDao{
    @Override
    public String getDAOAction() {
        return "Impl 2";
    }
}

EmployeeService.java

public interface EmployeeService {
    String getServiceMessage();
}

EmployeeServiceImpl.java

public class EmployeeServiceImpl implements EmployeeService{
    private EmployeeDao employeeDao; // to be injected
    public EmployeeDao getEmployeeDao() {
        return employeeDao;
    }
    /*
        public void setEmployeeDao(EmployeeDao employeeDao) {
            this.employeeDao = employeeDao;
        }
       */
    @Override
    public String getServiceMessage(){
        return employeeDao.getDAOAction();
        //return "Done";
    }
}

Tests

void public static void main(String[]args){
        // test
        Context context = DependencyInjector.runInjector(null);
        EmployeeServiceImpl service = (EmployeeServiceImpl) context.getBeanByName("employeeServiceImpl");
        System.out.println(service.getServiceMessage());
        //service.setEmployeeDao(new EmployeeDaoImpl1());
        System.out.println(service.getEmployeeDao());        
}

What just happened ?

It is easy-peasy /iːzɪˈpiːzi/: Here is the xml based framework structure:

sc1

Entities to represent the config xml file elements:

BeanFiled.java represent class property

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@Data
public class BeanField implements Serializable {
    @XmlAttribute
    private String name;
    @XmlAttribute
    private String value;

    @XmlAttribute
    private FieldInjectionType injectInto;


    public String getDefaultSetterName(){
        String setterName = "set".concat(String.valueOf(name.charAt(0)).toUpperCase().concat(name.substring(1)));
        System.out.println(setterName);
        return setterName;
    }
}

Bean.java represent the object

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@Data
public class Bean implements Serializable {
    @XmlAttribute
    private String name; // unique
    @XmlAttribute
    private String className;
    @XmlElement(name="bean-field")
    private List<BeanField> beanFields = new ArrayList<>();

    public BeanField addBeanField(BeanField beanField) throws BeanFieldExistsException {
        if(this.beanFields.contains(beanField)) throw new BeanFieldExistsException();
        this.beanFields.add(beanField);
        return beanField;
    }
}

Beans.java represent the objects list

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@Data
public class Beans implements Serializable {
    @XmlElement(name="Bean")
    private List<Bean> beans = new ArrayList<Bean>();

    public Bean addBean(Bean bean) throws BeanExistsException {
        if(this.beans.contains(bean)) throw new BeanExistsException();
        this.beans.add(bean);
        return  bean;
    }

    /***
     * Getting the bean with the given name
     * @param name
     * @return Bean
     * @throws BeanNotFoundException
     */
    public Bean getBeanByName(String name) throws BeanNotFoundException{
        Optional<Bean> bean2 = Optional.of(this.beans.stream().filter(bean -> bean.getName().equals(name)).collect(Collectors.toList()).get(0));
        return bean2.orElseThrow(() -> {
            return new BeanNotFoundException();
        });
    }

    public Bean getBeanByIndex(int index) throws BeanNotFoundException{
        Optional<Bean> bean2 = Optional.of(this.beans.get(index));
        return bean2.orElseThrow(() -> {
            return new BeanNotFoundException();
        });
    }
}

And the main object which is the context, where I stock all the injectable instances in a hashMap.

Context.java represent the context

public class Context {
    private  final HashMap<String, Object> instancesMap = new HashMap<>();

    public  Object addToInstancesMap(String name, Object object) throws BeanExistsException {
        if(instancesMap.containsKey(name) || instancesMap.containsValue(object)) throw new BeanExistsException();
        instancesMap.put(name, object);
        return object;
    }

    public  Object getBeanByName(String name){
        return instancesMap.getOrDefault(name, null);
    }
}

The main point, where the magic happens

Context.java represent the context Retrieving Beans from xml file:

    /***
 *
 * @param filePath
 * @return
 * @throws BeansCouldNotBeLoadedException
 */
private static Beans loadBeansFromConfigFile(String filePath) throws BeansCouldNotBeLoadedException {
        JAXBContext jaxbContext;
        Optional<Beans> beansOptional = null;
        try {
        jaxbContext = JAXBContext.newInstance(Beans.class);
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        Beans beans = (Beans) unmarshaller.unmarshal(new File(filePath));
        beansOptional = Optional.of(beans);

        } catch (Exception e) {
        e.printStackTrace();
        }

        return beansOptional.orElseThrow(() -> {
        return new BeansCouldNotBeLoadedException();
        });
        }

Creating the instances and do the injection, after that return the context please, (which have been used in the test Application)

Context.java represent the context

/***
 *
 * @param configFilePath
 * @return
 * @throws BeansCouldNotBeLoadedException
 */
public static Context runInjector(String configFilePath) throws BeansCouldNotBeLoadedException {

        /**
         * TODO:
         * 1- read xml file; create a beans, adding them to beans list,[done]
         * 2- do injection  (properties)
         * 3- do injection (constructor)
         * 4- validating file use xsd
         *
         */

        Beans beans = loadBeansFromConfigFile(Constants.DEFAULT_CONFIG_FILE);
        Context context = new Context();
        HashMap<String, Object> contextBeans = new HashMap<>();
        // load classes
        beans.getBeans().forEach(bean -> {
        try {
        Class beanClass = Class.forName(bean.getClassName());
        // create instances
        Object beanInstance = beanClass.getDeclaredConstructor().newInstance();
        contextBeans.put(bean.getName(), beanInstance);
        // check if bean has properties to
        if (!bean.getBeanFields().isEmpty()) {
        bean.getBeanFields().forEach(beanField -> {
        Method beanFieldSetter = null;
        if (beanField.getInjectInto().equals(FieldInjectionType.SETTER)) {
        try {
        // get the setter
        beanFieldSetter = beanClass.getDeclaredMethod(beanField.getDefaultSetterName(), contextBeans.get(beanField.getValue()).getClass().getInterfaces());
        // pass the value (injection) (invoking setter)
        beanFieldSetter.invoke(beanInstance, contextBeans.get(beanField.getValue()));

        } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
        }
        } else if (beanField.getInjectInto().equals(FieldInjectionType.FIELD)) {

        try {
        // if there is no setter methode use field based injection
        Field field = beanClass.getDeclaredField(beanField.getName());
        // Set the accessibility as true
        field.setAccessible(true);
        field.set(beanInstance, contextBeans.get(beanField.getValue()));
        } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
        }
        }


        }
        );
        }

        context.addToInstancesMap(bean.getName(), beanInstance);


        } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
        } catch (InstantiationException e) {
        throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
        } catch (BeanExistsException e) {
        throw new RuntimeException(e);
        }
        });
        return context;


        }

TODO: XML

  • validate config xml file (xsd)
  • on startup load the file create the beans
  • inject every bean where it should be injected

Annotations based Dependency injection

  • Processing package classes

  • create objects , add them to the app context

  • do field based injection

  • do constructor based inj

  • do setters based injection

  • validations on names and types of context objects, (Check to dos in code)

  • Refactoring

  • Jar file exportation