blob: f2ef13a1f2c4e204962459430e917a694d845db0 [file] [log] [blame]
 N4JS Language Specification

11. Extended Fetaures

11.1. Array and Object Destructuring

N4JS supports array and object destructuring as provided in ES6. This is used to conveniently assign selected elements of an array or object to a number of newly-declared or pre-existing variables or to further destructure them by using nested destructuring patterns [55].

11.1.1. Syntax

BindingPattern <Yield>:
ObjectBindingPattern<Yield>
| ArrayBindingPattern<Yield>
;

ObjectBindingPattern <Yield> returns BindingPattern:
{BindingPattern}
'{' (properties+=BindingProperty<Yield,AllowType=false> (',' properties+=BindingProperty<Yield,AllowType=false>)*)? '}'
;

ArrayBindingPattern <Yield> returns BindingPattern:
{BindingPattern}
'['
elements+=Elision* (
elements+=BindingRestElement<Yield>
(',' elements+=Elision* elements+=BindingRestElement<Yield>)*
(',' elements+=Elision*)?
)?
']'
;

BindingProperty <Yield, AllowType>:
=>(LiteralBindingPropertyName<Yield> ':') value=BindingElement<Yield>
| value=SingleNameBinding<Yield,AllowType>
;

fragment LiteralBindingPropertyName <Yield>*:
declaredName=IdentifierName | declaredName=STRING | declaredName=NumericLiteralAsString
// this is added here due to special treatment for a known set of expressions
| '[' (declaredName=SymbolLiteralComputedName<Yield> | declaredName=STRING) ']'
;

11.1.2. Semantics

The following example declares four variables a, b, x, and prop2. Variables a and x will have the value hello, whereas b and prop2 will have value 42.

var [a,b] = ["hello", 42];

var {prop1:x, prop2} = {prop1:"hello", prop2:42};

In the case of prop2, we do not provide a property name and variable name separately; this is useful in cases where the property name also makes for a suitable variable name (called single name binding).

One of the most useful use cases of destructuring is in a for..of loop. Take this example:

var arr1 = [ ["hello",1,2,3], ["goodbye",4,5,6] ];
}
// will print:
//   hello / [ 1, 2, 3 ]
//   goodbye / [ 4, 5, 6 ]

var arr2 = [ {key:"hello", value:42}, {key:"goodbye", value:43} ];
for(var {key,value} of arr2) {
console.log(key,'/',value);
}
// will print:
//   hello / 42
//   goodbye / 43

Array and object destructuring pattern can appear in many different places:

• In a variable declaration (not just in variable statements but also in other places where variable declarations are allowed, e.g. plain for loops; called destructuring binding; see Variable Statement).

• On the left-hand side of an assignment expression (the assignment expression is then called destructuring assignment; see Assignment Expression).

• In a for..in or for..of loop on the left side of the in/of (see for …​ of statement).

It can also be used in plain statements, but then we actually have one of the above two use cases.
• With lists of formal parameters or function arguments (not supported yet).

• For further details on array and object destructuring please refer to the ECMAScript 6 specification - [ECMA15a].

Type annotations can only be added when a new variable name is introduced since the short version would be ambiguous with the long one. For example:

var {x: someTypeOrNewVar} = ol

could either mean that a new variable someTypeOrNewVar is declared and ol.x is assigned to it, or that a new variable x is declared with type someTypeOrNewVar. The longer form would look like this:

var {x: x: someType} = ol

We can make this more readable:

var {propOfOl: newVar: typeOfNewVar} = ol

11.2. Dependency Injection

This chapter describes DI mechanisms for N4JS. This includes compiler, validation and language extensions that allow to achieve DI mechanisms built in into the N4JS language and IDE.

N4JS DI support specifies a means for obtaining objects in such a way as to maximize reusability, testability and maintainability, especially compared to traditional approaches such as constructors, factories and service locators. While this can be achieved manually (without tooling support) it is difficult for nontrivial applications. The solutions that DI provides should empower N4JS users to achieve the above goals without the burden of maintaining so-called ’boilerplate’ code.

Figure 8. DI Basic Terms

key: pass the dependency instead of letting the client create or find it

Core terms

• Service - A set of APIs describing the functionality of the service.

• Service Implementations - One or more implementations of given service API.

• Client - Consumer of a given functionality, uses the given Service Implementation.

• Injector - Object providing Service Implementation of a specific Service, according to configuration.

• Binding - Part of configuration describing which interface implementing a subtype will be injected, when a given interface is requested.

• Provider - Factory used to create instances of a given Service Implementation or its sub-components, can be a method.

• Injection Point - Part of the user’s code that will have the given dependency injected. This is usually fields, method parameters, constructor parameters etc.

• DI configuration - This describes which elements of the user’s code are used in mechanisms and how they are wired. It is derived from user code elements being marked with appropriate annotations, bindings and providers.

• di wiring - The code responsible for creating user objects. These are injectors, type factories/providers, fields initiators etc.

11.2.1. DI Components and Injectors

N4JS’ Dependency Injection systems is based on the notion of DIC.

Definition: DI Component

A DIC is a N4Class annotated with @GenerateInjector.

This annotation causes an injector to be created for (and associated to) the DI. DIC can be composed; meaning that when requested to inject an instance of a type, a DIC’s injector can delegate this request to the injector of the containing DIC. An injector always prioritizes its own configuration before delegating to the container’s injector. For validation purposes, a child DI can be annotated with @WithParent to ensure that it is always used with a proper parent.

Injector is the main object of DI mechanisms responsible for creating object graphs of the application. At runtime, injectors are instances of N4Injector.

Req. IDE-138: DI Component and Injector (ver. 1)

The following constraints must hold for a class C[/itex] marked as DIC:

1. A subclass S[/itex] of C[/itex] is a DIC as well and it must be marked with GenerateInjector.

2. If a parent DIC P[/itex] is specified via WithParent, then P[/itex] must be a DIC as well.

3. The injector associated to a DIC is of type N4Injector. It can be retrieved via N4Injector.of(DIC) in which DIC is the DIC.

4. Injectors associated to DIC a are DI-singletons (cf. Singleton Scope). Two calls to N4Injector.of(DIC) are different (as different DIC are assumed).

Req. IDE-139: Injection Phase (ver. 1)

We call the (transitive) creation and setting of values by an injector I[/itex] caused by the creation of an root object R[/itex] the injection phase. If an instance C[/itex] is newly created by the injector I[/itex] (regardless of the injection point being used), the injection is transitively applied on C[/itex]. The following constraints have to hold:

1. Root objects are created by one of the following mechanisms:

1. Any class or interface can be created as root objects via an injector associated to a DIC:
var x: X = N4Injector.of(DIC).create(X);
in which DIC is a DIC.

Of course, an appropriate binding must exist. [56]

2. If a type has the injector being injected, e.g. via field injection @Inject injector: N4Injector;, then this injector can be used anytime in the control flow to create a new root object similar as above (using create method).

3. If a provider has been injected (i.e. an instance of {N4Provider}), then its get() method can be used to create a root object causing a new injection phase to take place.

2. If C.ctor[/itex] is marked as injection point, all its arguments are set by the injector. This is also true for an inherited constructor marked as an injection point. See [Req-IDE-143] . For all arguments the injection phase constraints have to hold as well.

3. All fields of C[/itex], including inherited once, marked as injection points are set by the injector. For all fields the injection phase constraints have to hold as well.

The injector may use a provider method (of a binder) to create nested instances.

The injector is configured with Binders and it tracks Bindings between types (Binders and Bindings). An N4JS developer normally would not interact with this object directly except when defining an entry-point to his application. Injectors are configured with Binders which contain explicit Bindings defined by an N4JS developer. A set of these combined with implicit bindings creates the di configuration used by a given injector. To configure given Injectors with given Binder(s) use @UseBinder annotation.

11.2.1.1. DIComponent Relations

A Parent-Child relation can be established between two DIComponents. Child DIComponents use the parent bindings but can also be configured with their own bindings or change targets used by a parent. The final circumstance is local to the child and is referred to as rebinding. For more information about bindings see Binders and Bindings. A Child-Parent relation is expressed by the @WithParentInjector annotation attached to a given DIComponent. When this relation is defined between DIComponents, the user needs to take care to preserve the proper relation between injectors. In other words, the user must provide an instance of the parent injector (the injector of the DIComponent passes as a parameter to @WithParentInjector) when creating the child injector (injector of the DIComponent annotated with @WithParentInjector).

Example 99. Simple DIComponents Relation
@GenerateInjector
class ParentDIComponent{}

@GenerateInjector
@WithParentInjector(ParentDIComponent)
class ChildDIComponent{}

var parentInejctor = N4Inejctor.of(ParentDiCompoennt);
var childInjector = N4Inejctor.of(ChildDIComponent, parentInjector);

With complex DIComponent structures, injector instances can be created with a directly-declared parent and also with any of its children. This is due to the fact that any child can rebind types, add new bindings, but not remove them. Any child is, therefore, compatible with its parents.

A given DIComponent is compatible with another DIComponent if it has bindings for all keys in other component bindings.

DIC1,DIC2:DIC1.binding¯.key¯DIC2.binding¯.key¯DIC2<:DIC1[/itex]
Although subtype notation <:[/itex] is used here it does not imply actual subtype relations. It was used in this instance for of lack of formal notations for DI concepts and because this is similar to the Liskov Substitution principle.

A complex Child-Parent relation between components is depicted in Complex DIComponents Relations and Complex DIComponents Relations below.

Figure 9. Complex DIComponents Relations
Example 100. Complex DIComponents Relations
@GenerateInjector class A {}
@GenerateInjector @WithParentInjector(A) class B {}
@GenerateInjector @WithParentInjector(B) class C {}
@GenerateInjector @WithParentInjector(C) class D {}
@GenerateInjector @WithParentInjector(A) class B2 {}
@GenerateInjector @WithParentInjector(B2) class C2 {}
@GenerateInjector @WithParentInjector(C2) class D2 {}
@GenerateInjector @WithParentInjector(A) class X {}
@GenerateInjector @WithParentInjector(C) class Y {}

// creating injectors
var injectorA = N4Injector.of(A);
//following throws DIConfigurationError, expected parent is not provided
//var injectorB =  N4Injector.of(B);
//correct declarations
var injectorB =  N4Injector.of(B, injectorA);
var injectorC = N4Injector.of(C, injectorB);
var injectorD = N4Injector.of(D, injectorC);
var injectorB2 = N4Injector.of(B2, injectorA);
var injectorC2 = N4Injector.of(C2, injectorB2);
var injectorD2 = N4Injector.of(D2, injectorC2);

//Any injector of {A,B,C,D,b2,C2,D2} s valid parent for injector of X, e.g. D or D2
N4Injector.of(X, injectorD);//is ok as compatible parent is provided
N4Injector.of(X, injectorD2);//is ok as compatible parent is provided

N4Injector.of(Y, injectorC);//is ok as direct parent is provided
N4Injector.of(Y, injectorD);//is ok as compatible parent is provided

N4Injector.of(Y, injectorB2);//throws DIConfigurationError, incompatible parent is provided
N4Injector.of(Y, injectorC2);//throws DIConfigurationError, incompatible parent is provided
N4Injector.of(Y, injectorD2);//throws DIConfigurationError, incompatible parent is provided

11.2.2. Binders and Bindings

Binder allows an N4JS developer to (explicitly) define a set of Bindings that will be used by an Injector configured with a given Binder. There are two ways for Binder to define Bindings: @Bind (N4JS DI @Bind) annotations and a method annotated with @Provides.

Binder is declared by annotating a class with the @Binder annotation.

A Binding is part of a configuration that defines which instance of what type should be injected into an injection point (Injection Points) with an expected type.

Provider Method is essentially a factory method that is used to create an instance of a type. N4JS allows a developer to declare those methods (see N4JS DI @Provides) which gives them a hook in instance creation process. Those methods will be used when creating instances by the Injector configured with the corresponding Binder. A provider method is a special kind of binding (key[/itex]) in which the return type of the method is the key[/itex]. The target[/itex] type is unknown at compile time (although it may be inferred by examining the return statements of the provide method).

Definition: Binding

A binding is a pair bindkeytarget[/itex]. It defines that for a dependency with a given key which usually is the expected type at the injection point. An instance of type target[/itex] is injected.

A binding is called explicit if it is declared in the code, i.e. via @Bind annotation or @Provides annotation).

A binding is called implicit if it is not declared. An implicit binding can only be used if the key[/itex] is a class and derived from the type at the injection point, i.e. the type of the field or parameter to be injected. In that case, the target[/itex] equals the key[/itex].

A provider method M[/itex] (in the binder) defines a binding

bindM.returnTypeX[/itex]

(in which X[/itex] is an existential type with X<:target.returnType[/itex]).

For simplification, we define:

key*=target.returnType,if target is provider methodkey,otherwise (key is a type reference)[/itex]

and

target*=X<:target.returnType,if target is provider methodtarget,otherwise (target is a type reference)[/itex]

Req. IDE-140: Bindings (ver. 1)

For a given binding b=keytarget[/itex], the following constraints must hold: [57]

1. key[/itex] must be either a class or an interface.

2. target[/itex] must either be a class or a provider method.

3. If b[/itex] is implicit, then key[/itex] must be a class. If key[/itex] references a type T[/itex], then target=T[/itex] – even if key[/itex] is a use-site structural type.

4. key[/itex] and target*[/itex] can be nominal, structural or field-structural types, either definition-site or use-site. The injector and binder needs to take the different structural reference into account at runtime!

5. target*<:key[/itex] must hold

6. If during injection phase no binding for a given key is found, an DIUnsatisfiedBindingError is thrown.

Req. IDE-141: Transitive Bindings (ver. 1)

If an injector contains two given bindings b1=key1target1[/itex] and b2=key2key1[/itex], an effective binding b=key2target1[/itex] is derived (replacing b1[/itex]).

N4JS DI mechanisms don’t allow for injection of primitives or built-in types. Only user-defined N4Types can be used. In cases where a user needs to inject a primitive or a built-in type, the developer must wrap it into its own class [58]. This is to say that none of the following metatypes can be bound: primitive types, enumerations, functions, object types, union- or intersection types. It is possible to (implicitly) bind to built-in classes.

While direct binding overriding or rebinding is not allowed, Injector can be configured in a way where one type can be separately bound to different types with implicit binding, explicit binding and in bindings of the child injectors. Binding precedence is a mechanism of Injector selecting a binding use for a type. It operates in the following order:

1. Try to use explicit binding, if this is not available:

2. Try to delegate to parent injectors (order of lookup is not guaranteed, first found is selected). If this is not available then:

3. Try to use use implicit binding, which is simply to attempt to create the instance.

If no binding for a requested type is available an error will be thrown.

11.2.3. Injection Points

By injection point we mean a place in the source code which, at runtime, will be expected to hold a reference to a particular type instance.

11.2.3.1. Field Injection

In its simplest form, this is a class field annotated with @Inject annotation. At runtime, an instance of the containing class will be expected to hold reference to an instance of the field declared type. Usually that case is called Field Injection.

Req. IDE-142: Field Injection (ver. 1)

The injector will inject the following fields:

1. All directly contained fields annotated with @Inject.

2. All inherited fields annotated with @Inject.

3. The injected fields will be created by the injector and their fields will be injected as well.

Example 101. Simple Field Injection

Simple Field Injection demonstrates simple field injection using default bindings. Note that all inherited fields (i.e. A.xInA) are injected and also fields in injected fields (i.e. x.y)

Simple Field Injection
class X {
@Inject y: Y;
}
class Y {}

class A {
@Inject xInA: X;
}
class B extends A {
@Inject xInB: X;
}

@GenerateInjector
export public class DIC {
@Inject a: B;
}

var dic = N4Injector.of(DIC).create(DIC);
console.log(dic);              // --> DIC
console.log(dic.a);            // --> B
console.log(dic.a.xInA);       // --> X
console.log(dic.a.xInA.y);     // --> Y
console.log(dic.a.xInB);       // --> X
console.log(dic.a.xInB.y);     // --> Y
11.2.3.2. Constructor Injection

Parameters of the constructor can also be injected, in which case this is usually referred to as Constructor Inejction. This is similar to Method Injection and while constructor injection is supported in N4JS, method injection is not (see remarks below).

When a constructor is annotated with @Inject annotation, all user-defined, non-generic types given as the parameters will be injected into the instance’s constructor created by the dependency injection framework. Currently, optional constructor parameters are always initialized and created by the framework, therefore, they are ensured to be available at the constructor invocation time. Unlike optional parameters, variadic parameters cannot be injected into a type’s constructor. In case of annotating a constructor with @Inject that has variadic parameters, a validation error will be reported. When a class’s constructor is annotated with @Inject annotation, it is highly recommended to annotate all explicitly-defined constructors at the subclass level. If this is not done, the injection chain can break and runtime errors might occur due to undefined constructor parameters. In the case of a possible broken injection chain due to missing @Inject annotations for any subclasses, a validation warning will be reported.

Req. IDE-143: Constructor Injection (ver. 1)

If a class C[/itex] has a constructor marked as injection point, the following applies:

1. If C[/itex] is subclassed by S[/itex], and if S[/itex] has no explicit constructor, then S[/itex] inherits the constructor from C[/itex] and it will be an injection point handled by the injector during injection phase.

2. If S[/itex] provides its own injector, C.ctor[/itex] is no longer recognized by the injector during the injection phase. There will be a warning generated in S.ctor[/itex] to mark it as injection point as well in order to prevent inconsistent injection behavior. Still, C.ctor[/itex] must be called in S.ctor[/itex] similarly to other overridden constructors.

11.2.3.3. Method Injection

Other kinds of injector points are method parameters where (usually) all method parameters are injected when the method is called. In a way, constructor injection is a special case of the method itself.

11.2.3.3.1. Provider

Provider is essentially a factory for a given type. By injecting an N4Provider into any injection point, one can acquire new instances of a given type provided by the injected provider. The providers prove useful when one has to solve re-injection issues since the depended type can be wired and injected via the provider rather than the dependency itself and can therefore obtain new instances from it if required. Provider can be also used as a means of delaying the instantiation time of a given type.

N4Provider is a public generic built-in interface that is used to support the re-injection. The generic type represents the dependent type that has to be obtained. The N4Provider interface has one single public method: public T get() which should be invoked from the client code when a new instance of the dependent type is required. Unlike any other unbound interfaces, the N4Provider can be injected without any explicit binding.

The following snippet demonstrates the usage of N4Provider:

class SomeService { }

@Singleton
class SomeSingletonService { }

class SomeClass {

@Inject serviceProvider: N4Provider<SomeService>;
@Inject singletonServiceProvider: N4Provider<SomeSingletonService>;

void foo() {
console.log(serviceProvider.get() ===
serviceProvider.get()); //false

console.log(singletonServiceProvider.get() ===
singletonServiceProvider.get()); //true
}

}

It is important to note that the N4Provider interface can be extended by any user-defined interfaces and/or can be implemented by any user-defined classes. For those user-defined providers, consider all binding-related rules; the extended interface, for example, must be explicitly bound via a binder to be injected. The binding can be omitted only for the built-in N4Providers.

11.2.4. N4JS DI Life Cycle and Scopes

DI Life Cycle defines when a new instance is created by the injector as its destruction is handled by JavaScript. The creation depends on the scope of the type. Aside from the scopes, note that it is also possible to implement custom scopes and life cycle management via N4JSProvider and Binder@Provides methods.

11.2.4.1. Injection Cylces

Definition: Injection Cycle

We define an injection graph GVE[/itex] as a directed graph as follows: V[/itex] (the vertices) is the set types of which instances are created during the injection phase and which use . E[/itex] (the edges) is a set of directed and labeled edges v1v2label[/itex], where label indicates the injection point:

1. ToTf"field"[/itex], if Tf[/itex] is the actualy type of an an injected field of an instance of type To[/itex]

2. TcTp"ctor"[/itex], if Tp[/itex] is the type of a parameter used in a constructor injection of type Tc[/itex]

One cycle in this graph is an injection cycle.

When injecting instances into an object, cycles have to be detected and handled independently from the scope. If this is not done, the following examples would result in an infinite loop causing the entire script to freeze until the engine reports an error:

class A { @Inject b: B; }
class B { @Inject a: A; }
Figure 10. Field Cycle
class C { @Inject constructor(d: D) {} }
class D { @Inject c: C; }
Figure 10. Ctor Field Cycle
class E { @Inject constructor(f: F) {} }
class F { @Inject constructor(e: E) {} }
Figure 10. Ctor Cycle

The injector needs to detect these cycles and resolve them.

Req. IDE-144: Resolution of Injection Cycles (ver. 1)

A cycle cG[/itex], with G[/itex] being an injection graph, is resolved as follows:

1. If c[/itex] contains no edge with label="ctor"[/itex], the cycle is resolved using the algorithm described below.

2. If c[/itex] contains at least one edge with label="ctor"[/itex], a runtime exception is thrown.

Cycles stemming from field injection are resolved by halting the creation of new instances of types which have been already created by a containing instance. The previously-created instance is then reused. This makes injecting the instance of a (transitive) container less complicated and without the need to pass the container instance down the entire chain. The following pseudo code describes the algorithm to create new instances which are injected into a newly created object:

function injectDependencies(object) {
doInjectionWithCylceAwareness(object, {(typeof object -> object)})
}

function doInjectionWithCylceAwareness(object, createdInstancesPerType) {
forall v $\in$ injectedVars of object {
var type = retrieveBoundType(v)
var instance = createdInstancesPerType.get(type)
if (not exists instance) {
instance = createInstance(type, createdInstancesPerType)
doInjectionWithCylceAwareness(instance,
createdInstancesPerType $\cap$ {(type->instance)})
}
v.value = instance;
}
}

The actual instance is created in line 10 via createInstance. This function then takes scopes into account. The createdInstancesPerType map is passed to that function in order to enable cycle detection for constructor injection. The following scopes are supported by the N4JS DI, other scopes, cf. Jersey custom scopes and Guice custom scopes, may be added in the future.

This algorithm is not working for constructor injection because it is possible to already access all fields of the arguments passed to the constructor. In the algorithm, however, the instances may not be completely initialized.

11.2.4.2. Default Scope

The default scope always creates a new instance.

11.2.4.3. Singleton Scope

The singleton scope (per injector) creates one instance (of the type with @Singleton scope) per injector, which is then shared between clients.

The injector will preserve a single instance of the type of S and will provide it to all injection points where type of S is used. Assuming nested injectors without any declared binding where the second parameter is S, the same preserved singleton instance will be available for all nested injectors at all injection points as well.

The singleton preservation behavior changes when explicit bindings are declared for type S on the nested injector level. Let’s assume that the type S exists and the type is annotated with @Singleton. Furthermore, there is a declared binding where the binding’s second argument is S. In that case, unlike in other dependency injection frameworks, nested injectors may preserve a singleton for itself and all descendant injectors with @Bind annotation. In this case, the preserved singleton at the child injector level will be a different instance than the one at the parent injectors.

The tables below depict the expected runtime behavior of singletons used at different injector levels. Assume the following are injectors: C, D, E, F and G. Injector C is the top most injector and its nesting injector D, hence injector C is the parent of the injector D. Injector D is nesting E and so on. The most nested injector is G. Let’s assume J is an interface, class U implements interface J and class V extends class U. Finally assume both U and V are annotated with @Singleton at definition-site.

DI No Bindings depicts the singleton preservation for nested injectors without any bindings. All injectors use the same instance from a type. Type J is not available at all since it is not bound to any concrete implementation:

Table 12. DI No Bindings

Binding

Injector nesting (>[/itex])

C

D

E

F

G

J

NaN[/itex]

NaN[/itex]

NaN[/itex]

NaN[/itex]

NaN[/itex]

U

U0[/itex]

U0[/itex]

U0[/itex]

U0[/itex]

U0[/itex]

V

V0[/itex]

V0[/itex]

V0[/itex]

V0[/itex]

V0[/itex]

DI Transitive Bindings is configured by explicit bindings. At the root injector level, type J is bound to type U. Since the second argument of the binding is declared as a singleton at the definition-site, this explicit binding implicitly ensures that the injector and all of its descendants preserve a singleton of the bound type U. At injector level C, D and E, the same instance is used for type J which is type U at runtime. At injector level E there is an additional binding from type U to type V that overrules the binding declared at the root injector level. With this binding, each places where J is declared, type U is used at runtime.

Furthermore, since V is declared as a singleton, both injector F and G are using a shared singleton instance of type V. Finally, for type V, injector C, D and E should use a separate instance of V other than injector level F and G because V is preserved at injector level F with the U [/itex] V binding.

Table 13. DI Transitive Bindings
Binding J → U U → V

Injector nesting (>)

C

D

E

F

G

J

U0[/itex]

U0[/itex]

U0[/itex]

V0[/itex]

V0[/itex]

U

U0[/itex]

U0[/itex]

U0[/itex]

V0[/itex]

V0[/itex]

V

V1[/itex]

V1[/itex]

V1[/itex]

V0[/itex]

V0[/itex]

DI Re - Binding depicts the singleton behaviour but unlike the above table, the bindings are declared for the interface J.

Table 14. DI Re - Binding
Binding J → U J → V

Injector nesting (>[/itex])

C

D

E

F

G

J

U0[/itex]

U0[/itex]

U0[/itex]

V0[/itex]

V0[/itex]

U

U0[/itex]

U0[/itex]

U0[/itex]

U0[/itex]

U0[/itex]

V

V1[/itex]

V1[/itex]

V1[/itex]

V0[/itex]

V0[/itex]

DI Child Binding describes the singleton behavior when both bindings are configured at child injector levels but not the root injector level.

Table 15. DI Child Binding
Binding U [/itex] V J [/itex] U

Injector nesting (>[/itex])

C

D

E

F

G

J

NaN[/itex]

NaN[/itex]

NaN[/itex]

U0[/itex]

U0[/itex]

U

U1[/itex]

V0[/itex]

V0[/itex]

U0[/itex]

U0[/itex]

V

V1[/itex]

V0[/itex]

V0[/itex]

V0[/itex]

V0[/itex]

11.2.4.4. Per Injection Chain Singleton

The per injection chain singleton is ’between’ the default and singleton scope. It can be used in order to explicitly describe the situation which happens when a simple cycle is resolved automatically. It has more effects that lead to a more deterministic behavior.

Assume a provider declared as

var pb: Provider<B>;

to be available:

@PerInjectionSingleton
class A {  }

class B { @Inject a: A; @Inject a1: A;}

b1=pb.get();
b2=pb.get();
b1.a != b2.a
b1.a == b1.a1
b2.a == b2.a1
@Singleton
class A {  }

class B { @Inject a: A; @Inject a1: A;}

b1=pb.get();
b2=pb.get();
b1.a == b2.a
b1.a == b1.a1
b2.a == b2.a1
// no annotation
class A {  }

class B { @Inject a A; @Inject a1: A;}

b1=pb.get();
b2=pb.get();
b1.a != b2.a
b1.a != b1.a1
b2.a != b2.a1

11.2.5. Validation of callsites targeting N4Injector methods

Terminology for this section:

• a value is injectable if it

• either conforms to a user-defined class or interface (a non-parameterized one, that is),

• or conforms to Provider-of-T where T is injectable itself.

• a classifier declaring injected members is said to require injection

To better understand the validations in effect for callsites targeting

N4Injector.of(ctorOfDIC: constructor{N4Object}, parentDIC: N4Injector?, ...providedBinders: N4Object)

we can recap that at runtime:

• The first argument denotes a DIC constructor.

• The second (optional) argument is an injector.

• Lastly, the purpose of providedBinders is as follows:

• The DIC above is marked with one or more @UseBinder.

• Some of those binders may require injection.

• Some of those binders may have constructor(s) taking parameters.

• The set of binders described above should match the providedBinders.

Validations in effect for N4Injector.create(type{T} ctor) callsites:

• type{T} should be injectable (in particular, it may be an N4Provider).

11.2.6. N4JS DI Annotations

Following annotations describe API used to configure N4JSDI.

name

@GenerateInjector

targets

N4Class

retention policy

RUNTIME

transitive

NO

repeatable

NO

arguments

NO

@GenerateInjector marks a given class as DIComponent of the graph. The generated injector will be responsible for creating an instance of that class and all of its dependencies.

name

@WithParentInjector

targets

N4Class

retention policy

RUNTIME

transitive

NO

repeatable

NO

arguments

TypeRef

@WithParentInjector marks given injector as depended on other injector. The depended injector may use provided injector to create instances of objects required in its object graph.