【问题标题】:How to initialize injected value before REST call?如何在 REST 调用之前初始化注入的值?
【发布时间】:2016-02-11 16:27:08
【问题描述】:

我有一个依赖与 HBase 的重量级连接的 JAX-RS 应用程序(在 Tomcat 上运行 Jersey 2)。我想在我的应用程序中为多个资源初始化和重用该连接。我已经设置了一个将连接绑定为 Singleton 的 Binder,并使用 @Inject 注释将该连接注入到我的资源中。但是,由于在第一次调用服务之前不会发生注入,因此直到那时才初始化连接。

应用程序:

public class MyApplication extends ResourceConfig {

    public MyApplication() {
        super(MyResource.class);
        register(new HbaseBinder());
    }
}

粘合剂:

public class HbaseBinder extends AbstractBinder {

    @Override
    protected void configure() {
        bindAsContract(HbaseConnection.class).in(Singleton.class);
        bind(new HbaseConnection()).to(HbaseConnection.class);
    }
}

注射:

@Path("/myResource")
public class MyResource {

    @Inject
    private HbaseConnection hbaseConnection;

    ...
}

HBase 连接:

@Singleton
public class HbaseConnection {    
    public Connection getConnection() throws IOException {
        ...
    }
    ...
}

我想做的是在应用程序部署时初始化单例,以便它准备好继续第一次调用服务。这样做的正确方法是什么?

【问题讨论】:

  • 我想当你实例化它时它会初始化。是因为整个 Jersey 应用程序直到第一个请求才加载,还是只是连接直到第一个请求才加载?
  • 我知道应用程序已加载,因为我已经能够添加一个 ServletContextListener 并在部署时执行它。但是 HBase 连接直到注入时间才被实例化,直到第一次调用 myResource 端点。
  • 尝试使用Immediate Scope
  • 我也不确定调用 ServletContextListener 是否表明 Jersey 运行时已启动。您可以通过在 ResourceConfig 构造函数中添加日志/打印来简单地检查。如果它没有加载,您可以为 Jersey servlet 设置启动时加载。您需要在 web.xml 中执行此操作
  • 即时作用域是一种矫枉过正,尽管它会起作用。此外,您可能不喜欢 HK2 在取消部署应用程序时留下的线程(不幸的是,就测试而言,此功能/行为仍然存在于 HK2 中)。没关系,配置 HK2 就足够了,正如我在下面解释的那样,以获得所需的急切单例初始化。

标签: web-services rest dependency-injection jersey jax-rs


【解决方案1】:

感谢您提供的所有 cmets 和答案。需要以上的组合。

部分问题是我使用@PostConstruct 来调用 HbaseConnection.getConnection() 方法有一个未经检查的异常。一旦我摆脱了这一点,并切换到即时范围,该类似乎已正确加载。这是我的最终解决方案:

public class MyApplication extends ResourceConfig {

    @Inject
    public MyApplication(ServiceLocator locator) {
        super(MyResource.class);
        register(new HbaseBinder());
        ServiceLocatorUtilities.enableImmediateScope(locator);
    }
}

@Path("/myResource")
public class MyResource {    

    @Inject
    private HbaseConnection hbaseConnection;

    ...
}

public class HbaseBinder extends AbstractBinder {

    @Override
    protected void configure() {
        bindAsContract(HbaseConnection.class).in(Immediate.class);
    }
}

@Immediate
public class HbaseConnection {

    @PostConstruct
    public void postConstruct() {
        // Call getConnection(), wrapped in try/catch.
    }

    public Connection getConnection() throws IOException {
        // Get the connection.
    }

    @PreDestroy
    public void preDestroy() {
        // Call cleanup(), wrapped in try/catch.
    }

    public void cleanup() throws IOException {
        // Close/cleanup the connection
    }
}

现在我唯一的问题是,有些线程/ThreadLocals 似乎在取消部署时留下了,但这一定是我正在使用的库中的错误,因为我无法控制它们的生命周期。

12-Feb-2016 14:01:45.054 INFO [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.startup.HostConfig.undeploy Undeploying context [/my-resource]
12-Feb-2016 14:01:46.129 WARNING [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [my-resource] appears to have started a thread named [ImmediateThread-1455303641406] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 sun.misc.Unsafe.park(Native Method)
 java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
 java.util.concurrent.SynchronousQueue$TransferQueue.awaitFulfill(SynchronousQueue.java:764)
 java.util.concurrent.SynchronousQueue$TransferQueue.transfer(SynchronousQueue.java:695)
 java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:941)
 java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1066)
 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 java.lang.Thread.run(Thread.java:745)
12-Feb-2016 14:01:46.130 WARNING [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [my-resource] appears to have started a thread named [Thread-5] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 sun.net.dns.ResolverConfigurationImpl.notifyAddrChange0(Native Method)
 sun.net.dns.ResolverConfigurationImpl$AddressChangeListener.run(ResolverConfigurationImpl.java:144)
12-Feb-2016 14:01:46.134 SEVERE [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [my-resource] created a ThreadLocal with key of type [org.apache.htrace.core.Tracer.ThreadLocalContext] (value [org.apache.htrace.core.Tracer$ThreadLocalContext@2c25bbe0]) and a value of type [org.apache.htrace.core.Tracer.ThreadContext] (value [org.apache.htrace.core.Tracer$ThreadContext@787153ae]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
12-Feb-2016 14:01:46.135 SEVERE [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [my-resource] created a ThreadLocal with key of type [org.apache.hadoop.io.Text$1] (value [org.apache.hadoop.io.Text$1@1badb836]) and a value of type [sun.nio.cs.UTF_8.Encoder] (value [sun.nio.cs.UTF_8$Encoder@13652d32]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
12-Feb-2016 14:01:46.136 SEVERE [ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [my-resource] created a ThreadLocal with key of type [org.apache.hadoop.hdfs.DFSUtil$1] (value [org.apache.hadoop.hdfs.DFSUtil$1@6080a3db]) and a value of type [java.util.Random] (value [java.util.Random@169acbf5]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
12-Feb-2016 14:03:02.383 INFO [Thread-7] org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading Illegal access: this web application instance has been stopped already. Could not load [org.apache.hadoop.util.ShutdownHookManager$2]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
 java.lang.IllegalStateException: Illegal access: this web application instance has been stopped already. Could not load [org.apache.hadoop.util.ShutdownHookManager$2]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
    at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1353)
    at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForClassLoading(WebappClassLoaderBase.java:1341)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1206)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1167)
    at org.apache.hadoop.util.ShutdownHookManager.getShutdownHooksInOrder(ShutdownHookManager.java:124)
    at org.apache.hadoop.util.ShutdownHookManager$1.run(ShutdownHookManager.java:52)

12-Feb-2016 14:03:02.383 INFO [Thread-4] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-apr-8080"]
12-Feb-2016 14:03:02.449 INFO [Thread-4] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["ajp-apr-8009"]
12-Feb-2016 14:03:02.500 INFO [Thread-4] org.apache.catalina.core.StandardService.stopInternal Stopping service Catalina
12-Feb-2016 14:03:02.530 INFO [Thread-4] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["http-apr-8080"]
12-Feb-2016 14:03:02.581 INFO [Thread-4] org.apache.coyote.AbstractProtocol.stop Stopping ProtocolHandler ["ajp-apr-8009"]

【讨论】:

  • 正如我在上面的评论中所说,直接作用域会留下线程。如果您愿意,您可以查看它并通过改进为 hk2 做出贡献。 :) 它正在java.net/jira/browse/JERSEY-2291 进行跟踪(至少从球衣一侧)。
  • @peeskillet 我不明白你的意思。布赖恩说,线程在取消部署时会留下来,我是说,对此没有简单的解决方案,我也鼓励他为 hk2 做出贡献。是的,这就是为什么在泽西岛默认没有启用它的原因..
  • @peeskillet 你到底是谁?您是否参与了 ​​JAX-RS 社区或泽西岛的开发?
【解决方案2】:

您的HbaseConnection 被初始化两次。

  • 首先,它在您的活页夹中初始化(前提是您的 MyApplication 类在您的 web.xml 中注册为 init-param
  • 然后,当HK2检测到@Singleton注解(或者当它执行bindAsContract指令时)再次初始化

因此,要使您的示例正常工作,您需要:

  1. HbaseConnection 类中删除@Singleton 注释。
  2. HBaseBinder 类中删除行 bindAsContract(HbaseConnection.class).in(Singleton.class);

MyApplication 的初始化发生在 servlet 容器启动期间(因此构造了HbaseConnection),这正是您想要的,对吧?

是您提供实例(在活页夹中),而不是 HK2。这就是为什么您不想通过使用@Singleton 来指示它来创建实例的原因。

活页夹应该是:

public class HbaseBinder extends AbstractBinder {

    @Override
    protected void configure() {
        // just bind is ok
        bind(new HbaseConnection()).to(HbaseConnection.class);
    }
}

还有 HBase 连接:

// do not annotate with `@Singleton`!
public class HbaseConnection {    
    public Connection getConnection() throws IOException {
        ...
    }
    ...
}

【讨论】:

  • 这行得通,但是如果我想在容器关闭之前使用 PreDestroy 注释来清理连接怎么办?除非 HbaseConnection 使用 Singleton 注释,否则似乎不会调用我的 PreDestroy。
猜你喜欢
  • 1970-01-01
  • 2013-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-29
  • 1970-01-01
相关资源
最近更新 更多