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:
- Which class to instantiate actually.
- How to instantiate it.
- 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);
}