Baratine on GitHub

Injection

@Inject Introduction

The @Inject annotation is used for dependency injection to combine services without tying up the configuration. The configuration can occur cleanly during initialization and the lookup happens when you need it.

The two phases are registration and injection. For example, a book store might use a search service to find books. To get a copy of the SearchService, it would use injection:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class MyBookStore
{
  @Inject SearchService _search;

  ...
  public void findBookByTitle(String title, Result<Book> result)
  {
    _search.find("title", title, result.of());
  }
}

Before the search service can be injected, it needs to be registered. In Baratine, the registration can use the Web.bean() method like the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class MyBookMain
{
  public static void main(String []args)
  {
    Web.bean(new SearchServiceImpl("localhost", 6920))
       .to(SearchService.class);

    Web.include(MyRestBookStore.class);
    Web.start(args);
  }
}

The bean method registers an instance (or a class or producer), which is then bound to an API. In this case, we register a search service client that connects to localhost:6920 and attach it to SearchService. Any code that needs the search service, such as our bookstore above, can inject it using the interface.

Baratine’s injection defaults to a singleton service. If multiple services inject the bean, only one copy of the bean is created. This default is different from some other injection, because Baratine is designed around services, and the other injections are designed around request objects.

Bean Registration

Beans can be registered with the injection system with several options:

  • A @Bean annotated producing class, which provides the instance.
  • An instance, useful for programmatic configuration.
  • A class, used with other injection.
  • A provider, which programmatically instantiates the instance.

@Bean annotation registration

The @Bean annotation is used with the Web.include methods to register an application class that creates beans. This registration is a two step process: register the producer class, which registers its producer methods.

An advantage of the @Bean annotation registration is allowing the producer to use injection itself. For example, injecting configuration classes. The search registration might look like the following:

1
2
3
4
5
6
7
8
public class MyBookConfig
{
  @Bean
  public SearchService searchService()
  {
    return new SearchServiceImpl("localhost", 6920);
  }
}

The @Bean annotation is an injection @Qualifier, which tells the injection to register the method as an injection producer. When a service needs a SearchService it will call the producer method to create the service. As mentioned before, since Baratine defaults to singletons, only one copy of the search service will be instantiated. The same instance is cached and used for any other injection.

The producer class, MyBookConfig, is registered using Baratine’s Web.include method in the application main class.

1
2
3
4
5
6
7
8
9
public class MyBookMain
{
  public static void main(String []args)
  {
    Web.include(MyBookConfig.class);

    Web.include(MyRestBookStore.class);
    Web.go(args);
  }

Injecting into @Bean Producers

Bean producers themselves can use injection, which can be useful for configuration. The following injects the Baratine configuration object and uses it to configure the search service.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class MyBookConfig
{
  @Inject Config _config;

  @Bean
  public SearchService searchService()
  {
    String address = _config.get("search.address", "localhost");
    int port = _config.get("search.port", int.class, 6920);

    return new SearchServiceImpl(address, port);
  }
}

Instance registration with Web.bean

An object can be registered for injection and bound to one of its interfaces using the Web.bean call.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class MyBookMain
{
  public static void main(String []args)
  {
    Web.bean(new SearchServiceImpl())
       .to(SearchService.class);

    ...
  }
}

Class registration with Web.bean

A class can be registered with Web.bean, which asks the injector to create an instance of the class for injection. Class injection can use injection itself, including configuration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SearchServiceImpl
{
  private String _address;
  private int _port;

  @Inject
  public SearchServiceImpl(Config config)
  {
    _address = config.get("search.address", "localhost");
    _port = config.get("search.port", int.class, 6920);
  }
}

public class MyBookMain
{
  public static void main(String []args)
  {
    Web.bean(SearchServiceImpl.class)
       .to(SearchService.class);
    ...
  }
}

Producer registration with Web.bean

Programmatic producers create a new instance programmatically.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class MyBookMain
{
  public static void main(String []args)
  {
    Producer<SearchService> producer = ()->new SearchServiceImpl();
    Web.producer(producer)
       .to(SearchService.class);
    ...
  }
}

Type Matching

Type-matching for injection is strict, only exact base class matches are used for injection. Generic types are allowed as long as the bounds match.

For example, if there is a binding for Dog to Cerberus, then an injection to Dog will succeed, but an injection to Animal will fail.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyTest
{
  @Inject
  MyAnimal _myAnimal; // will fail

  @Inject
  MyDog _myDog; // will succeed
  ...
}

public interface MyDog extends MyAnimal {}

public class MyMain
{
  public static void main(String []args)
  {
    Web.bean(Cerberus.class).to(MyDog.class);
    ...

    Web.go(args);
  }
}

@Qualifier

The @Qualifier annotation is used to distinguish injection of the same type. Qualifiers are custom annotations which themselves are annotated by @Qualifier. The default qualifier is @Bean. Other built-in qualifiers include @Service and @Named.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.inject.Qualifier;

@Qualifier
@Retention(RUNTIME)
@Target({TYPE,METHOD,FIELD,PARAMETER})
public @interface MyRed
{
}

The injection point qualifier must match the binding qualifier. Below, we define the binding in MyDogConfig and use it in MyRest. The injection will receive Clifford because the qualifiers match.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MyDogConfig
{
  @MyRed
  public MyDog dog()
  {
    return new Clifford();
  }

  @MyBlack
  public MyDog blackDog()
  {
    return new Labrador();
  }
}

public class MyRest
{
  @Inject @MyRed
  MyDog _myDog;

  ...
}

...
public static void main(String []args)
{
  Web.include(MyDogConfig.class);
  Web.include(MyRest.class);

  Web.go(args);
}

Provider

A Provider can return injection instances programmatically. The provider itself is injected.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class MyRest
{
  @Inject
  Provider<MyDog> _myDog;

  void myMethod()
  {
    MyDog dog = _myDog.get();
    ...
  }
}

Scope: @Singleton and @Factory

Injection supports two scopes: singleton and factory.

@Singleton scope is the default; it returns only a single instance of the bean.

@Factory scope returns a new instance for each injection point, which works well in combination with Provider.

1
2
3
4
5
6
7
8
public class MyHttpConfig
{
  @Factory
  public MyHttp connect()
  {
    return new HttpClient("localhost", 6920);
  }
}

@Priority

@Priority can distinguish between multiple matching bindings. When you want a lower-priority default that can be overridden, the lower-priority default can have a lower priority value.

1
2
3
4
5
6
7
8
public class MyDefaultConfig
{
  @Bean @Priority(-1)
  public MyDog dog()
  {
    return new PlainDog();
  }
}

Priority can also be used in testing to give a test mock value a higher priority.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class MyTest
{
  @Test
  public void myTest()
  {
    Web.bean(new MockDog()).to(MyDog.class).priority(1);
    ...
    Web.start();

    assertEquals(...);
  }
}

Built-in Beans

Baratine automatically registers the following beans:

  • Config - for baratine.yml and command-line configuration
  • InjectManager - the injection manager
  • ServiceManager - the service manager

Service Injection

Services are available for injection. The injected value is the Service proxy. Because of service isolation, calls to the proxy will be sent to the implementation through the service inbox.

Services use @Service as the qualifier. If the address is missing, the injector will create an address based on the proxy name. So MyServiceProxy would look up the service at local:///MyServiceProxy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class MyRest
{
  @Inject @Service
  MyServiceProxy _myService;
  ...
}

public class MyMain
{
  public static void main(String []args)
  {
    Web.service(MyServiceImpl.class);
    Web.include(MyRest.class);

    Web.go(args);
  }
}

If the proxy includes a @Service annotation, the @Service can be omitted from the injection.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Service
public interface MyServiceApi
{
}

public class MyRest
{
  @Inject
  MyServiceProxy _myService;
  ...
}