Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dagger custom scopes, how to?

How to create custom scopes with Dagger?

Are there guidelines? I didn't find them.

I'm developing a Vaadin application and will need a custom scope. Something like UiScoped.

best regards

like image 725
Jako Avatar asked Jun 04 '13 10:06

Jako


People also ask

How do I make my own Dagger scope?

How You Can Create a Custom Scopes. create custom scopes with Dagger is pretty easy, you just have to follow these steps. Step 1) declare your annotation. Step 2) annotate the dependencies with this annotation in the module.

What is custom scope in Dagger?

A scope is an annotations class with specific additional annotations: @Scope and @Retention. @Scope annotation is provided by Dagger library to define custom scopes. In our example, we create two scopes: @ActivityScope (for activities) and @FragmentScope (for fragments).

What are scopes in Dagger 2?

Scope refers to the lifetime of an object. Consider an example of a simple class. largeScope — Its scope is tied to the lifetime of the class. It can be accessed anywhere in the class.

Are custom scopes allowed in Kotlin?

To use scopes, you just need to annotate a component and provision methods with @Singleton (or any other custom scopes).


1 Answers

Dagger doesn't do scope using the same sort of mechanism that Guice does. Dagger, specifically, does not transparently handle scope the way Guice does, with various scoping annotations, one Injector, and different instance caches behind the scenes. Instead, it uses two principles. First, that @Singleton means "one per graph" (the strictest interpretation of JSR-330) and, secondly, that graphs can be linked in a hierarchy.

Dagger uses this tree of hierarchically linked graphs, where you create a graph by adding more modules and extending it via the plus() method, to create a "scoped" graph which can be of a shorter lifetime. This similar to Child Injectors in guice. An important principle here is that instances in the extended graph can see instances in the originating graph, but not the reverse. So the concentric nature of the shorter life-time is mirrored in the visibility - a shorter-lived object can see (depend-upon) a longer lived object, but not the reverse. So an object that lives for the life of a request can see an object that lives for the life of an application, but not the reverse.

It is through this mechanism that one is expected to scope cached instances more narrowly.

If one configures a graph with some modules, and there is a singleton it will have one instance cached in that graph supplied to all dependent objects. If one creates an extension to that graph via the plus() method, configuring it with other modules that contain @Singleton annotated bindings, then these other modules would be one-per-graph... but one-per instance of the shorter-lived ObjectGraph instance.

For example, let's simulate a server that responds to requests, where we want some objects that live for the life of the app, and some objects which live only for the shorter-life of a request:

@Module()
public class MyAppModule {
  @Provides ConnectionDictonary connectionDictionary() {
    return new ConnectionDictonary(System.getProperty("some.property"));
  }

  /** Stateless mockable utilities for this app */
  @Provides Util util() { new Util(); }

  @Provides @Singleton DataStore store() { 
    return new DataStore();
  }

  @Provides @Singleton ConnectionPool pool(DataStore store, ConnectionDictionary dict) { 
    try {
      return DataStore.connectionPool(dict, System.getProperty("pool.size"));
    } catch (Exception e) {
      // bad bad bad
      throw new RuntimeException("Could not connect to datastore.", e);
    }
  }

}

// This module "adds to" MyAppModule by adding additional graph elements in
// an extended graph.
@Module(injects=MyRequestEndpoint.class, addsTo = MyAppModule.class)
public class MyRequestModule {
  private Request req;
  public MyRequestModule(Request req) { this.req = req; }

  @Provides @Singleton RequestObject request() { return req; }

  @Provides @Singleton User user(ConnectionPool pool, Request req, Util util) {
    try {
      Connection conn = pool.obtain();
      // getUser cannot throw null;
      return util.getUser(conn, req.get("user.id"), Crypto.hash(req.get("pass")));
    } catch (UserNotFoundException e) {
      return User.UNKNOWN;
    } catch (Exception e) {
      throw new RuntimeException("Could not obtain a user.", e);
    } finally { 
      // TODO: try-with-resources in Java7
      pool.release();
    }
  }

}

public class MyRequestEndpoint {
  @Inject ConnectionPool pool;
  @Inject Request req;

  public Output performService() {
    try {
      Connection conn = pool.obtain();
      // ... does stuff with request
    } finally {
      conn.release();
    }
  }
}

public class MyApp {    
  public void main(String ... args) {
    graph = ObjectGraph.create(MyAppModule.class);
    new ServiceListener(graph).start(); 
  }
}

public ServiceListener {
  private final ObjectGraph appGraph;
  public ServiceListener(ObjectGraph appGraph) {
    this.appGraph = appGraph;
  }

  //... infrastructure for listening and building request/response objects, etc.

  public void serveRequest(Request req, Response res) {
    // Take the application-scoped graph and create another graph we will 
    // use in this request and throw away.
    ObjectGraph requestGraph = MyApp.graph().plus(new MyRequestModule(req));
    Output output = requestGraph.get(MyRequestEndpoint.class).performService();
    Util.populateResult(output, result);
    result.flush();
  }
}

In this example, each MyRequestEndpoint would get a shared instance of ConnectionPool, but an endpoint in any two requests would get two different RequestObjects.

This is a somewhat silly example built off the top of my head on the J2EE pattern. Something this trivial you wouldn't structure this way, and you would need stronger scaffolding for a proper server model. Indeed, the Dagger project will likely do such a thing (though I respectfully recommend using injected service objects and a single dispatch servlet or filter).

But it hopefully illustrates a narrower scope in a familiar model

The key is not in the annotation, but in the lifetime of graphs. You create a shorter-lived graph as a "child" or "extension" of a longer-lived graph. Objects memoized in these graphs have the lifetimes (or scopes) of the graph-management objects.

like image 84
Christian Gruber Avatar answered Oct 27 '22 09:10

Christian Gruber