19

Let's say I have a Closeable object injected through Guice using request scope:

@Provides @RequestScoped
public MyCloseableResource providesMyCloseableResource(){
  return new MyCloseableResourceImpl();
}

Is it possible to hook-up a clean-up method that would automatically call close() on my resource when the scope exists, without resorting to custom scope implementation?

Looking at the custom scope implementation guide on the Guice wiki, it shows that scopes should be created and cleaned up like this:

/**
 * Runs {@code runnable} in batch scope.
 */
public void scopeRunnable(Runnable runnable) {
  scope.enter();
  try {
    // explicitly seed some seed objects...
    scope.seed(Key.get(SomeObject.class), someObject);
    // create and access scoped objects
    runnable.run();
  } finally {
    scope.exit();
  }
}

I am wondering if there is way to hook-up some custom clean-up code in the finally of the built-in scopes (especially session and request scopes).

If it isn't possible, might there be issues that would discourage this kind of automatic clean-up?

I have found ways of achieving the same effect in servlet containers by implementing a Filter to create and clean-up a resource per request, which works great but I am curious if it is possibly with pure Guice.

Community
  • 1
  • 1
rodion
  • 14,729
  • 3
  • 53
  • 55

1 Answers1

5

I faced a similar problem myself and finally rolled a Disposable interface which offers nothing but a public void dispose() method. I find this especially valuable for classes that register listeners somewhere and need to unregister them at a defined time. What I already had was my AttributeHolderScope which I blogged about so I won't repeat that part here. The only thing that is missing now is the AbstractAttributeHolder which looks like this:

/**
 * An anstract base class for implementing the {@link AttributeHolder}
 * interface which has an implementation of the attribute related methods.
 *
 * @author Matthias Treydte <waldheinz at gmail.com>
 */
public abstract class AbstractAttributeHolder
        implements AttributeHolder, Disposable {

    private final Object lock = new Object();
    private transient Map<Object, Object> attributes;

    public AbstractAttributeHolder() {
        this.attributes = new HashMap<Object, Object>();
    }

    public void replaceAttributes(Map<Object, Object> newAttr) {
        synchronized (getAttributeLock()){
            this.attributes = newAttr;
        }
    }

    @Override
    public Object getAttributeLock() {
        return this.lock;
    }

    @Override
    public final void putAttribute(Object key, Object value) {
        synchronized (getAttributeLock()) {
            attributes.put(key, value);
        }
    }

    @Override
    public final boolean hasAttribute(Object key) {
        synchronized (getAttributeLock()) {
            return attributes.containsKey(key);
        }
    }

    @Override
    public final Object getAttribute(Object key) {
        synchronized (getAttributeLock()) {
            return attributes.get(key);
        }
    }

    @Override
    public final Set<Object> getAttributes() {
        synchronized (getAttributeLock()) {
            return Collections.unmodifiableSet(
                    new HashSet<Object>(this.attributes.values()));
        }
    }

    @Override
    public void dispose() {
        synchronized (this.getAttributeLock()) {
            for (Object o : this.attributes.values()) {
                if (o instanceof Disposable) {
                    final Disposable d = (Disposable) o;
                    d.dispose();
                }
            }

            this.attributes.clear();
        }
    }
}

This class itself implements Disposable so you can have nested scopes and when you dispose an outer scope, all nested scopes and, more importantly, all injected instances that implement Disposable get cleaned up. And to precisely answer you question: I don't think that this is possible with the Scope implementations provided by Guice itself, but it can be done. Everytime I look at this code I ask myself if this can't be done in a more concise way, but then it works beautifully (at least for me).

Gregor Koukkoullis
  • 2,255
  • 1
  • 21
  • 31
Waldheinz
  • 10,399
  • 3
  • 31
  • 61
  • An extendible scope implementation, very good idea! So to use this with request scope, I would create `MyRequestScope` extending your scope class from the blog, wire it into the same place as the default `RequestScope` (the tricky part), do `scope.enter()` with `AbstractAttributeHolder` and make sure I call `dispose()` in the finally (or in the `exit()` of `MyRequestScope`)? If you have a nice way of integrating this could you share it (the code and/or techniques) please? – rodion Jan 09 '11 at 03:08
  • Regarding the synchronization related code, it seems unnecessary because `AttributeHolder` is always accessed from `ThreadLocal` hence one unique instance per thread. Am I missing something? – rodion Jan 09 '11 at 03:11
  • 1) I don't use this in an Servlet environment, so I am not sure how to do this properly. – Waldheinz Jan 10 '11 at 08:41
  • 2) It depends how you use the code, I need the synchronization because for me at any given time there may be more than one Thread in a Scope so the synchronization is needed I think. – Waldheinz Jan 10 '11 at 08:43
  • 1) Thanks! Shame about the lack of automatic clean-up in scopes. Could have been useful. Anyway, your approach seems to work well in general! The integration can be done using a servlet filter, like one described here, if anyone is interested: http://blog.yanivkessler.com/2010/06/lightweight-jdo-persistence-filter.html – rodion Jan 10 '11 at 12:07
  • 2) With regard to `synchronized(ah.getAttributeLock()){...}` inside the `scope(key,outer)` method: because you do `ah=holder.get()` just before it, it ''should'' always return a different instance per-thread (see java-doc), therefore no two threads will ever be blocked by following `synchronized` block. If `holder.get()` may return the same instance for different threads, then using `ThreadLocal` here is misleading. I guess, the only possible scenario is where `ah.getAttributeLock()` returns a shared lock object for different instances, but surely that's just making things too complicated:))) – rodion Jan 10 '11 at 12:24
  • I wrote lines about this and the synchronization here: http://waldheinz.de/2011/01/cleaning-up-resources-with-guice/ -- if you think I'm wrong about this please comment there or here, but I think some synchronization is needed, possibly even "more" than what I have now. – Waldheinz Jan 10 '11 at 13:15