Package sting

Annotation Interface Injector


@Documented @Retention(RUNTIME) @Target(TYPE) @StingProvider("[FlatEnclosingName]Sting_[SimpleName]_Provider") public @interface Injector
Annotates an interface for which a dependency-injected implementation is to be generated. This annotation is a top-level Sting processor entrypoint. The implementation builds a component graph from the included types and exposes the services declared on the injector interface.

Component Graph

The component graph is created in multiple phases.

The first phase involves collecting all the types that are declared via the includes() property and resolving each entry to a Sting-managed contribution. This adds a binding (a.k.a. a potential component) for every Injectable annotated type and a binding for every method in Fragment annotated types. If an included type is annotated with an annotation that is meta-annotated by StingProvider, Sting first resolves that framework-managed type to the provider type and then adds the provider's contributions.

The second phase involves building a set of actual components created by the injector. Any potential binding that is annotated with the Eager annotation is added to the set of components. The inputs are also modelled as eager components. The compiler then examines the services declared by the service methods and attempts to resolve the services into components. When a component is added to the list of component, the service dependencies are added to the sert of services to resolve. When there is no services left to resolve, the injector is considered complete and the compiler terminates this phase.

The next phase will identify the binding for each component will mark the component as eager and if the binding is annotated with the Eager annotation. All dependencies of the component that are not Supplier dependencies are marked as eager. This process is recursively applied to dependencies of dependencies. At the end of this phase the components are all categorized into those that are eager and created when the injector is instantiated and components that are lazy and are created on demand.

The final phase performs validation and correctness checking. It is during this phase that circular dependencies are detected and rejected and that injection requests for injection of singular values with multiple bindings that satisfy are detected.

Resolving Services

A service is resolved into a component by looking at the set of bindings present in the injector. If a binding exists that publishes the same type with the same qualifier then the binding is considered a match.

If the binding is marked as optional (i.e. the component is created by a method annotated with Nullable in a type annotated with the Fragment annotation, or it is an optional injector input) and the service request does not explicitly allow optional bindings then the compiler generates an error as it is not able to determine statically that the service will be available. Optional bindings may be consumed by nullable instance requests, Optional-based requests and Collection<T> requests. For Collection<T> requests, any optional binding that yields null is omitted from the resulting collection.

If no matching binding is found then the compiler will attempt direct auto-discovery by looking for a class annotated with Injectable that has the same name as the type of the service. If found and the type matches the service then the class will be added to the set of components created. If that does not succeed, Sting will attempt provider-backed auto-discovery for framework-managed types annotated with an annotation meta-annotated by StingProvider. In the provider-backed case, the resolved provider type must publish the requested framework-managed type using the default qualifier. If no matching binding is found and the service is not optional then the compiler will generate an error.

Generated Classname

The generated class will have the name of the type annotated with the @Injector annotation prepended with Sting_. For example, the class mybiz.MyInjector will produce an implementation named mybiz.Sting_MyInjector. Nested classes are also supported but their names have the $ sign replaced with a _. i.e. The nested class named mybiz.MyOuterClass.MyInjector will generate an implementation named mybiz.MyOuterClass_Sting_MyInjector

Service methods

Service methods

Instance methods defined on the injector allow access to services contained within the injector and also define the root services that are used to build the component graph. The instance methods must be abstract, have zero parameters, throw no exceptions and return services. Sting actively processes Named on these methods to qualify the requested service and Nullable to mark an instance request as optional. Typed is ignored on injector output methods. Service methods may also request optional services via Optional, Supplier<Optional<T>> and Collection<Supplier<Optional<T>>>.

Instantiation

A injector is created by invoking the constructor of the generated class. The generated class is package access so unless you are only using the injector from within the package it was created, you need to expose a method to create the injector. The usual pattern is to define a static method named create on the injector interface that creates an instance of the injector.

Example:


 @Injector(includes = {BackendFragment.class, FrontendFragment.class})
 public interface MyInjector {

   public static MyInjector create() {
     return new Sting_MyInjector();
   }

   MyWidget myWidget();
 }

 public class Main {
   public static void main(String[] args) {
     MyInjector injector = MyInjector.create();
     ... injector.myWidget() ...
   }
 }

The Injector annotation use the inputs() parameter that declares services that are passed into the injector. These services are made available to components within the component graph and maybe be qualified and/or marked as optional. Each input service is supplied to the injector as a constructor parameter in the generated class.

Example of using input services:


 @Injector( includes = {BackendFragment.class, FrontendFragment.class},
            inputs = { @Injector.Input( type = MyService.class ),
                       @Injector.Input( qualifier = "hostname", type = String.class ) } )
 interface MyInjector {
   MyWidget myWidget();
 }

 public class Main {
   public static void main(String[] args) {
     MyService service = ...;
     MyInjector injector = new Sting_MyInjector(service, "mybiz.com");
   }
 }

Circular Dependencies

Circular dependencies within the injector are detected and rejected during the compilation phase. Circular dependencies can be broken by passing a Supplier dependency. i.e The developer injects the type Supplier<OtherType> instead of OtherType and then calls Supplier.get() on the supplier when access to the service is needed.

  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static @interface 
    A specification of a service that is supplied to an injector during construction.
  • Optional Element Summary

    Optional Elements
    Modifier and Type
    Optional Element
    Description
    boolean
    A flag controlling whether explicitly included types must be @Fragment-annotated.
    A flag controlling whether the injector implementation will be optimized for compilation by GWT.
    Class<?>[]
    A list of types that contribute to the object graph.
    boolean
    A flag controlling whether the injector implementation can be added to other injectors.
    A list of services that must be passed into the injector.
  • Element Details

    • includes

      @Nonnull Class<?>[] includes
      A list of types that contribute to the object graph. When fragmentOnly() is true, these types must be @Fragment-annotated interfaces. When fragmentOnly() is false, these types can also be @Injectable-annotated classes. Types annotated with annotations meta-annotated by StingProvider may also be explicitly included, in which case Sting resolves the framework-managed type to the provider type before contributing bindings to the object graph. The de-duplicated contributions of the @Fragment-annotated interfaces in the includes, and of their inclusions recursively, are all contributed to the object graph.

      If the annotation processor detects a dependency that is required but not explicitly included in the includes list then it will attempt to automatically add the type to the graph. This first attempts direct auto-discovery for @Injectable types and then provider-backed auto-discovery for framework-managed types annotated with an annotation meta-annotated by StingProvider. In the provider-backed case, the resolved provider must publish the framework-managed type using the default qualifier. The current implementation includes types if they were compiled in the same invocation of the java compiler. In the future the annotation processor will load the descriptors from the filesystem.

      Returns:
      a list of types that contribute to the injector's object graph.
      Default:
      {}
    • fragmentOnly

      boolean fragmentOnly
      A flag controlling whether explicitly included types must be @Fragment-annotated. If set to false, the injector may explicitly include @Injectable-annotated types. This also applies when an explicit include resolves via StingProvider to an Injectable-annotated provider type.
      Returns:
      true to require explicit includes to be @Fragment-annotated, false otherwise.
      Default:
      true
    • inputs

      A list of services that must be passed into the injector. The annotation processor will generate a constructor with one parameter for every input. Each input value MUST specify the type parameter otherwise the annotation processor is unable to determine the type of the binding.
      Returns:
      a list of services that must be passed into the injector.
      Default:
      {}
    • injectable

      boolean injectable
      A flag controlling whether the injector implementation can be added to other injectors. If set to true, then the injector can be included in another injector. The inputs() are services that need to be provided while the service methods will define services that this injector provides.
      Returns:
      true to make the injector able to be included in another injector, false otherwise.
      Default:
      false
    • gwt

      @Nonnull Feature gwt
      A flag controlling whether the injector implementation will be optimized for compilation by GWT. This primarily involves the addition of the @DoNotInline annotation to lazy component accessors within the injector implementation to avoid inlining a component accessor and all transitive lazy component accessors that can increase code-size, compilation time and run time.

      If set to Feature.AUTODETECT then the optimization for gwt will be enabled if the class javaemul.internal.annotations.DoNotInline is present on the classpath.

      Returns:
      true to optimize the injector implementation for transpilation to javascript, false otherwise.
      Default:
      AUTODETECT