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.
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()?
Have you considered using a provider? It would be easy to write one that meets your requirements, e.g.:
import com.google.inject.Provider
class RootObjectProvider implements Provider<RootObject> {
...
@Override
RootObject get() {
ClassD d = new ClassD( .... );
ClassB b = new ClassB( ..., d, ...);
ClassC c = new ClassC( ..., d, ...); // Note that b and c share d.
return new RootObject(b, c, ...);
}
}
You can use the provider two ways:
- Bind it as a provider with either the
@Provides
interface or the .toProvider()
binding decoration.
- Inject the provider directly and invoke it to create
RootObject
instances as needed.
Hope that this helps.
May I ask why do you need to have singletons ?
I would not recommend to create custom scope. The best and easiest way to mix scopes is to inject providers instead of objects. With the providers you can control the scope of your object from you business code logic.
See this Guice documentation for details.