mrszj / google-guice

Automatically exported from code.google.com/p/google-guice
Apache License 2.0
0 stars 0 forks source link

Are Http scopes and scope implementations sufficiently general? #100

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
Are Http scopes and scope implementations sufficiently general?

I am very new to Guice so what I am proposing here may not be correct ...
but here it goes.

I was looking at the scope features (doc please ...) to figure how I could
use scopes in my applications. I noticed that the REQUEST and SESSION
scopes are very http Servlet oriented. I was wondering if this was
necessary.

I think that non http Servlet based application could benefit from general
purpose REQUEST and SESSION scopes. Http Servlet based application could
also benefit from this. So I am proposing create more general REQUEST and
SESSION scopes and to use these to implement http Servlet scoping.

First, I would define a general purpose class for thread based scope to
take over the non http Servlet API part of the GuiceFilter class. For
example:

public final class RequestScopeSetter {

    private RequestScopeSetter()
    {
        // Hide
    }

    private static final ThreadLocal<Map<String, Object>> REQUEST_CACHE 
        = new ThreadLocal<Map<String, Object>>();

    public static void beginScope()
    {
        REQUEST_CACHE.set(new HashMap<String, Object>());
    }

    public static void endScope()
    {
        REQUEST_CACHE.set(null);
    }

    static Map<String, Object> getCache() {
        Map<String, Object> cache = REQUEST_CACHE.get();
        if (cache == null) {
            throw new IllegalStateException(
                "Cannot access request scoped object. " +
                "Either we are not currently inside a " +
                "scoped thread, or you may have forgotten " + 
                "to initialize the thread with " +
                RequestScopeSetter.class.getName() +
                ".beginScope() method."
            );
        }
        return cache;
    }
}

Next I would modify the Filter class to use this mechanism (or define
another Filter):

  public void doFilter(ServletRequest servletRequest,
      ServletResponse servletResponse, FilterChain filterChain)
      throws IOException, ServletException {
    Context previous = localContext.get();
    try {
      localContext.set(new Context((HttpServletRequest) servletRequest,
          (HttpServletResponse) servletResponse));
      RequestScopeSetter.beginScope();
      try {
          filterChain.doFilter(servletRequest, servletResponse);
      } finally {
        RequestScopeSetter.endScope();
      }

    } finally {
      localContext.set(previous);
    }
  }

I would maybe define a Runnable for other uses:

class RequestScopeSetterRunnable implements Runnable {
    private final Runnable toRun;
    RequestScopeSetterRunnable(Runnable toRun)
    {
        this.toRun = toRun;
    }

    public void run()
    {
        RequestScopeSetter.beginScope();
        try {
            toRun.run();
        } finally {
            RequestScopeSetter.endScope();
        }
    }
}

The REQUEST scope would be implemented as:

final class Scopes {

    public static final Scope REQUEST = new Scope() {
        public <T> Provider<T> scope(Key<T> key, 
            final Provider<T> creator) {
            final String name = key.toString();
            return new Provider<T>() {
                public T get() {
                    Map<String, Object> threadCache 
                        = ThreadScopeSetter.getCache();
                    synchronized (threadCache) {
                        @SuppressWarnings("unchecked")
                        T t = (T) threadCache.get(name);
                        if (t == null) {
                            t = creator.get();
                            threadCache.put(name, t);
                        }
                        return t;
                    }
                }

                public String toString() {
                    return String.format("%s[%s]", creator, REQUEST);
                }
            };
        }

        public String toString() {
            return "Scopes.REQUEST";
        }
    };

// ...

This change would allow to reuse scopes, bindings and annotations for both
http Servlet and non-http Servlet (or a mix of both) code. Is this
feasible ?

Original issue reported on code.google.com by cque...@gmail.com on 4 May 2007 at 1:49

GoogleCodeExporter commented 9 years ago
I believe we may have made a mistake in locating the @RequestScoped and
@SessionScoped annotations in com.google.inject.servlet.  The concepts they 
represent
should not have been tied to the servlet concept.  Our ServletScopes 
implementations
for these are servlet-specific, of course.  But this would let you bind those 
same
annotations to whatever you please without having to import from
com.google.inject.servlet.

Still, I don't know what we can do about this now.

Original comment by kevin...@gmail.com on 15 May 2007 at 4:57

GoogleCodeExporter commented 9 years ago
I suppose the best we can do is fix the documentation on those two annotations 
and
move them to the main jar file.  But we can't change the package. :(

Original comment by kevin...@gmail.com on 3 Jun 2007 at 6:34

GoogleCodeExporter commented 9 years ago
Do we really need "request" and "session" scope outside of a web application?

Original comment by crazybob...@gmail.com on 3 Jun 2007 at 6:41

GoogleCodeExporter commented 9 years ago
Why wouldn't we?  Perhaps it just has an RMI or other RPC interface.  And 
perhaps it
wants to reuse modules that are also used by a web front-end.  It sounds totally
plausible to me.  How or why they'd use Session is a mite questionable, of 
course.

Original comment by kevin...@gmail.com on 3 Jun 2007 at 6:48

GoogleCodeExporter commented 9 years ago
When I said "really," I meant "really," as in does somebody really need this 
badly
enough to futz up the API? Because if they do, we will. I'm not sure "request" 
is the
right word for RPC calls anyway.

Original comment by crazybob...@gmail.com on 3 Jun 2007 at 7:00

GoogleCodeExporter commented 9 years ago
I personally need request and thread scopes to be the same. I don't have an 
issue
with session (I included it for generality only). Our web application (probably
others also) has a scheduler to execute tasks. Much of the code used in tasks is
common to http requests. I.e. there is not much difference between request 
performing
certain actions and a scheduler thread performing other actions. In fact most 
of the
code is Servlet unaware.

Original comment by cque...@gmail.com on 5 Jun 2007 at 5:56

GoogleCodeExporter commented 9 years ago
+1 for the addition of a thread scope. I also created my own, so that makes 
two...
I use it to scope objects in a per request security context. To make my code 
work in 
a standalone scenario, I can't live without that thread scope. I'll take a look 
if I 
can donate some of my code, but it obviously looks similar to cquezel's.

I wouldn't bother with session scope either.

Original comment by robbie.v...@gmail.com on 6 Jun 2007 at 7:49

GoogleCodeExporter commented 9 years ago
My code attached, including simple unit test. Feedback appreciated.

Original comment by robbie.v...@gmail.com on 7 Jun 2007 at 11:40

Attachments:

GoogleCodeExporter commented 9 years ago
Blogged about it (includes code example):
http://garbagecollected.wordpress.com/2007/06/07/guice-thread-scope/

Original comment by robbie.v...@gmail.com on 7 Jun 2007 at 12:37

GoogleCodeExporter commented 9 years ago
Guice is very flexible, you can add any scope you want.

Original comment by feng.ronald@gmail.com on 7 Jun 2007 at 12:45

GoogleCodeExporter commented 9 years ago
Wait a minute -- you say "thread scope", but then you say "per request".  Which 
is
it?  Is it a request scope, or a thread scope?  A "thread scope" would be 
something
that lives for the entire lifetime of the thread.

Original comment by kevin...@gmail.com on 7 Jun 2007 at 4:28

GoogleCodeExporter commented 9 years ago
It could be both, actually. If you don't reset it, it lives as long as the 
thread, 
and if you do, it could live as long as a request. Pure thread scoping probably 
isn't always what you want if you do thread pooling (or if your 
ejb/web/whatever 
container does). So the scope is thread, or less. Does that make sense?

Maybe the upcoming conversation scope addresses some of this? (the "or less" 
part?)

Original comment by robbie.v...@gmail.com on 7 Jun 2007 at 5:09

GoogleCodeExporter commented 9 years ago
I don't think there's any value in the concept of "resetting" a scope.  If you 
want
it to live as long as the thread does (e.g. you just want to reuse 
SimpleDateFormat
instances), that's thread scope; if you want the lifetime to be shorter, you 
want
your own Request or Job or Task or UnitOfWork or Foo scope.

Original comment by kevin...@gmail.com on 7 Jun 2007 at 5:34

GoogleCodeExporter commented 9 years ago
Thanks Kevin: you're right. What I implemented is a UnitOfWork scope that, in 
my 
specific scenario, is able to replace the HTTP Request scope. And if you don't 
use 
the reset() method in my code, it will behave exactly like a thread scope.

I gave all this some more thought, and I agree that it doesn't make sense to 
add an 
HTTP Request scope replacement to Guice. What we need is a Conversation scope 
that 
isn't web specific. How will you guys implement Conversation scope?

Thread scope could still be useful, and perhaps we should raise a new issue for 
that 
one.

Original comment by robbie.v...@gmail.com on 7 Jun 2007 at 6:41

GoogleCodeExporter commented 9 years ago
Created issue #100 for the Thread scope thing.

Original comment by robbie.v...@gmail.com on 8 Jun 2007 at 10:27

GoogleCodeExporter commented 9 years ago
Argh.. issue #114, I mean.

Original comment by robbie.v...@gmail.com on 8 Jun 2007 at 10:28

GoogleCodeExporter commented 9 years ago
I've been able to get rid of my use case for this issue, after changing my
application to use pure Thread scope as implemented in issue #114.

First, I define an interface like this:

public interface GuiceCallable<V> {
    public V call(Injector injector);
}

And then I use a template class that creates a thread per method invocation:

public class GuiceTemplate {
    ...
    public <V> V call(final GuiceCallable<V> c) {
        Future<V> future = Executors.newSingleThreadExecutor().submit(
            new Callable<V>() {
                public V call() {
                    return c.call(this.injector);
                }
            }
        );
        try {
            return future.get();
        } catch (InterruptedException e) {
            ...
        } catch (ExecutionException e) {
            ...
        }
    }
}

So I guess this is an extra use case for issue #114.

Original comment by robbie.v...@gmail.com on 11 Jun 2007 at 2:50

GoogleCodeExporter commented 9 years ago
I think this issue can be closed.

My summary:
- HTTP Request scope can be replaced by using THREAD scope. The only caveat I 
spot 
is when one creates threads within the template; people who need that could 
implement their own THREAD scope with InheritableThreadLocal, or create a 
custom 
solution.
- An HTTP Session scope replacement is probably less important. For a lot of 
standalone (as in Swing, ...) applications, SINGLETON will do just fine because 
only 
one application instance will run at a time in a single JVM.
- As for the Conversation scope for non web apps (which I mentioned), it will 
be 
hard to implement a solution that is generic enough for most people to use. But 
it 
would be really, really cool.

Migration from HTTP to THREAD/... can be as easy as replacing some bindings, 
and 
perhaps rewriting one class. That's not too bad.

Original comment by robbie.v...@gmail.com on 11 Jun 2007 at 10:04

GoogleCodeExporter commented 9 years ago
Robbie summarized this nicely. We're going to leave everything as-is. Issue 114 
will cover thread scope, 
inheritable thread scope etc., and whether they should be included.

I think the best takeaway on this issue is that we should write a how-to doc 
that walks through implementing a 
custom scope.

Original comment by limpbizkit on 31 May 2008 at 7:23