Mittwoch, 23. Oktober 2013

BARACUS from Scratch - Part 4 - IOC and DI

Table of Contents

Previous Tutorial : Basic Persistence Mapping

In this tutorial I am going to give You a brief overview about the IOC and DI implementation of the Baracus framework. With few interfaces ond even fewer effort You can make Your application context to the central instance of management for Your beans and components.

 Download the sourcecode of this tutorial from Github

 Step 1 - Dependency Injection 

Dependency Injection (or shortened DI) is the most common and most well known aspect of IOC. IOC itself stands for Inversion of Control and is basically summarizeable as the "Hollywood principle" in the Java bean world. The hollywood priciple says "Don't call us, we'll call You".

Basically, Dependency Injection means to us, that in our world no bean constructors are called explicitly. Instead of creating a bean instance, the instance of a bean is created by the bean container and brought into our bean via injection.

Beans are instantiated by the ApplicationContext


To make use of CDI and DI, You have to register Your Java class as a container managed bean. If you followed the prior tutorials, You already made that when You registered Your DAOs - the approach Baracus follows disregards anything bound to dynamic annotation scanning, so You have to call the registerBeanClass function providing Your bean class to the Baracus Application container. That's it. Now Your bean a) is instantiated and managed by Baracus and b) automatically has become injecteable to other beans.

Baracus brings You a ConfigurationDao bean by default, which enables You to store and retrieve key-value-pairs for Your config options. It is very sensible to encapsulate this into a ConfigurationService, so here we start :

package org.baracus.service;

import org.baracus.annotations.Bean;
import org.baracus.dao.ConfigurationDao;
import org.baracus.model.ConfigurationParameter;

/**
 * Created with IntelliJ IDEA.
 * User: marcus
 */

@Bean
public class ConfigurationService {

    @Bean
    ConfigurationDao configurationDao;

}


After registering this class, the configurationDao will be injected automatically, that means, we don't have to call any contructors. As you can see, the ConfigurationDao and the service class both are annotated with the @Bean annotation.

This annotation is only for documentation purpose! The injection is done on the type of the bean class!

After creating the service bean, we need to register it in the ApplicationContext:

package org.baracus.application;

// ....

public class ApplicationContext extends BaracusApplicationContext {

    static {
        // ....
        registerBeanClass(ConfigurationService.class);
    }   

}

That's it. Finally let us place some sensibe function to demonstrate the encapsulation of a configuration parameter into the bean:


package org.baracus.service;

import org.baracus.annotations.Bean;
import org.baracus.dao.ConfigurationDao;
import org.baracus.model.ConfigurationParameter;

/**
 * Created with IntelliJ IDEA.
 * User: marcus
 */

@Bean
public class ConfigurationService {

    @Bean
    ConfigurationDao configurationDao;

    private static final String KEY_APP_INIT_DONE="APPL_INIT_DONE";

    private static enum YesNo {
        YES,
        NO
    }

    public boolean isApplicationInitializationDone() {
        ConfigurationParameter parameter= configurationDao.getByName(KEY_APP_INIT_DONE);
        if (parameter != null) {
            return YesNo.YES.toString().equals(parameter.getConfigParameterValue());
        }
        return false;
    }

    public void setApplicationInitializationDone(boolean isEnabled) {
        ConfigurationParameter parameter= configurationDao.getByName(KEY_APP_INIT_DONE);

        if (parameter == null) {
            parameter = new ConfigurationParameter();
            parameter.setConfigParameter(KEY_APP_INIT_DONE);
        }

        parameter.setConfigParameterValue(isEnabled ? YesNo.YES.toString()
                                                    : YesNo.NO.toString());

        configurationDao.save(parameter);

    }
}

That's all. Now we are able to create some "first-run-logics" when the Application is launched the very first time on a device.

DI : Conclusion

Baracus offers You a very simple - but lightweight - type based dependency injection, which has got certain limitations :

  1. Discrete Types only. You can use inheritance in Your beans, but You always must register a discrete type in order to have dependency injection. The Baracus implementation is very simple and lightweight and does not support complex qualifiers like CDI (JSR 330) implementations like spring.
  2. No polymorphisms, You cannot register a bean implementation twice!
  3. Default constructors; Your bean must a) provice a default constructor - or - a constructor taking the context.
Therefore, you get some powerful feautures :
  1. You have dependency injection available in any Activity
  2. You have dependency injection available in any implementation of ManagedFragment. You should not use the standard fragment, because Android has the very painful behaviour on re-instantiating fragment internally on a device rotate. ManageFragment takes care of the DI part.
  3. You even can have circular dependencies - although a good design should avoid this.
  4. In good old times (tm) everything was coupled using interfaces. Since JEE 6 this paradigm has changed, so reducing the injection to the most basic information (=the class) is imo a quite effective way.

Step 2 - Making use of Lifecycle Management

Sometimes You need run some code in order to make a component usable to others. A good example is a cache, which is built up on start and needs to be teared down on shutdown.

To get suport of the lifecycle management, simply implement Initializeable or the Destroyable interfaces.

The postConstruct() method will be called, after Baracus finished the bean's dependency injection and enables You to take influence on the bean initialization before delivery.

The onDestroy() method is called, before the bean is discarded and lets You take influence on the bean's disposal. I chose the name using the android function in order to avoid double implementations on form classes.

So we are having Our ConfigurationService like this :

package org.baracus.service;

import org.baracus.annotations.Bean;
import org.baracus.dao.ConfigurationDao;
import org.baracus.lifecycle.Destroyable;
import org.baracus.lifecycle.Initializeable;
import org.baracus.model.ConfigurationParameter;

import java.util.Date;

/**
 * Created with IntelliJ IDEA.
 * User: marcus
 */

@Bean
public class ConfigurationService implements Initializeable, Destroyable{

    @Bean
    ConfigurationDao configurationDao;

    Date lastStarted;

    private static final String KEY_APP_INIT_DONE="APPL_INIT_DONE";
    private static final String KEY_APP_LAST_START="APPL_LAST_START";

    private static enum YesNo {
        YES,
        NO
    }

    public boolean isApplicationInitializationDone() {
        ConfigurationParameter parameter= configurationDao.getByName(KEY_APP_INIT_DONE);
        if (parameter != null) {
            return YesNo.YES.toString().equals(parameter.getConfigParameterValue());
        }
        return false;
    }

    public void setApplicationInitializationDone(boolean isEnabled) {
        ConfigurationParameter parameter= configurationDao.getByName(KEY_APP_INIT_DONE);

        if (parameter == null) {
            parameter = new ConfigurationParameter();
            parameter.setConfigParameter(KEY_APP_INIT_DONE);
        }

        parameter.setConfigParameterValue(isEnabled ? YesNo.YES.toString()
                                                    : YesNo.NO.toString());

        configurationDao.save(parameter);

    }


    @Override
    public void onDestroy() {
        ConfigurationParameter parameter= configurationDao.getByName(KEY_APP_LAST_START);

        if (parameter == null) {
            parameter = new ConfigurationParameter();
            parameter.setConfigParameter(KEY_APP_LAST_START);
        }

        parameter.setConfigParameterValue(String.valueOf(lastStarted.getTime()));

        configurationDao.save(parameter);

    }

    @Override
    public void postConstruct() {
        lastStarted = new Date();
    }

}

Notice, the destruction function is not called by android natively on exit. You need to wire it explicitly atm.

Finish : Demonstrating the lifecycle


To demonstrate bean container shutdown and startup, I modified the button callback :

package org.baracus;

// ...

public class HelloAndroidActivity extends Activity {

    // ...

    public void onButtonTestClicked(View v) {
        customerService.testService();
        bankAccountService.createAndOrDumpAccount();
        ApplicationContext.destroy(true);
        ApplicationContext.initApplicationContext();
    }

}


That's all. Now how are able to manage bean lifecycles with the container :)

Next Tutorial : Advanced persistence mapping

Keine Kommentare:

Kommentar veröffentlichen