使用 HK2 将休眠会话注入 Jersey

Injecting hibernate session to Jersey using HK2

我正在开发小应用程序并遇到一些 DI 问题。

我有一个存储库 class 用于保存我注入到服务中的实体。我想使用 H2K 向它注入 Session 对象。 为此,我尝试做以下 SO 帖子中描述的类似操作:

  1. Jersey + HK2 + Grizzly: Proper way to inject EntityManager?
  2. Using Jersey 2.0, how do you register a bindable instance per request?

所以我创建了 SFFactory class 并在 ApplicationConfig 中注册它。

SFFactory.java

import org.glassfish.hk2.api.Factory;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

public class SFFactory implements Factory<Session> {

private SessionFactory factory;

public SFFactory() {
    Configuration configuration = new Configuration();
    configuration.configure("hibernate.cfg.xml");
    StandardServiceRegistryBuilder srBuilder = new StandardServiceRegistryBuilder();
    srBuilder.applySettings(configuration.getProperties());
    factory = configuration.buildSessionFactory(srBuilder.build());
    }

@Override
public Session provide() {
    return factory.openSession();
    }

@Override
public void dispose(Session session) {
    if (session.isOpen()) {
        session.close();
        }
    }
}

ApplicationConfig.java

import org.alexdzot.phonettesttask.repository.MessageRepository;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.hibernate.Session;

import javax.inject.Singleton;
import javax.ws.rs.ApplicationPath;

@ApplicationPath("/rest/*")
public class ApplicationConfig extends ResourceConfig {

public ApplicationConfig() {
    packages("org.alexdzot.phonettesttask");
    register(new AbstractBinder() {
        @Override
        protected void configure() {
            bindFactory(SFFactory.class).to(Session.class);
            bindFactory(MessageRepositoryFactory.class).to(MessageRepository.class).in(Singleton.class);
            }
        });
    }
}

DefaultMessageRepository

import org.alexdzot.phonettesttask.model.Message;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.jvnet.hk2.annotations.Service;

import javax.inject.Inject;
import java.util.List;

@Service
public class DefaultMessageRepository implements MessageRepository {

@Inject
private Session session;

public void saveMessage(Message message) {
    Session session = null;
    Transaction tx = null;
    try {
        tx = session.beginTransaction();
        session.save(message);
        tx.commit();
        } catch (Exception e) {
        tx.rollback();
        } finally {
        session.close();
        }
    }

public List<Message> getAllMessages() {
    Session session = null;
    Transaction tx = null;
    List<Message> messages = null;
    try {
        tx = session.beginTransaction();
        messages = session.createCriteria(Message.class).list();

        } catch (Exception e) {
        tx.rollback();
        } finally {
        session.close();
        return messages;
        }
    }
}

但是当我 运行 应用程序并尝试调用存储库方法时,我得到 NullPoinerException。我不明白我做错了什么。 Tomcat 日志是这样说的:

*08-Aug-2015 02:42:18.232 SEVERE [http-nio-8080-exec-10] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [org.alexdzot.phonettesttask.config.ApplicationConfig] in context with path [] threw exception [java.lang.NullPointerException] with root cause
 java.lang.NullPointerException
    at org.alexdzot.phonettesttask.repository.DefaultMessageRepository.getAllMessages(DefaultMessageRepository.java:42)
    at org.alexdzot.phonettesttask.service.MessageResource.viewSentMessages(MessageResource.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.invoke(ResourceMethodInvocationHandlerFactory.java:81)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.run(AbstractJavaResourceMethodDispatcher.java:144)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161)
    at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:205)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102)
    at org.glassfish.jersey.server.ServerRuntime.run(ServerRuntime.java:308)
    at org.glassfish.jersey.internal.Errors.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors.call(Errors.java:267)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:291)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1140)
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:403)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:386)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:334)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:617)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1521)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1478)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)*

知道我的解决方案有什么问题以及导致该问题的原因吗?

主要问题:

[java.lang.NullPointerException]

查看您的存储库class

@Service
public class DefaultMessageRepository implements MessageRepository {

    @Inject
    private Session session; <-------------------+
                                                 | // You shadow session
    public void saveMessage(Message message) {   |
        Session session = null;   <--------------+
        Transaction tx = null;
        try {
            tx = session.beginTransaction();
        ...
    }
    ...
}

您正在隐藏注入的场 session。所以你只是使用本地 session,它是空的。所以在你的方法中,去掉Session session = null;就行了。注入应该可以正常工作,您应该可以只使用 session 字段。

另一个问题:

所有请求都使用相同的 Session。由于 DefaultMessagFactory 是一个单例,HK2 断言 Session 也应该是一个单例,因为它包含在一个单例中。因此,不是将 SFFactory 作为默认的 @PerLookup 作用域,而是得到一个单例工厂,因此只会创建一个 Session。这不是您应该想要的行为。您可以通过在工厂 class.

中放置一些打印语句来对此进行测试

确保为每个请求创建新的 Session 的一种方法是:

  1. 使用javax.inject.Provider<Session>延迟加载会话,这将允许使用将其保留在我们配置的范围内。

    @Inject
    private javax.inject.Provider<Session> session;
    
    @Override
    public void saveEvent(Event event) {
        Session s = session.get();
        Transaction tx = s.beginTransaction(); 
    
  2. 在请求范围内配置SFFactory

    @Override
    protected void configure() {
        bindFactory(SFFactory.class)
                .to(Session.class)
                .in(RequestScoped.class);
    }
    

另一件要修复的事情是 SessionFactory 应该是整个应用程序的单个实例。为此,我们可以为它创建一个工厂,并将其注入 SFFactory。例如

public class SessionFactoryFactory implements Factory<SessionFactory> {

    private final SessionFactory factory;

    public SessionFactoryFactory() {
        Configuration configuration = new Configuration();
        configuration.configure("hibernate.cfg.xml");
        StandardServiceRegistryBuilder srBuilder = new StandardServiceRegistryBuilder();
        srBuilder.applySettings(configuration.getProperties());
        factory = configuration.buildSessionFactory(srBuilder.build());
        System.out.println("--- SessionFactory Created ---");
    }

    @Override
    public SessionFactory provide() {
        System.out.println("--- SessionFactory Provided ---");
        return factory;
    }

    @Override
    public void dispose(SessionFactory factory) {
        factory.close();
    }
}

你的 SFFactory

public class SFFactory implements Factory<Session> {

    private final SessionFactory factory;

    @Inject
    public SFFactory(SessionFactory factory) {
        this.factory = factory;
    }

    @Override
    public Session provide() {
        System.out.println("--- Session Created ---");
        return factory.openSession();
    }

    @Override
    public void dispose(Session session) {
        if (session.isOpen()) {
            session.close();
        }
    }
}

你的配置会变成这样

@Override
protected void configure() {
    bindFactory(SessionFactoryFactory.class)
            .to(SessionFactory.class)
            .in(Singleton.class);
    bindFactory(SFFactory.class)
            .to(Session.class)
            .in(RequestScoped.class);
}

更新

所以看起来除了阴影之外,另一个主要问题是 OP 没有显示的 MessageRepositoryFactory(在评论的 github 项目中)。

public class MessageRepositoryFactory implements Factory<MessageRepository> {
    @Override
    public MessageRepository provider() {
        return new DefaultMessageRepository();
    }
    ...
}

当您自己实例化对象时,默认情况下,框架不处理注入。所以这就是为什么 Session 的注入没有发生。

您可以像这样简单地绑定

而不是像这样使用 Factory
bind(DefaultMessageRepository.class).to(MessageRepository.class).in(..);

这样框架就创建了实例。 Factory 应该真的只用在不可能以上述方式绑定的地方,例如你有一些初始化要做。我们甚至可以不为 SessionFactoryFactory 创建 Factory 而是在别处进行所有会话工厂配置并只绑定实例。例如

SessionFactory sessionFactory =...
...
bind(sessionFactory).to(SessionFactory.class);

我们只绑定一个 SessionFactory 单例。