使用 HK2 将休眠会话注入 Jersey
Injecting hibernate session to Jersey using HK2
我正在开发小应用程序并遇到一些 DI 问题。
我有一个存储库 class 用于保存我注入到服务中的实体。我想使用 H2K 向它注入 Session 对象。
为此,我尝试做以下 SO 帖子中描述的类似操作:
- Jersey + HK2 + Grizzly: Proper way to inject EntityManager?
- 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
的一种方法是:
使用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();
在请求范围内配置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
单例。
我正在开发小应用程序并遇到一些 DI 问题。
我有一个存储库 class 用于保存我注入到服务中的实体。我想使用 H2K 向它注入 Session 对象。 为此,我尝试做以下 SO 帖子中描述的类似操作:
- Jersey + HK2 + Grizzly: Proper way to inject EntityManager?
- 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
的一种方法是:
使用
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();
在请求范围内配置
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
单例。