Skip to content
This repository was archived by the owner on Jul 4, 2022. It is now read-only.

How to use the Dependency Injection

Leonhard edited this page Oct 29, 2020 · 6 revisions

Dependency Injection (DI) in SimplixCore using Guice

SimplixCore utilizes enterprise-level development techniques powered by Google to ensure fast development by reducing the need for boilerplate code. If you want to learn more about the used Dependency Injection framework Guice, you can check out the wiki.

What is Dependency Injection?

According to Wikipedia:

In software engineering, dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies. In the typical "using" relationship[1] the receiving object is called a client and the passed (that is, "injected") object is called a service. The code that passes the service to the client can be many kinds of things and is called the injector. Instead of the client specifying which service it will use, the injector tells the client what service to use. The "injection" refers to the passing of a dependency (a service) into the object (a client) that would use it.

In SimplixCore, we have so called components. Components can serve as client and service at the same time. This means that every component can require other components as dependencies.

The Model

This is the DI stack of SimplixCore

This image shows the Dependency Injection model of a SimplixApplication. Continue reading with this image in mind.

The Application Injector

The injector is the heart of Dependency Injection. It holds the information about every configured module and its components. The injector is used to inject constructor parameters or get instances from configured bindings. Every application has its own injector.

Modules

Modules are used to divide components from specific aspects from each other. For example, having a BungeeCordModule and a SpigotModule ensures that components bound to the SpigotModule are only usable in contexts where the SpigotModule is available.

Bindings

You can configure own bindings to a module. A binding describes the relation between a Key and its Provider. See https://github.com/google/guice/wiki/Bindings for further information on that.

Component Interceptors

The class AbstractSimplixModule serves some additional AOP features like intercepting detected Components. You can register a ComponentInterceptor during the initialization of the module:

@Slf4j  
@ApplicationModule("Example")
public final class ExampleModule extends AbstractSimplixModule {  
  
  {
    registerComponentInterceptor(Listener.class, listener -> {  
      log.info("A wild listener is passing by: " + listener.getClass().getName());  
      // Do listener registration and stuff  
    });  
  }
  
}

This interceptor will print every subclass of Listener that was annotated with @Component and was bound to ExampleModule into the log.

Annotations

Annotations are used for further reducing boilerplate code.

@Component

Any class that was marked with this annotation is a component. It can require other dependencies and or can be required from other dependencies. If this class requires other dependencies, the constructor must be annotated with @Inject. For example:

@Component(ExampleModule.class)  
public final class CarCacheHandler {  
  
  private final CarSqlHandler carSqlHandler;
  
  private final LoadingCache<Integer, Car> carCache =  
    CacheBuilder.newBuilder().build(new CacheLoader<Integer, Car>() {  
      @Override  
      public Car load(@NotNull Integer integer) {  
        return carSqlHandler.loadById(integer);  
      }
    });  
  
  @Inject  
  public CarCacheHandler(CarSqlHandler carSqlHandler) {  
    this.carSqlHandler = carSqlHandler;  
  }
  
  public Car getCar(int id) {  
    return carCache.getUnchecked(id);  
  }

}

This class requires an instance of CarSqlHandler to be constructed. The class CarSqlHandler is also annoted with @Component so it can be injected by the injector. Other components can now require this class to get some cars from the database, in this example.

@AlwaysConstruct

Normally, Components are lazy constructed. When there is no need for a specific component to be injected, there will also be no instance of this component. A component annotated with @AlwaysConstruct will always be constructed during application installation.

Notice: ComponentInterceptors will also construct their intercepted components.

@RequireModules

Only usable in combination with @SimplixApplication. This class makes it possible to create module instances using a specified Supplier<AbstractSimplixModule[]>. This is necessary when your AbstractSimplixModule extension requires constructor parameters which would make the automatic registration using @ApplicationModule impossible.

@ScanComponents

Only usable in combination with @SimplixApplication. This advises the SimplixInstaller to look at specified locations for components and modules. Typically this points to the common base package of your application. For example: @ScanComponents("dev.simplix.core")

@ApplicationModule

This marks a class for automatic Module detection during module scanning. The annotated module needs to have an accessible default constructor. Otherwise please register the module using @RequireModules or using the register method of SimplixInstaller.

@Private

SimplixApplications can depend on each other. An application called A which is depending on an application called B can access every binding of application B. In some cases (e.g. localization) you won't want to let other applications access some bindings of your application. In that case, you can annotate that binding with @Private.

Getting Components without injection.

When you want to utilize the features of dependency injection, go for it. But don't construct components using their constructor. This will result in a new instance which is different from the one Guice is using for other components. Use something like that instead:

CarSqlHandler sqlHandler = SimplixInstaller.instance().injector(MyApplication.class).getInstance(CarSqlHandler.class);

And this isn't beautiful nor conventional. Better you design your application with DI in mind. Your goal to achieve is: Everything is a component.

Clone this wiki locally