1. Introduction

xtend-ioc is a compile-time inversion of control framework for Xtend.
Its main features are

  • component instantiation and life-cycle management,

  • dependency injection,

  • event dispatching between component instances and

  • aspect-oriented programming using method interceptors.

1.1. Xtend

xtend-ioc is built on Xtend:

Xtend is a statically-typed programming language which translates to comprehensible Java source code. Syntactically and semantically Xtend has its roots in the Java programming language but improves on many aspects.

— by the Xtend website

Xtend has lots of useful features compared to Java, and it has fairly good IDE support. Its advanced compile-time metaprogramming facilities make it the perfect basis of xtend-ioc.
xtend-ioc does not support Java directly, only indirectly via Xtend.

Although this documentation sometimes refers to "Java classes", in most cases this means "Java classes translated from Xtend".
Similarly, "Java objects" mean instances of "Java classes translated from Xtend".

1.2. Inversion of control and dependency injection

In software engineering, inversion of control (IoC) describes a design in which custom-written portions of a computer program receive the flow of control from a generic, reusable library. A software architecture with this design inverts control as compared to traditional procedural programming: in traditional programming, the custom code that expresses the purpose of the program calls into reusable libraries to take care of generic tasks, but with inversion of control, it is the reusable code that calls into the custom, or task-specific, code.

In this definition xtend-ioc is the "generic, reusable library" that

creates and manages instances of classes

instead of the classes would be instantiated using the new operator in user code,

injects dependencies into objects

instead of each object would look up them manually, and

dispatches events between objects

instead of calling methods directly on other objects.

1.3. Compile-time vs runtime IOC frameworks

xtend-ioc is a purely compile-time IOC framework.
It means that all validation and code generation is performed during the Xtend to Java source code translation phase.
This design decision has major advantages like

  • most errors are detected in compile-time

  • the generated Java code is completely static so e.g. GWT is supported

Although there are disadvantages as well compared to runtime IOC frameworks, for example component lookup using runtime classpath scanning cannot be supported.
Note that a simple compile-time component scanning is supported.

2. Status

xtend-ioc is in early-access phase, it is not feature-complete yet and is not recommended for production.
Although all features and examples presented in this documentation are working (the examples are unit tests indeed), the framework source code still needs some cleanup…​

Source code is available on GitHub, compiled binaries are uploaded to Central Repository.

3. Usage

Eclipse with the Xtend plugin is the recommended development environment for xtend-ioc applications.
After installing Eclipse and the Xtend plugin, follow these steps to create an xtend-ioc application:

  • Create a new Maven project by selecting "File→New→Project.." and "Maven Project" (a "simple project" is sufficient, no archetype is required).

  • Add Xtext nature to the project (by selecting "Configure → Add Xtext Nature").

  • Add the dependecy com.erinors:xtend-ioc-core:0.2.1 to the project.

  • Create a new Xtend class and start implementing your components and modules.
    See an example to quickly learn the basics!

4. Building blocks

The main building blocks of xtend-ioc are:

  • A component is a definition that specifies how instances of it should be created and managed by modules.

  • A component instance is a Java object, instance of a component.

  • A module is a definition that specifies the components contained by it.

  • A module instance manages the life-cycle of its contained component instances, provides additional services for them, and may optionally provide external access to them.

Let’s see an example just to taste the syntax:

interface HelloService { (1)
	def String sayHello(String name)
}

@Component (2)
class EnglishHelloServiceImpl implements HelloService {
	override sayHello(String name) '''Hello «name»!'''
}

@Component
class HungarianHelloServiceImpl implements HelloService {
	override sayHello(String name) '''Szia «name»!'''
}

@Component
class AnotherComponent {
	@Inject
	public EnglishHelloServiceImpl englishHelloService (3)
}

@Module( (4)
components=#[EnglishHelloServiceImpl, HungarianHelloServiceImpl, AnotherComponent] (5)
)
interface ChattyModule {
	def EnglishHelloServiceImpl englishHelloService() (6)

	def HungarianHelloServiceImpl hungarianHelloService() (7)

	def List<? extends HelloService> helloServices() (8)

	def AnotherComponent anotherComponent()
}

class ChattyModuleTest {
	@Test
	def void test() {
		val module = ChattyModule.Peer.initialize (9)
		assertEquals("Hello Jeff!", module.englishHelloService.sayHello("Jeff")) (10)
		assertEquals("Szia Jeff!", module.hungarianHelloService.sayHello("Jeff")) (11)
		assertEquals(2, module.helloServices.size) (12)
		assertTrue(
			module.englishHelloService ===
			module.anotherComponent.englishHelloService (13)
		)
	}
}
1 Define a service interface to "say hello".
2 Annotate classes with @Component to turn them to components.
3 Another component with @Inject-ed depedency
4 Annotate an interface with @Module to turn it to a module.
5 Specify the components contained in the module.
6 Declare a method for accessing the EnglishHelloServiceImpl component.
7 Declare a method for accessing the HungarianHelloServiceImpl component.
8 Declare a method for accessing all components implementing HelloService
9 Initialize the module, ie. instantiate it.
10 Say hello in English.
11 Say hello in Hungarian.
12 Count the components implementing HelloService - not surprisingly the result is 2.
13 By default components are "singletons" which means that all references point to the same component instance.

4.1. Components

There are two ways to define a component:

4.1.1. @Component / component classes

A Java class is a component class if

  • it is annotated with @Component and

  • it has a valid component constructor, that is

    • either the class has exactly one constructor annotated with @Inject

    • or none of its constructors is annotated with @Inject and a no-args constructor exits (even the default constructor or an explicitly defined one).

When a new instance of a component class is requested then it is instantiated by using its component constructor.

Component classes may have superclasses. If a component's superclass is itself a component class then dependency injection is performed as expected.

Components should be focused in functionality.

4.1.2. @Qualifier / qualifiers

Programming against interfaces is considered a good practice therefore components should be referenced by interfaces instead of concrete classes. When multiple component classes implement the same interface, the component classes can be identified by labeling them with qualifiers.

A qualifier is an annotation marked with @Qualifier:

@Qualifier
annotation English {}

Let’s rewrite our first example using qualifiers and interface references:

interface HelloService {
	def String sayHello(String name)
}

@Qualifier
annotation English { (1)
}

@Qualifier
annotation Hungarian { (2)
}

@Component
@English (3)
class EnglishHelloServiceImpl implements HelloService {
	override sayHello(String name) '''Hello «name»!'''
}

@Component
@Hungarian (4)
class HungarianHelloServiceImpl implements HelloService {
	override sayHello(String name) '''Szia «name»!'''
}

@Component
class AnotherComponent {
	@Inject
	@English
	public HelloService englishHelloService (5)
}

@Module(
	components=#[EnglishHelloServiceImpl, HungarianHelloServiceImpl, AnotherComponent]
)
interface ChattyModule {
	@English (6)
	def HelloService englishHelloService()

	@Hungarian (7)
	def HelloService hungarianHelloService()

	def List<? extends HelloService> helloServices()

	def AnotherComponent anotherComponent()
}

class ChattyModuleTest {
	@Test
	def void test() {
		val module = ChattyModule.Peer.initialize
		assertEquals("Hello Jeff!", module.englishHelloService.sayHello("Jeff"))
		assertEquals("Szia Jeff!", module.hungarianHelloService.sayHello("Jeff"))
		assertEquals(2, module.helloServices.size)
		assertTrue(
			module.englishHelloService ===
			module.anotherComponent.englishHelloService
		)
	}
}
1 Define qualifier @English.
2 Define qualifier @Hungarian.
3 Qualify EnglishHelloServiceImpl with @English.
4 Qualify HungarianHelloServiceImpl with @Hungarian.
5 AnotherComponent depends on a component implementing HelloService and qualified with @English (that will be resolved as EnglishHelloServiceImpl).
6 Declare a method for accessing the component implementing HelloService and qualified with @English (that will be resolved as EnglishHelloServiceImpl).
7 Declare a method for accessing the component implementing HelloService and qualified with @Hungarian (that will be resolved as HungarianHelloServiceImpl).

Of course a component may have multiple qualifiers like:

@Qualifier
annotation ThreadSafe {}

@Qualifier
annotation Production {}

@Component
@ThreadSafe
@Production
class SomeComponent {}

Qualifiers may have attributes of any supported type:

  • primitive value

  • String

  • enum

  • class literal

  • annotation (any annotation is supported, even annotations not marked with @Qualifier)

  • array of any of the above types

Example:

@Qualifier
annotation Language {
  String value
}

@Component
@Language("english")
class SomeComponent {}

4.1.3. Type signature of components

Every component has a type signature (cT, cQ) that is composed of

  • cT: a Java type and

  • cQ: a set of qualifiers.

In case of a component class these are defined as

  • cT: if specified explicitly then @Component.type otherwise the Java type of the component class declaration and

  • cQ: the set of qualifiers the component class is annotated with.

Components can be referenced by a component reference rT, rQ where

  • rT: a Java type

  • rQ: a set of qualifiers

A component with (cT, cQ) type signature satisfies a component reference (rT, rQ) if

  • cT is assignable to rT (using the typing rules of the Java language) and

  • the set cQ contains all elements of set rQ.

The operation of finding all components in a module satisfying a given component reference is called component reference resolution.
Component reference resolution may result in zero or more components.

The most common component reference is the injection point: a component reference defined by an Xtend field, method or parameter declaration.

4.1.4. @Inject / dependency injection

The most important feature of xtend-ioc is dependency injection i.e. the process of resolving a component's references to other components.
In practice this means the resolution of injection points.

There are two types of injection points in components:

  • Field: the component reference is defined by a field declaration of the component class

  • Constructor: the component references are defined by the parameter declarations of a component constructor

Different types of injection points canbe mixed in the same component.

In case of optional dependencies the injection point should be annotated with @NotRequired. If the component reference cannot be resolved then null is injected.

To ensure null-safety it is recommended to use indirect component references instead of directly injected component instances.
In this documentation @Inject refers to @com.erinors.ioc.shared.api.Inject.
But @javax.inject.Inject is supported as well and their behaviour is currently identical.
Field injection

Injected fields are marked with @Inject:

@Component
class SomeComponent {
  @Inject
  ReferenceToAnotherComponent anotherComponent
}

Component reference resolution is performed using

  • rT = type of the field declaration

  • rQ = qualifiers the field is annotated with

Constructor injection

Injected constructors are marked with @Inject:

@Component
class SomeComponent {
  @Inject
  new(AnotherComponent anotherComponent) {}
}

Component reference resolution is performed for each constructor parameter with

  • rT = type of the parameter declaration

  • rQ = qualifiers the parameter is annotated with

Indirect component references

Indirect component references are useful if

  • more than one instance of the component is required (this is useful for prototype scoped components)

  • instantiation of the referenced component should be delayed for some reason

Indirect component references are supported by injecting a component supplier:

interface Handler<T> {}

@Component
class AnotherComponent {
}

@Component
class TestComponent {
	@Inject (1)
	public Supplier<AnotherComponent> componentSupplier

	@Inject
	public AnotherComponent injectedComponent
}

@Module(components=#[AnotherComponent, TestComponent])
interface TestModule {
	def TestComponent testComponent()
}

class Example {
	@Test
	def void test()	{
		val module = TestModule.Peer.initialize
		val testComponent = module.testComponent
		assertTrue( (2)
			testComponent.injectedComponent == testComponent.componentSupplier.get
		)
	}
}
1 Inject supplier.
2 The directly injected and the indirectly supplied component instances are the same because AnotherComponent is singleton.
Currently only com.google.common.base.Supplier is supported as component supplier but there are plans to support others (eg. java.util.function.Supplier).
Indirect component references are supported by annotating the injection point with @NotRequired. In this case the supplier returns null.
References to multiple components

When a component reference is resolved to multiple components they can be injected as type List or Iterable:

interface Handler {
}

@Component
class IntegerHandler implements Handler {
}

@Component
class DoubleHandler implements Handler {
}

@Component
class TestComponent {
	@Inject
	public List<? extends Handler> handlers (1)
	@Inject
	public Iterable<Handler> handlers2 (2)
}

@Module(components=#[IntegerHandler, DoubleHandler, TestComponent])
interface TestModule {
	def IntegerHandler integerHandler()

	def DoubleHandler doubleHandler()

	def TestComponent testComponent()
}

class Example {
	@Test
	def void test() {
		val module = TestModule.Peer.initialize
		assertEquals(
			#{module.doubleHandler, module.integerHandler},
			module.testComponent.handlers.toSet
		)
		assertEquals(
			module.testComponent.handlers,
			module.testComponent.handlers2
		)
	}
}
1 Inject list of component references by type List<? extends Handler>.
2 Inject list of component references by type Iterable<Handler>.
Of course the component reference can be injected as a List even if it is resolved to only one component instance.
If no components are compatible with the component reference then an empty List would be injected.
This is valid only if injection point is annotated with @Optional otherwise a compilation error is raised.
Generic component references

Generic components can be referenced as expected:

interface Handler<T> {
}

@Component
class IntegerHandler implements Handler<Integer> {
}

@Component
class DoubleHandler implements Handler<Double> {
}

@Component
class TestComponent {
	@Inject
	public Handler<Integer> integerHandler

	@Inject
	public List<Handler<? extends Number>> numberHandlers (1)
}

@Module(components=#[IntegerHandler, DoubleHandler, TestComponent])
interface TestModule {
	def IntegerHandler integerHandler()

	def DoubleHandler doubleHandler()

	def TestComponent testComponent()
}

class Example {
	@Test
	def void test() {
		val module = TestModule.Peer.initialize
		assertTrue(module.integerHandler == module.testComponent.integerHandler)
		assertTrue(
			#{module.doubleHandler, module.integerHandler} == module.testComponent.numberHandlers.toSet
		)
	}
}
1 Both List<Handler<? extends Number>> and List<? extends Handler<? extends Number>> are valid. Iterable could be used as well instead of List.
But List<Handler<Number>> would not be valid because there is no component implementing Handler<Number> (note that because of Java typing rules, Handler<Integer> is not assignable to Handler<Number>, only to Handler<? extends Number>).

4.1.5. @Scope / component scope

By default every component has only one instance. When the component is referenced multiple times the same instance is returned always.
This behaviour is defined by the scope of the component which defines whether a new instance of the component should be created or an existing instance should be used when the component is referenced.
The built-in scopes are:

  • singleton and

  • prototype.

The scope of a component can be specified by the scope annotation.
If a component does not have an explicit scope annotation then its scope will be the default singleton scope.

Custom scopes can be added by implementing a ScopeManager.
@Singleton / singleton scope

Components with singleton scope have never more than one instance.
Multiple references to the same component are resolved to the same component instance.

@Component (1)
class TestComponent {
}

@Module(components=TestComponent)
interface TestModule {
	def TestComponent testComponent()
}

class Example {
	@Test
	def void test() {
		val module = TestModule.Peer.initialize
		val testComponent1 = module.testComponent
		val testComponent2 = module.testComponent
		assertTrue( (2)
			testComponent1 == testComponent2
		)
	}
}
1 Define singleton component.
2 All references to the component returns the same component instance.

This is the default scope so usually no scope annotation is specified on singleton components.
(So although there there is a scope annotation @Singleton, it is rarely used.)

@Prototype / prototype scope

Components annotated with @Prototype have prototype scope.
Each reference to a prototype scoped component resolves to a new instance of the component.

@Component
@Prototype (1)
class TestComponent {
}

@Module(components=TestComponent)
interface TestModule {
	def TestComponent testComponent()
}

class Example {
	@Test
	def void test() {
		val module = TestModule.Peer.initialize
		val testComponent1 = module.testComponent
		val testComponent2 = module.testComponent
		assertTrue( (2)
			testComponent1 != testComponent2
		)
	}
}
1 Define prototype component.
2 Each references to the component returns a new component instance.

4.1.6. @PostConstruct and @PreDestroy / lifecycle callbacks

Methods in component classes are lifecycle callbacks if they are annotated with lifecycle annotations:

Lifecycle annotation Description

@PostConstruct

The method is called after the given component instance is created and its dependencies are injected.

@PreDestroy

The method is called when the given component instance is in the process of being disposed.

The @PreDestroy lifecycle annotation is not supported by all scopes.
For example the singleton scope supports it but the prototype scope does not.
@PreDestroy methods are called by the scope manager before the component instance is being disposed.
In case of singleton components this happens only when their module is closed.

Example:

@Component (1)
class TestComponent {
	@Accessors(PUBLIC_GETTER)
	static String status = "uninitialized" (2)

	@PostConstruct (3)
	def void initialize() {
		status = "initialized"
	}

	@PreDestroy (4)
	def void close() {
		status = "closed"
	}
}

@Module(components=TestComponent)
interface TestModule {
	def TestComponent testComponent()
}

class Example {
	@Test
	def void test() {
		val module = TestModule.Peer.initialize (5)
		assertEquals("uninitialized", TestComponent.status) (6)
		module.testComponent (7)
		assertEquals("initialized", TestComponent.status)
		module.close (8)
		assertEquals("closed", TestComponent.status)
	}
}
1 Declare TestComponent of scope singleton.
(Adding @Prototype to this class would result in a compile-time error because the prototype scope does not support @PreDestroy methods.)
2 Declare static status field (the unit test will use it).
3 Declare @PostConstruct callback method.
4 Declare @PreDestroy callback method.
5 Initialize module.
6 The component is not initialized yet (because it is not eager).
7 The component is intialized when first referenced (i.e. after the single instance of it is created), @PostConstruct callbacks are called in this phase.
8 The component is unintialized when the module is closed, @PreDestroy callbacks are called in this phase.

4.1.7. @Eager / eager components

By default components are instantiated only when they are first referenced.
To force the eager instantiation of a component it should be annotated with @Eager.

@Component (1)
class LazyComponent {
	@Accessors(PUBLIC_GETTER)
	static String status = "uninitialized"

	@PostConstruct
	def void initialize() {
		status = "initialized"
	}
}

@Component
@Eager (2)
class EagerComponent {
	@Accessors(PUBLIC_GETTER)
	static String status = "uninitialized"

	@PostConstruct
	def void initialize() {
		status = "initialized"
	}
}

@Module(components=#[LazyComponent, EagerComponent])
interface TestModule {
	def LazyComponent lazyComponent()

	def EagerComponent eagerComponent()
}

class Example {
	@Test
	def void test() {
		val module = TestModule.Peer.initialize
		assertEquals( (3)
			"uninitialized",
			LazyComponent.status
		)
		assertEquals( (4)
			"initialized",
			EagerComponent.status
		)
		module.lazyComponent (5)
		assertEquals( (6)
			"initialized",
			LazyComponent.status
		)
	}
}
1 Components are lazy by default.
2 Define eager component.
3 The lazy component is not instantiated when the module is created.
4 The eager component is instantiated right after the module is created.
5 Force instantiation of the lazy component.
6 The lazy component instance is now initialized.
@Eager is usually used on singleton components.
The intended initialization order of eager components can be specified by @Priority.
If a prototype scoped component is marked as @Eager then right after the component instance is created, it is immediately dereferenced by the module instance.

4.1.8. @Provider / components instantiated by user code

There are cases when we would like to use instances of a Java class as components but the class does not comply with the requirements of component classes.
In most cases it is possible to create a subclass with a valid component constructor but usually it is not practical. Besides if the class is final - like String (yes, any Java object can be a component, even a string) - then there is no way to turn it into a component class.

To overcome these limitations, component classes can provide other component instances by provider methods.

Provider methods must have an explicit return type, type inference is not supported.
Simple component providers

Simple component providers are no-args instance methods defined in normal components and annotated with @Provider.

class ProvidedComponent { (1)
}

@Component
class MainComponent {
  @Provider
  def ProvidedComponent provider() {
    new ProvidedComponent() (2)
  }
}
1 The provided component’s class is not annotated with @Component, it’s a simple POJO.
2 Create a new instance of the provided component using the new operator.

Altough in this case the provided component is instantiated directly using the new operator, its lifecycle is managed by the module, and similar rules apply to them as to normal components:

There are important differences as well:

  • A provided component's type signature is composed of

    • the return type of the provider method and

    • the qualifiers statically declared on the provider method and the parameterized qualifiers supported by the provider method (see Parameterized component providers).

  • lifecycle annotations are not supported.

  • They usually don’t have @Inject-ed dependencies but use the injected dependencies of the enclosing component.
    (They may have injected dependencies like any POJOs but the resulting code is more readable if the dependecies are passed explicitly to the constructor of the implementing class.)

Parameterized component providers

Parameterized component providers are very similar to simple component providers, the only difference is that they can provide component instances for multiple qualifiers.

Parameterized component providers is an experimental feature, use it at your own risk.
As all features of xtend-ioc, parameterized component providers is a compile-time feature.
@Qualifier (1)
annotation ConfigurationValue {
	String value
}

@Component
class ProviderComponent {
	Properties configuration

	@PostConstruct
	def void initialize() {
		// In a real provider the configuration would be loaded from a file
		configuration = new Properties
		configuration.setProperty("a", "A")
		configuration.setProperty("b", "B")
	}

	@Provider( (2)
	parameterizedQualifiers=@ParameterizedQualifier(qualifier=ConfigurationValue, (3)
	attributeName="value", (4)
	parameterName="configurationName" (5)
	))
	def String configurationValueProvider(String configurationName) {
		return configuration.getProperty(configurationName)
	}
}

@Component (6)
class TestComponent {
	@Inject
	@ConfigurationValue("a")
	public String a

	@Inject
	@ConfigurationValue("b")
	public String b
}

@Module(components=#[ProviderComponent, TestComponent])
interface TestModule {
	def TestComponent testComponent()
}

class Example {
	@Test
	def void test() {
		val module = TestModule.Peer.initialize
		val testComponent = module.testComponent
		assertEquals("A", testComponent.a) (7)
		assertEquals("B", testComponent.b)
	}
}
1 Declare qualifier annotation with attributes.
2 Declare parameterized provider.
3 Specify the qualifier annotation supported by the provider.
4 Specify the annotation attribute name.
5 Specify the method parameter name the annotation attribute value is mapped to.
6 Declare component with injected fields.
7 Test injected values.

4.2. Modules

4.2.1. @Module / defining modules

A module is defined by a Java interface annotated with @Module.
The @Module annotation defines the component classes managed by the module

  • explicitly by listing the component classes using the components attribute and/or

  • implicitly by specifying the attribute componentScanClasses and/or componentImporters.

Component importers specify the components to be imported by using @ImportComponents.

Component scan attempts to find components recursively in the package of each listed class.

Component scan example:

interface SomeInterface {
}

@Component
class Component1 implements SomeInterface {
}

@Component
class Component2 implements SomeInterface {
}

@Module(componentScanClasses=TestModule) (1)
interface TestModule {
	def List<SomeInterface> instances()
}

class Example {
	@Test
	def void test() {
		val module = TestModule.Peer.initialize
		assertEquals(2, module.instances.size) (2)
	}
}
1 Define component scan root packages.
2 Both Component1 and Component2 is found by component scanning.
Component scan is an experimental feature, use it at your own risk.

4.2.2. Module inheritance

A module can inherit other modules by extending the their interface.
When module M1 inherits module M2 then all components contained by M2 will be managed by M1 as well. This rule is recursive.

@Component
class TestComponent {
}

@Module(components=TestComponent)
interface ParentModule {
	def TestComponent testComponent()
}

@Module (1)
interface TestModule extends ParentModule {
}

class Example {
	@Test
	def void test() {
		assertNotNull( (2)
			TestModule.Peer.initialize.testComponent
		)
	}
}
1 TestModule inherits ParentModule.
2 The inherited component is available in TestModule.

4.2.3. Module lifecycle / Singleton modules

A module must be instantiated before it can be used. Only non-abstract modules can be instantiated.

There are two types of modules specified by @Module.singleton:

  • Singleton: the module has exactly one instance.
    This singleton instance of the module ModuleInterface can be instantiated by calling ModuleInterface.Peer.initialize().
    The singleton instance is available by calling ModuleInterface.Peer.get().

  • Non-singleton: the module may have multiple instances, a new instance of the module ModuleInterface can be created by calling ModuleInterface.Peer.constructInstance().

In case of inheritance of singleton modules all modules in the inheritance chain share the same module instance:

@Component
class TestComponent {
}

@Module(components=TestComponent)
interface ParentModule {
	def TestComponent testComponent()
}

@Module (1)
interface TestModule extends ParentModule {
}

class Example {
	@Test
	def void test() {
		TestModule.Peer.initialize (2)
		assertTrue(TestModule.Peer.get === ParentModule.Peer.get) (3)
		assertTrue( (4)
			TestModule.Peer.get.testComponent ===
			ParentModule.Peer.get.testComponent
		)
	}
}
1 TestModule inherits ParentModule.
2 Initialize TestModule.
3 Both TestModule and ParentModule share the same runtime instance.
4 TestComponent is available from both module instances.

When the module instance is not needed anymore (e.g. on application shutdown) it should be closed by calling ModuleInterface.Peer.close().
During the close operation predestroy methods are called on the corresponding component instances.

4.2.4. Abstract and non-abstract modules

All module is non-abstract by default that is all component referencess must be resolvable, otherwise a compilation error is raised.

If it is known that not all component referencess are available (on purpose) then the module must be explicitly marked as abstract by specifying the @Module(isAbstract=true).

interface SomeService {
}

@Component
class TestComponent {
	@Inject
	public SomeService someService
}

@Module(components=TestComponent, isAbstract=true) (1)
interface ParentModule {
	def TestComponent testComponent()
}

@Component
class SomeServiceComponent implements SomeService {
}

@Module(components=SomeServiceComponent)
interface TestModule extends ParentModule {
	def SomeService someService()
}

class Example {
	@Test
	def void test() {
		// Compile-time error: ParentModule.Peer.initialize() (2)
		val module = TestModule.Peer.initialize
		assertTrue(TestModule.Peer.get === ParentModule.Peer.get) (3)
		assertTrue(module.testComponent.someService === module.someService) (4)
	}
}
1 ParentModule is declared as abstract because the TestComponent.someService component reference is not resolvable.
2 ParentModule is abstract therefore no initialize() method is available on it.
3 Both TestModule and ParentModule share the same runtime instance. There is no difference compared to non-abstract modules.
4 Dependency injection works between modules as expected.

4.2.5. Module-level component references

Module interfaces may declare component references:

  • rT: the return type of the method declaration

  • rQ: the qualifiers the method declaration is annotated with

These methods can be called on the module instance from "external" code:

interface SomeInterface {
}

@Component
class TestComponent implements SomeInterface {
}

@Module(components=TestComponent)
interface TestModule { (1)
	def TestComponent testComponent()

	def SomeInterface someInterface()

	def Supplier<SomeInterface> someInterfaceSupplier()
}

class Example {
	@Test
	def void test() { (2)
		val module = TestModule.Peer.initialize
		assertTrue(module.testComponent === module.someInterface)
		assertTrue(module.testComponent === module.someInterfaceSupplier.get)
	}
}
1 Declare module with module-level component references.
2 All declared component references refer to the same singleton TestComponent instance.

4.2.6. The relation of modules and component classes

The same component class can be contained by multiple modules.
In this case component dependency resolutions happens independently in each module, so it is possible that a component reference is resolved to different component instances.

@Component (1)
class TestComponent {
	@Inject
	public String value
}

@Component
class Provider1 {
	@Provider (2)
	def String provider() '''1'''
}

@Component
class Provider2 {
	@Provider (3)
	def String provider() '''2'''
}

@Module(components=#[TestComponent, Provider1]) (4)
interface TestModule1 {
	def TestComponent testComponent()
}

@Module(components=#[TestComponent, Provider2]) (5)
interface TestModule2 {
	def TestComponent testComponent()
}

class Example {
	@Test
	def void test() {
		val module1 = TestModule1.Peer.initialize
		val module2 = TestModule2.Peer.initialize
		val testComponent1 = module1.testComponent
		val testComponent2 = module2.testComponent
		assertEquals( (6)
			"1", testComponent1.value
		)
		assertEquals( (7)
			"2", testComponent2.value
		)
	}
}
1 Declare component TestComponent with injected dependency.
2 Declare component provider Provider1 with value "1".
3 Declare component provider Provider2 with value "2".
4 TestModule1 contains TestComponent and Provider1.
5 TestModule2 contains TestComponent and Provider2.
6 TestComponent’s dependency is resolved using `Provider1 in TestModule1.
7 TestComponent’s dependency is resolved using `Provider2 in TestModule2.
Because each module may have only one instance, inheritance is not supported with a common parent module.
I.e. if both TestModule1 and TestModule2 would inherit from the same parent module, only module1 could be initialized, the initialization of module2 would fail.

4.2.7. The component dependency graph

Each non-abstract module defines a dependency graph between components: it is a directed acyclic graph (DAG) where each node Cn is a component and each directed edge C1→`C2` corresponds to a direct component reference declared in C1 and resolved as C2. Note that indirect component references (by Supplier or Optional) are not present in the dependency graph.

If the dependency graph would not be a DAG i.e. it contains a directed cycle then a compile-time error is raised.

The following example does not compile, there are two compile-time errors:

  • Error message #1: Component reference cycle detected: Component1 → Component4 → Component3 → Component1 [E004]

  • Error message #2: Component reference cycle detected: Component1 → Component4 → Component3 → Component2 → Component1 [E004]

@Component
class Component1 {
	@Inject (1)
	Component4 component4

	def boolean someBusinessMethod() {
		component4.anotherBusinessMethod
	}
}

@Component
class Component2 {
	@Inject
	public Component1 component1
}

@Component
class Component3 {
	@Inject
	public Component1 component1

	@Inject
	public Component2 component2
}

@Component
class Component4 {
	@Inject
	public Component3 component3

	def anotherBusinessMethod() {
		true
	}
}

@Module(components=#[Component1, Component2, Component3, Component4])
interface TestModule
{
	def Component1 component1()
}
1 Injecting Component4 directly causes compile-time errors.

The cycle can be avoided by referencing one of the affected components indirectly using a Supplier:

@Component
class Component1 {
	// Direct injection is not allowed because it would cause two cycles in the dependency graph.
	// @Inject
	// Component4 component4

	@Inject (1)
	Supplier<Component4> component4Supplier

	def boolean someBusinessMethod() {
		!component4Supplier.get.anotherBusinessMethod (2)
	}
}

@Component
class Component2 {
	@Inject
	public Component1 component1
}

@Component
class Component3 {
	@Inject
	public Component1 component1

	@Inject
	public Component2 component2
}

@Component
class Component4 {
	@Inject
	public Component3 component3

	def anotherBusinessMethod() {
		true
	}
}

@Module(components=#[Component1, Component2, Component3, Component4])
interface TestModule
{
	def Component1 component1()
}

class AvoidDependencyGraphCycleTest {
	@Test
	def void test() {
		val module = TestModule.Peer.initialize
		assertFalse(module.component1.someBusinessMethod)
	}
}
1 Injection is done indirectly by Supplier<Component4>.
2 The component instance is referenced indirectly using Supplier.get().
Component providers reference their enclosing component directly.

4.2.8. Module report

If the module ModuleName is compiled without errors, two additional files are generated next to the module interface declaration:

  • ModuleName.dot: the dependency graph in GraphViz format

  • ModuleName.html: a HTML report that contains some information about the module like module properties, declared components, the dependency graph, etc.

E.g. the example above has the following graph:

G 1 Component1 [1] 2 Component2 [2] 2->1 3 Component3 [3] 3->1 3->2 4 Component4 [4] 4->3
The module report is a work in progress.
Currently graph rendering is very slow (done with viz.js).

4.2.9. GWT support

If the module ModuleInterface is annotated with @GwtEntryPoint then a special class named ModuleInterfaceEntryPoint is generated.
This class implements com.google.gwt.core.client.EntryPoint and the implementation instantiates the module.
Components with post-construct methods may be used to implement bootstrap code like UI construction.

4.3. Module-local event dispatching

Events allow simple communication between component instances in a module instance:

Event classes are simple POJOs.

Events can be fired by invoking the fire() method of a special built-in component type Event<EventClass> that can be injected as usual.

Events can be observed by declaring instance methods in components annotated with @EventObserver.
The observed event type can be specified implicitly by a method parameter or explicitly by @EventObserver.type (in the latter case the event object is not available in the method).

An observer method receives all subtypes of its declared event type by default. This can be changed by specifying the annotation attribute rejectSubtypes=true.

@Data (1)
class MessageEvent {
	String message
}

@Component
@Eager
@Priority(1) (2)
class EventSourceComponent {
	@Inject
	Event<MessageEvent> event (3)

	@PostConstruct
	def void componentInitialized() {
		fireEvent("C") (4)
	}

	@EventObserver(eventType=ModuleInitializedEvent) (5)
	def void moduleInitialize() {
		fireEvent("M") (6)
	}

	def void fireEvent(String message) {
		event.fire(new MessageEvent(message)) (7)
	}
}

@Component
@Eager
@Priority(0) (8)
class EventObserverComponent {
	val messages = newArrayList

	def getMessages() {
		messages.join(",")
	}

	@EventObserver (9)
	def void observe(MessageEvent event) {
		messages += event.message
	}
}

@Module(components=#[EventSourceComponent, EventObserverComponent])
interface TestModule {
	def EventSourceComponent source()

	def EventObserverComponent observer()
}

class Example {
	@Test
	def void test() {
		val module = TestModule.Peer.initialize (10)
		assertEquals("M", module.observer.messages) (11)
		module.source.fireEvent("1") (12)
		assertEquals("M,1", module.observer.messages) (13)
	}
}
1 Declare event class.
2 EventSourceComponent is eagerly initialized before EventObserverComponent.
3 Inject event.
4 Fire event "C" when the component instance is initialized.
5 Declare event observer method without parameters.
6 Fire event "M" when the module instance is initialized.
7 Event.fire() can be used to fire events.
8 EventObserverComponent is eagerly initialized after EventSourceComponent.
9 Declare event observer method receiving the event object.
10 EventSourceComponent fires event "C" during component initialization and "M" during module initialization.
11 EventObserverComponent received only "M" because it was initialized only after EventSourceComponent.
12 Fire event "1".
13 EventObserverComponent received message "1".

4.4. Interceptors / Aspect-oriented programming support

An interceptor is a class used to interpose in component method invocations.
Interceptors implement cross-cutting tasks, such as logging or auditing, that are separate from the business logic of the component.

An interceptor is defined by an interceptor annotation that is an annotation marked with @Interceptor.
An interceptor can be applied by annotating a method of a component with the interceptor annotation.

Let’s implement a method entry/exit logger interceptor:

@Interceptor(LoggedInvocationHandler) (1)
annotation Logged {
	String loggerName = "test" (2)
}

@Component (3)
class LoggedInvocationHandler implements InterceptorInvocationHandler<LoggedInvocationPointConfiguration> (4)
{
	@Inject (5)
	Logger logger

	override handle(
		LoggedInvocationPointConfiguration invocationPointConfiguration, (6)
		InvocationContext context (7)
	) {
		val loggerName = invocationPointConfiguration.loggerName (8)
		logger.log(loggerName, '''>> «invocationPointConfiguration.methodName»(«context.arguments.join(", ")»)''')
		try {
			val returned = context.proceed (9)
			logger.log(loggerName, '''<< «invocationPointConfiguration.methodName»: «returned»''')
			return returned (10)
		} catch (Exception e) {
			logger.log(loggerName, '''!! «invocationPointConfiguration.methodName»: «e.message»''')
			throw e
		}
	}
}

@Component (11)
class Logger {
	val buffer = new StringBuilder

	def void log(String loggerName, String message) {
		buffer.append(
		'''
			[«loggerName»] «message»
		''')
	}

	def String getBuffer() {
		buffer.toString
	}
}

@Component
class SomeComponent {
	@Logged (12)
	def int method(int value, boolean fail) {
		if (fail)
			throw new IllegalStateException("Failed!")
		else
			value * 2
	}
}

@Module(components=#[SomeComponent, Logger]) (13)
interface TestModule {
	def SomeComponent someComponent()

	def Logger logger()
}

class LoggedExample {
	@Test
	def void test() {
		val m = TestModule.Peer.initialize

		assertEquals(6, m.someComponent.method(3, false))

		try {
			m.someComponent.method(3, true)
			fail
		} catch (Exception e) {
		}

		assertEquals('''
			[test] >> method(3, false)
			[test] << method: 6
			[test] >> method(3, true)
			[test] !! method: Failed!
		'''.toString, m.logger.buffer)
	}
}
1 Interceptor annotations are annotated with @Interceptor, specifying the interceptor implementation class, the "interceptor invocation handler" (that is a class implementing InterceptorInvocationHandler).
2 Interceptor annotations may have attributes, the actual values of these are made available for the invocation handler.
3 Interceptor invocation handlers must be components.
4 The invocation handler class of @Logged must implement InterceptorInvocationHandler<LoggedInvocationPointConfiguration>, where the LoggedInvocationPointConfiguration is generated automatically.
5 Invocation handlers are normal components, they may e.g. inject dependencies.
6 The LoggedInvocationPointConfiguration gives access to the static properties of the invocation point, like the method name or the actual attribute values of the interceptor annotation.
7 The InvocationContext type gives access to the dynamic properties of the invocation point (like the actual method arguments), and allows calling the original method.
8 The actual annotation attribute values are available in the LoggedInvocationPointConfiguration class.
9 The interceptor can and usually should call the original method…​
10 …​ and return the return value of it.
11 A simple logger implementation.
12 The @Logged interceptor annotation is applied to the method.
13 The interceptors invocation handler component is added to a module automatically if an interceptor annotation requires it.

Another example is a simple method profiler:

@Interceptor(ProfiledInvocationHandler)
annotation Profiled {
}

@Component
class ProfiledInvocationHandler implements InterceptorInvocationHandler<ProfiledInvocationPointConfiguration> {
	@Inject
	Logger logger

	override handle(
		ProfiledInvocationPointConfiguration invocationPointConfiguration,
		InvocationContext context
	) {
		val start = System.currentTimeMillis
		logger.log("test", "Started profiling")
		try {
			context.proceed
		} finally {
			logger.log("test", '''Elapsed «System.currentTimeMillis-start»ms''')
		}
	}
}

@Component
class SomeComponent {
	@Profiled
	def void sleep(long waitMillis) {
		Thread.sleep(waitMillis) (2)
	}
}

@Module(components=#[SomeComponent, Logger])
interface TestModule {
	def SomeComponent someComponent()

	def Logger logger()
}

class ProfiledExample {
	@Test
	def void test() {
		val m = TestModule.Peer.initialize

		m.someComponent.sleep(100)
		m.someComponent.sleep(300)

		assertTrue(m.logger.buffer.matches('''
			\[test\] Started profiling
			\[test\] Elapsed 1..ms
			\[test\] Started profiling
			\[test\] Elapsed 3..ms
		'''))
	}
}

A method may have multiple interceptors, the interceptor handlers are called in the order of the interceptor annotations:

@Component
class SomeComponent {
	@Profiled
	@Logged
	def void sleep1(long waitMillis) { (1)
		Thread.sleep(waitMillis)
	}

	@Logged
	@Profiled
	def void sleep2(long waitMillis) { (2)
		Thread.sleep(waitMillis)
	}
}

@Module(components=#[SomeComponent, Logger])
interface TestModule {
	def SomeComponent someComponent()

	def Logger logger()
}

class MultipleInterceptorsExample {
	@Test
	def void testSleep1() {
		val m = TestModule.Peer.initialize

		m.someComponent.sleep1(100)

		assertTrue(m.logger.buffer.matches('''
			\[test\] Started profiling
			\[test\] >> sleep1\(100\)
			\[test\] << sleep1:
			\[test\] Elapsed 1..ms
		''')) (3)

		TestModule.Peer.close
	}

	@Test
	def void testSleep2() {
		val m = TestModule.Peer.initialize

		m.someComponent.sleep2(100)

		assertTrue(m.logger.buffer.matches('''
			\[test\] >> sleep2\(100\)
			\[test\] Started profiling
			\[test\] Elapsed 1.?.?ms
			\[test\] << sleep2:
		''')) (4)

		TestModule.Peer.close
	}
}
1 Method sleep1() has interceptors: @Profiled, @Logged
2 Method sleep2() has interceptors: @Logged, @Profiled
3 When calling sleep1(), invocation order is: ProfiledInvocationHandlerLoggedInvocationHandler → the method itself → LoggedInvocationHandlerProfiledInvocationHandler
4 When calling sleep2(), invocation order is: LoggedInvocationHandlerProfiledInvocationHandler → the method itself → ProfiledInvocationHandlerLoggedInvocationHandler

4.5. Dependency injection for non-components

Dependency injection is supported for simple POJOs annotated with @Injectable. The module specified in this annotation will be used for component reference resolution.
Both field and constructor injection is supported. Besides, the injection of individual constructor parameters is supported as well, see the example below.

Component reference resolution works differently for abstract and non-abstract modules:

  • For non-abstract modules it works the same as in case of normal components.

  • However resolution is limited for abstract modules: a component reference can be resolved only if there is a compatible module-level component reference declared.

Event dispatch is not supported for non-components.
@Component
class ValueProvider {
	@Provider
	def String value() '''a'''
}

@Module(components=ValueProvider)
interface TestModule { (1)
	def String value()
}

@Injectable(TestModule) (2)
class Injectable1 {
	@Inject
	public String value
}

@Data
@Injectable(TestModule) (3)
class Injectable2 {
	String value

	@Inject
	new(String value) {
		this.value = value
	}
}

@Data
@Injectable(TestModule) (4)
class Injectable3 {
	int number

	String value

	new(int number, @Inject String value) {
		this.number = number
		this.value = value
	}
}

class Example {
	@Test
	def void test() {
		TestModule.Peer.initialize (5)
		assertEquals("a", new Injectable1().value) (6)
		assertEquals("a", new Injectable2().value) (7)
		assertEquals("a", new Injectable3(1).value) (8)
	}
}
1 Declare module with provider component.
2 Declare injectable class Injectable1 with field injection.
3 Declare injectable class Injectable2 with constructor injection.
4 Declare injectable class Injectable3 with constructor parameter injection.
5 Explicit module initialization is required before the @Injectable class is instantiated.
6 Instantiate Injectable1.
7 Instantiate Injectable2. Note that because the constructor is injected therefore the object is created using a generated no-args constructor.
8 Instantiate Injectable3. Note that because the declared 2-args constructor is injected therefore the object is created using a generated 1-arg constructor.

5. FAQ

  1. What is the license for xtend-ioc?

    All sources code is licensed under MPL v2.
    All documentation is licensed under 80x15

  2. Is Java supported?

    No, only Xtend is supported.
    Altough it would be possible to implement most xtend-ioc features using Java Annotation Processing, it is not planned currently.

  3. Is Google Web Toolkit supported?

    Yes, the generated code is GWT compatible.

6. Appendix

6.1. Message codes

[E001] @Module is supported only for interface declarations.

Only interfaces may be annotated with @Module.

[E002] @Component is supported only for class declarations.

Only classes may be annotated with @Component.

[E003] @Injectable is supported only for class declarations.

Only classes may be annotated with @Injectable.

[E004] Component reference cycle detected: ComponentX → …​ → ComponentX

There is a cycle in the component dependency graph therefore the correct order of component instantiation cannot be resolved. See the section about the component dependency graph how this error could be avoided.

[E005] Component class should be non-generic: CLASS

Only component classes with not type parameters may be referenced by modules.