Custom Guice Scope, or a better approach?

后端 未结 4 1477
迷失自我
迷失自我 2020-12-13 14:55

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

相关标签:
4条回答
  • 2020-12-13 15:28

    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()?

    0 讨论(0)
  • 2020-12-13 15:29

    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:

    1. Bind it as a provider with either the @Provides interface or the .toProvider() binding decoration.
    2. Inject the provider directly and invoke it to create RootObject instances as needed.

    Hope that this helps.

    0 讨论(0)
  • 2020-12-13 15:43

    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;
    
    0 讨论(0)
  • 2020-12-13 15:45

    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.

    0 讨论(0)
提交回复
热议问题