Sinobu

Concept

In Sinobu, lifestyle refers to the way an object is created and managed, corresponding to the scope in terms of DI containers such as SpringFramework and Guice, but without the process of registering with the container or destroying the object.

Creating an object

In Java, it is common to use the new operator on the constructor to create a new object. In many cases, this is sufficient, but in the following situations, it is a bit insufficient.

  • To manage the number of objects to be created.
  • To create objects associated with a specific context.
  • To generate objects with complex dependencies.
  • The type of the object to be generated is not statically determined.

While DI containers such as SpringFramework or Guice are commonly used to deal with such problems, Sinobu comes with its own very simple DI container. The following code shows the creation of an object using DI container.

void createObject() {
    class Tweet {
    }

    Tweet one = I.make(Tweet.class);
    assert one != null;
}

As you can see from the above code, there is no actual container object; Sinobu has only one global container in the JVM, and that object cannot be accessed directly. In order to create an object from a container, we need to call I#make(Class).

Defining lifestyle

In order to define a lifestyle, you need to implement Lifestyle interface. This interface is essentially equivalent to Callable. It is called when container requests the specific type. It makes the following 3 decisions:

  1. Which class to instantiate actually.
  2. How to instantiate it.
  3. How to manage the instances.

Sinobu defines two lifestyles that are frequently used. One is the prototype pattern and the other is the singleton pattern.

Prototype

The default lifestyle is Prototype, it creates a new instance on demand. This is applied automatically and you have to configure nothing.

public void prototype() {
    class Tweet {
    }

    Tweet one = I.make(Tweet.class);
    Tweet other = I.make(Tweet.class);
    assert one != other; // two different instances
}

Singleton

The other is the singleton lifestyle, which keeps a single instance in the JVM and always returns it. This time, the lifestyle is applied with annotations when defining the class.

public void singleton() {
    @Managed(Singleton.class)
    class Tweet {
    }

    Tweet one = I.make(Tweet.class);
    Tweet other = I.make(Tweet.class);
    assert one == other; // same instance
}

Custom lifestyle

You can also implement lifestyles tied to specific contexts. Custom class requires to implement the Lifestyle interface and receive the requested type in the constructor. I'm using I#prototype(Class) here to make Dependency Injection work, but you can use any instantiation technique.

class PerThread<T> implements Lifestyle<T> {
    private final ThreadLocal<T> local;

    PerThread(Class<T> requestedType) {
        // use sinobu's default instance builder
        Lifestyle<T> prototype = I.prototype(requestedType);

        // use ThreadLocal as contextual instance holder
        local = ThreadLocal.withInitial(prototype::get);
    }

    public T call() throws Exception {
        return local.get();
    }
}
public void perThread() {
    @Managed(PerThread.class)
    class User {
    }

    // create contextual user
    User user1 = I.make(User.class);
    User user2 = I.make(User.class);
    assert user1 == user2; // same

    new Thread(() -> {
        User user3 = I.make(User.class);
        assert user1 != user3; // different
    }).start();
}

Builtin lifestyles

Sinobu has built-in defined lifestyles for specific types.

Applying lifestyle

To apply a non-prototype lifestyle, you need to configure each class individually. There are two ways to do this.

Use Managed annotation

One is to use Managed annotation. This method is useful if you want to apply lifestyle to classes that are under your control.

@Managed(Singleton.class)
class UnderYourControl {
}

Use Lifestyle extension

Another is to load the Lifestyle implementation. Sinobu has a wide variety of extension points, and Lifestyle is one of them. This method is useful if you want to apply lifestyle to classes that are not under your control.

class GlobalThreadPool implements Lifestyle<Executor> {

    private static final Executor pool = Executors.newCachedThreadPool();

    public Executor call() throws Exception {
        return pool;
    }
}
public void loadLifestyle() {
    I.load(GlobalThreadPool.class);
}
IntroductionDependency Injection