Skip to content

RestrictedBindingSource

Googler edited this page Mar 3, 2021 · 5 revisions

Restricting the Binding Source

Motivation

If you own a Guice library that provides a set of bindings, you may want to prevent code outside your library from providing them. Reasons include:

  • Preventing users from rolling their own fake implementation of your bindings in tests, in order to force them to use the canonical fake module your library maintains.

  • Letting alternate implementations of your set of bindings appear throughout your codebase makes it harder to refactor client code.

  • Guaranteeing a contract for downstream libraries. There can be invariants between your bindings that you want to guarantee by only allowing a limited set of vetted implementations - similar to how Guava guarantees the ImmutableSet contract by only allowing subclasses within the library.

The @RestrictedBindingSource annotation lets you do this - it lets you restrict which modules can provide your bindings. Annotating your bindings with @RestrictedBindingSource lets you specify a permit annotation that modules providing them need to be annotated with. For example, this is how you would ensure that @IpAddress Integer and RoutingTable bindings can only be provided by NetworkModule:

// Modules annotated with this Permit can provide Network bindings.
@RestrictedBindingSource.Permit
@Retention(RetentionPolicy.RUNTIME)
@interface NetworkPermit {}

// Bindings with the @IpAddress qualifier annotation can only be provided by
// modules with the NetworkPermit annotation.
@RestrictedBindingSource(
  explanation = "Please install NetworkModule instead of binding network bindings yourself.",
  permits = {NetworkPermit.class})
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface IpAddress {}

// The RoutingTable binding can only be provided by modules annotated with the
// NetworkPermit annotation.
@RestrictedBindingSource(
  explanation = "Please install NetworkModule instead of binding network bindings yourself.",
  permits = {NetworkPermit.class})
public interface RoutingTable {
  int getNextHopIpAddress(int destinationIpAddress);
}

@NetworkPermit
public final class NetworkModule extends AbstractModule {
  @Provides @IpAddress int provideIp( ... ) { ... }

  @Override
  protected void configure() {
    // RoutingModule is permitted to provide the RoutingTable binding because
    // it is installed by NetworkModule, which is annotated with NetworkPermit
    // - ie. it's enough for any module providing a binding (directly or
    // indirectly) to have the right permit.
    install(new RoutingModule());
  }
}

private final RoutingModule extends AbstractModule {
  @Provides RoutingTable provideRoutingTable( ... ) { ... }
}

If the restriction is violated, the explanation will be included in the error message, pointing the user to the correct module to install for providing the restricted binding. The explanation will also be included in the error message if the restricted binding is missing - again pointing the developer to the right module to install.

ModuleAnnotatedMethodScanner as a Source of Bindings

In addition to modules, ModuleAnnotatedMethodScanners are also a source of bindings and can be annotated with permits to grant them permission to provide restricted bindings.

For example, if a scanner scans @FooProvides methods and binds types provided by those methods to @Foo Type, @Foo can be restricted such that only the scanner can provide bindings annotated with @Foo.

Clone this wiki locally