Dependency Injection in Xtext with Google Guice

In Xtext, there are many Java classes which implement logic, behavior, or supply configuration. These classes implement Java interfaces and are typically only supposed to be accessed through these interfaces, which makes their implementations interchangeable. This is where Xtext utilizes Google Guice:

Services

The most parts of Xtext are implemented as services. A service is an object which implements a certain interface and which is instantiated and provided by Guice. Nearly every concept of the Xtext framework can be understood as this sort of service: The XtextEditor, the XtextResource, the IParser and even fine grained concepts as the PrefixMatcher for the content assist are configured and provided by Guice.

Xtext ships with generic default implementations for most or the services or uses generator fragments to automatically generate service implementations for a grammar. Thereby, Xtext strives to provide meaningful implementations out of the box and to allow customization wherever needed. Developers are encouraged to subclass existing services and configure them for their languages in their modules.

When Guice instantiates an object, it also supplies this instance with all its dependent services. All a service does is to request “some implementation for a certain interface” using the @Inject-annotation. Based on the modules configuration Guice decides which class to instantiate or which object to reuse.

For example, Guice can automatically initialize member variables with the needed services.


public class MyLanguageLinker extends Linker {

  @Inject
  private IScopeProvider scopeProvider;

  @Inject(optional=true)
  private IXtext2EcorePostProcessor postProcessor;
  
  (...)
}

Furthermore, Guice can pass the needed services as method parameters or event into a constructor call.


public class MyLanguageGrammarAccess implements IGrammarAccess {

  private final GrammarProvider grammarProvider;

  private TerminalsGrammarAccess gaTerminals;

  @Inject
  public MyLanguageGrammarAccess(GrammarProvider grammarProvider,
    TerminalsGrammarAccess gaTerminals) {
    this.grammarProvider = grammarProvider;
    this.gaTerminals = gaTerminals;
  }

  (...)
}

For further details, please refer to the Google Guice Documentation

Modules

The configuration of services for a language built with Xtext is done via modules:

In total, this leads to five modules for a typical Xtext Language. They are visualized in the image below. The image is further explained in the following subsections.

Modules intended for customization

When the generator runs the first time, it creates two modules named <MyLanguage>RuntimeModule and <MyLanguage>UIModule. They are placed in the language’s root-package in the src/-folder of the language’s runtime-project and the language’s UI-project. Both are initially empty and will never be overwritten by the generator. They are intended for customization. By default, they extend a generated module.

Generated Modules

The fully generated modules are called Abstract<MyLanguage>RuntimeModule and Abstract<MyLanguage>UiModule respectively. They contain all components which have been generated specifically for the language at hand. What goes into these modules depends on the fragments you use in the generator.

Note: This modules are replaced on every subsequent execution of the generator. Don’t put any custom code into them. Manually written code has go into the concrete subclasses.

Default Module

Finally the fully generated modules extend the DefaultRuntimeModule, which contains all the default configuration. The default configuration consists of all components for which we have generic default implementations.

Changing Configuration

We use the primary modules ( <MyLanguage>RuntimeModule and <MyLanguage>UiModule) in order to change the configuration. These classes are initially empty and have been generated to allow customization.

In order to provide a simple and convenient way, the default module extends the AbstractGenericModule. It does not provide any bindings itself but comes up with a convenient and declarative way to specify mappings. The default API provided by Guice is based on fluent API and a builder pattern. This is also very readable but does not allow submodules to change any of the bindings. The AbstractGenericModule allows to declare and override bindings in all subclasses like this:

public Class<? extends IFooService> bindIFooService() {
     return MyFooServiceImpl.class;
}

Such a method will be interpreted as a binding from IFooService to MyFooServiceImpl.class. Note that you simply have to override a method from a super class (e.g. from the generated or default module) in order to change the respective binding. For example, in the picture above, the DefaultRuntimeModule configures the IFormatter to be implemented by the OneWhitespaceFormatter. The AbstractMyLanguageModule overrides this binding by mapping the IFormatter to MyLanguageFormatter. The type to type binding will create a new instance of the given target type for each dependency. If you want to make it a singleton and thereby ensure only one instance to be created and reused for all dependencies you can add the following annotation:

@SingletonBinding
public Class<? extends IFooService> bindIFooService() {
  return MyFooServiceImpl.class;
}

In addition if the creation of the type causes any necessary side effects, so you want it to be instantiated eagerly, you can set the eager property to true. This is shown in the following snippet:

@SingletonBinding(eager=true)
public Class<? extends IFooService> bindIFooService() {
  return MyFooServiceImpl.class;
}

One more way of specify a binding is currently supported: If you need to control the way, the instance is created, you can declare a type to object mapping:

public IFooService bindIFooService() {
  return new MyFooServiceImpl();
}

Note that, although this is a convenient and simple way, you have of course also the full power of Guice, i.e. you can override the Guice method void configure(Binder) and use the afore mentioned fluent API to do whatever you want.