15
BindingAnnotations
Copybara-Service edited this page 2024-03-29 12:40:17 -07:00

Binding Annotations

Occasionally you'll want multiple bindings for the same type. For example, you might want both a PayPal credit card processor and a Google Checkout processor. To enable this, bindings support an optional binding annotation. The annotation and type together uniquely identify a binding. This pair is called a key.

Defining binding annotations

Binding annotations are Java annotations that are annotated with meta annotation @Qualifier or @BindingAnnotation.

Defining a binding annotation requires two lines of code plus several imports. Put this in its own .java file or inside the type that it annotates.

package example.pizza;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;

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

// Older code may still use Guice `BindingAnnotation` in place of the standard
// `@Qualifier` annotation. New code should use `@Qualifier` instead.
@BindingAnnotation
@Target({ FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface GoogleCheckout {}

You don't need to understand all of these meta-annotations, but if you're curious:

  • @Qualifier is a JSR-330 meta-annotation that tells Guice that this is a binding annotation. Guice will produce an error if multiple binding annotations are ever applied to the same member. Guice's @BindingAnnotation is also used for this purpose in older code.
  • The @Target({FIELD, PARAMETER, METHOD}) annotation prevents the @PayPal annotation from accidentally being applied in places where it serves no purpose.
  • @Retention(RUNTIME) makes the annotation available at runtime, which is required in Guice.

To depend on the annotated binding, apply the annotation to the injected parameter:

public class RealBillingService implements BillingService {

  @Inject
  public RealBillingService(@PayPal CreditCardProcessor processor,
      TransactionLog transactionLog) {
    ...
  }

Lastly we create a binding that uses the annotation:

final class CreditCardProcessorModule extends AbstractModule {
  @Override
  protected void configure() {
    // This uses the optional `annotatedWith` clause in the `bind()` statement
    bind(CreditCardProcessor.class)
        .annotatedWith(PayPal.class)
        .to(PayPalCreditCardProcessor.class);
  }

  // This uses binding annotation with a @Provides method
  @Provides
  @GoogleCheckout
  CreditCardProcessor provideCheckoutProcessor(
      CheckoutCreditCardProcessor processor) {
    return processor;
  }
}

@Named

Guice comes with a built-in binding annotation @Named that takes a string:

public class RealBillingService implements BillingService {

  @Inject
  public RealBillingService(@Named("Checkout") CreditCardProcessor processor,
      TransactionLog transactionLog) {
    ...
  }

To bind a specific name, use Names.named() to create an instance to pass to annotatedWith:

final class CreditCardProcessorModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(CreditCardProcessor.class)
      .annotatedWith(Names.named("Checkout"))
      .to(CheckoutCreditCardProcessor.class);
  }
}

Since the compiler can't check the string, we recommend using @Named sparingly. Defining your own purpose-built annotations provides better type-safety.

Binding Annotations with Attributes

Guice supports binding annotations that have attribute values (like @Named). In the rare case that you need such an annotation (and can't use an @Provides method) we encourage you to use @AutoAnnotation from the Auto/Value project, as properly implementing an annotation is error-prone. If you do decide to manually create a custom implementation be sure to properly implement the equals() and hashCode() specifications detailed in the Annotation Javadoc. Pass an instance of this class to the annotatedWith() binding clause.