Skip to content

BindingAnnotations

Googler edited this page Mar 29, 2024 · 15 revisions

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.

Clone this wiki locally