如何使用ServletScopes.scopeRequest()和ServletScopes.continueRequest()?

Gil*_*ili 8 java guice

  1. 一个人应该如何使用ServletScopes.scopeRequest()
  2. 如何获取@RequestScopedCallable中对象的引用?
  3. 有什么意义seedMap?是否意味着覆盖默认绑定?
  4. 这个方法与ServletScopes.continueRequest()有什么区别?

Gil*_*ili 12

回答我自己的问题:

  1. ServletScopes.scopeRequest()新的请求范围内运行Callable .注意不要跨不同的范围引用对象,否则最终会遇到线程问题,例如尝试使用已被其他请求关闭的数据库连接.static或者顶级课程是你的朋友.
  2. 在将其Callable传递给ServletScopes.scopeRequest()之前注入.因此,您必须小心Callable包含哪些字段.更多关于此的信息.
  3. seedMap允许您将非范围对象注入范围.这很危险,所以要注意你注射的东西.
  4. ServletScopes.continueRequest()类似,只是它在现有的请求范围内运行.它获取当前HTTP范围的快照并将其包装在Callable中.原始HTTP请求完成(您从服务器返回一些响应),然后在单独的线程中异步完成实际操作.当稍后调用Callable(在该单独的线程中)时,它将有权访问原始的HttpServletRequest,但不能访问HTTP响应或会话.

那么,最好的方法是什么?

如果您不需要将用户对象传递到Callable:注入Callable请求范围外部,并将其传递给ServletScopes.scopeRequest().该Callable只可参考Provider<Foo>,而不是Foo,否则你会与请求范围之外注入情况下结束.

如果您需要将用户对象传递给Callable,请继续阅读.

假设您有一个将名称插入数据库的方法.我们有两种方式将名称传递给Callable.

方法1:使用子模块传递用户对象:

  1. 定义InsertName,Callable插入数据库的a:

    @RequestScoped
    private static class InsertName implements Callable<Boolean>
    {
      private final String name;
      private final Connection connection;
    
      @Inject
      public InsertName(@Named("name") String name, Connection connection)
      {
        this.name = name;
        this.connection = connection;
      }
    
      @Override
      public Boolean call()
      {
        try
        {
          boolean nameAlreadyExists = ...;
          if (!nameAlreadyExists)
          {
            // insert the name
            return true;
          }
          return false;
        }
        finally
        {
          connection.close();
        }
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 绑定子模块中的所有用户对象,并使用RequestInjector.scopeRequest()来调用可调用范围:

    requestInjector.scopeRequest(InsertName.class, new AbstractModule()
    {
      @Override
      protected void configure()
      {
        bind(String.class).annotatedWith(Names.named("name")).toInstance("John");
      }
    })
    
    Run Code Online (Sandbox Code Playgroud)
  3. 我们实例化一个RequestInjector请求外,它,反过来,注射第二Callable 内部请求.第二个Callable可以Foo直接引用(不需要提供者),因为它是在请求范围内注入的.

import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.servlet.ServletScopes;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * Injects a Callable into a non-HTTP request scope.
 * <p/>
 * @author Gili Tzabari
 */
public final class RequestInjector
{
    private final Map<Key<?>, Object> seedMap = Collections.emptyMap();
    private final Injector injector;

    /**
     * Creates a new RequestInjector.
     */
    @Inject
    private RequestInjector(Injector injector)
    {
        this.injector = injector;
    }

    /**
     * Scopes a Callable in a non-HTTP request scope.
     * <p/>
     * @param <V> the type of object returned by the Callable
     * @param callable the class to inject and execute in the request scope
     * @param modules additional modules to install into the request scope
     * @return a wrapper that invokes delegate in the request scope
     */
    public <V> Callable<V> scopeRequest(final Class<? extends Callable<V>> callable,
        final Module... modules)
    {
        Preconditions.checkNotNull(callable, "callable may not be null");

        return ServletScopes.scopeRequest(new Callable<V>()
        {
            @Override
            public V call() throws Exception
            {
                return injector.createChildInjector(modules).getInstance(callable).call();
            }
        }, seedMap);
    }
}
Run Code Online (Sandbox Code Playgroud)

方法2:Callable在引用的请求之外注入Provider<Foo>.call()然后get(),该方法可以是请求范围内的实际值.对象对象通过a传递seedMap(我个人觉得这种方法反直觉):

  1. 定义InsertName,Callable插入数据库的a.请注意,与方法1不同,我们必须使用Providers:

    @RequestScoped
    private static class InsertName implements Callable<Boolean>
    {
      private final Provider<String> name;
      private final Provider<Connection> connection;
    
      @Inject
      public InsertName(@Named("name") Provider<String> name, Provider<Connection> connection)
      {
        this.name = name;
        this.connection = connection;
      }
    
      @Override
      public Boolean call()
      {
        try
        {
          boolean nameAlreadyExists = ...;
          if (!nameAlreadyExists)
          {
            // insert the name
            return true;
          }
          return false;
        }
        finally
        {
          connection.close();
        }
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 为您要传入的类型创建伪造的绑定.如果不这样做,您将获得:No implementation for String annotated with @com.google.inject.name.Named(value=name) was bound. https://stackoverflow.com/a/9014552/14731解释了为什么需要这样做.

  3. 使用所需的值填充seedMap:

    ImmutableMap<Key<?>, Object> seedMap = ImmutableMap.<Key<?>, Object>of(Key.get(String.class, Names.named("name")), "john");
    
    Run Code Online (Sandbox Code Playgroud)
  4. 调用ServletScopes.scopeRequest():

    ServletScopes.scopeRequest(injector.getInstance(InsertName.class), seedMap);
    
    Run Code Online (Sandbox Code Playgroud)