Here's my problem:
It's first important to know that I'm writing a simulation. This is a standalone application, and is single-threaded. I have essentially two classes of objects that have different scoping requirements.
Classes that should be used as singletons throughout the entire simulation. An instance of Random, as an example.
Groups of classes that are created together, and within the group, each instance should be treated like a Singleton. For example, say RootObject
is the top level class, and has a dependency to ClassA
and ClassB
, both of which have a dependency to ClassD
. For any given RootObject
, both of its dependencies (ClassA
and ClassB
) should depend on the same instance of ClassD
. However, instances of ClassD
should not be shared across different instances of RootObject
.
Hopefully that makes sense. I can think of two approaches to this. One is to mark all of the injected objects as Singletons, create the root injector, and spin off a child injector each time I need to create a new RootObject
instance. Then, the instances of RootObject
and all of its dependencies are created as Singletons, but that scoping information is thrown away the next time I go to create another RootObject
.
The second approach is to implement some type of custom scope.
The Guice documentation gives conflicting advice... On one hand, it says that you should have a single injector, and that ideally it is called once to create some top level class. On the other hand, it says to stay away from custom scopes.
Guice provides special syntax for making an object that has the singleton scope, and is initialized to eager mode rather than lazy mode. Following is the syntax: bind(NotificationService. class).to(SMSService.
Guice comes with a built-in @Singleton scope that reuses the same instance during the lifetime of an application within a single injector.
In Guice, the @Inject annotation will be used to define setter-based as well as constructor-based dependency injection. An instance of this class is utilized to send notifications by means of the accessible correspondence services.
@Provides Methods When you need code to create an object, use an @Provides method. The method must be defined within a module, and it must have an @Provides annotation. The method's return type is the bound type. Whenever the injector needs an instance of that type, it will invoke the method.
It seems to me like you need a scope for each instance of RootObject
and all its dependencies.
In Guice you can create a custom scope, say @ObjectScoped
, like this:
@Target({ TYPE, METHOD }) @Retention(RUNTIME) @ScopeAnnotation public @interface ObjectScoped {}
Now just place RootObject
, A
, B
and D
into this scope:
@ObjectScoped public class RootObject { private A a; private B b; @Inject public RootObject(A a, B b) { this.a = a; this.b = b; } public A getA() { return a; } public B getB() { return b; } } @ObjectScoped public class A { private D d; @Inject public A(D d) { this.d = d; } public D getD() { return d; } } // The same for B and D
Now each RootObject
has its own scope. You can implement this as a simple HashMap
:
public class ObjectScope { private Map<Key<?>,Object> store = new HashMap<Key<?>,Object>(); @SuppressWarnings("unchecked") public <T> T get(Key<T> key) { return (T)store.get(key); } public <T> void set(Key<T> key, T instance) { store.put(key, instance); } }
To integrate these scopes with Guice you will need a com.google.inject.Scope
-implementation which lets you switch the scopes and the corresponding wiring in your Module
.
public class GuiceObjectScope implements Scope { // Make this a ThreadLocal for multithreading. private ObjectScope current = null; @Override public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) { return new Provider<T>() { @Override public T get() { // Lookup instance T instance = current.get(key); if (instance==null) { // Create instance instance = unscoped.get(); current.set(key, instance); } return instance; } }; } public void enter(ObjectScope scope) { current = scope; } public void leave() { current = null; } } public class ExampleModule extends AbstractModule { private GuiceObjectScope objectScope = new GuiceObjectScope(); @Override protected void configure() { bindScope(ObjectScoped.class, objectScope); // your bindings } public GuiceObjectScope getObjectScope() { return objectScope; } }
Initialize your program like this:
ExampleModule module = new ExampleModule(); Injector injector = Guice.createInjector(module); GuiceObjectScope objectScope = module.getObjectScope();
Create the first instance of RootObject
and its corresponding scope:
ObjectScope obj1 = new ObjectScope(); objectScope.enter(obj1); RootObject rootObject1 = injector.getInstance(RootObject.class); objectScope.leave();
Just switch the scope for a second group of objects:
ObjectScope obj2 = new ObjectScope(); objectScope.enter(obj2); RootObject rootObject2 = injector.getInstance(RootObject.class); objectScope.leave();
Test if your requirements are met:
assert rootObject1 != rootObject2; assert rootObject1.getA() != rootObject2.getA(); assert rootObject1.getA().getD() == rootObject1.getB().getD(); assert rootObject1.getA().getD() != rootObject2.getB().getD();
To work with a group of objects just enter its scope and use the injector:
objectScope.enter(obj1); B b1 = injector.getInstance(B.class); objectScope.leave(); assert rootObject1.getB() == b1;
With a little setup, Guice can provide two-tier scoping without a custom scope. The outer one is @Singleton
, and the inner is @RequestScoped
, provided by the servlet
extension. This works even if you're talking about something other than a Java EE servlet container.
Have a single, root-level injector to handle your singletons. Be sure to declare the request scope annotation in your root-level module as so:
public class RootModule extends AbstractModule { @Override protected void configure() { // Tell guice about the request scope, so that we can use @RequestScoped bindScope(RequestScoped.class, ServletScopes.REQUEST); } }
When you want to enter a sub-scope, you do this:
private void scopeAndInject(final Object perRequestSeed) { try { ServletScopes.scopeRequest(new Callable<Void>() { public Void call() { Injector requestScoped = getRootInjector().createChildInjector( new AbstractModule() { @Override protected void configure() { bind(Object.class).toInstance(perRequestSeed); } } ); requestScoped.get(Something.class); return null; } }, new HashMap<Key<?>, Object>()).call(); } catch (Exception e) { throw new RuntimeException(e); } }
What we're doing here is using ServletScopes.scopeRequest
to run the anonymous Callable
inside a new request scope. The Callable
then creates a child injector and adds a new binding for any per-request seed objects.
The seeds are objects that @RequestScoped
things would need but couldn't be created by Guice alone, like requests or iteration IDs. The new HashMap
passed as the second argument to scopeRequest
is another way to literally insert seeds into the new scope. I prefer the submodule way, since the bindings for the seeded values are always required anyway.
The child injector is then "in" the request scope and can be used to provide @RequestScoped
things.
See this also: How to use ServletScopes.scopeRequest() and ServletScopes.continueRequest()?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With