4

I'm developing little app and have some issues with DI.

I have a repository class for persisting my entities which I inject to my service. And I'd like to inject Session object to it using H2K. For this purpose I try do something similar described in following SO posts:

  1. Jersey + HK2 + Grizzly: Proper way to inject EntityManager?
  2. Using Jersey 2.0, how do you register a bindable instance per request?
  3. How do I properly configure an EntityManager in a jersey / hk2 application?

So I created SFFactory class and register it in 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;
        }
    }
}

But when I run the app and trying to call repository methods I get NullPoinerException. I can't understand what am I doing wrong. This is what Tomcat log is saying:

*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$1.invoke(ResourceMethodInvocationHandlerFactory.java:81)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.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$2.run(ServerRuntime.java:308)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors$1.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)*

Any ideas what wrong with my solution and what causes that problem?

4

1 回答 1

5

主要问题:

[java.lang.NullPointerException]

查看您的存储库类

@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。所以你只是使用 local session,它是空的。因此,在您的方法中,只需摆脱Session session = null;. 注入应该可以正常工作,并且您应该能够使用该session字段。

另一个问题:

Session所有请求都使用相同的方法。由于DefaultMessagFactory是单例,HK2 断言也Session应该是单例,因为它包含在单例中。因此,您没有创建SFFactory默认@PerLookup范围,而是获得了一个单例工厂,因此只会Session创建一个。这不是您应该想要的行为。您可以通过在您的工厂类中放置一些打印语句来测试这一点。

确保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);
}

更新

所以似乎除了阴影之外,另一个主要问题是MessageRepositoryFactoryOP 没有显示(在 github 项目中来自评论)。

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

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

Factory您可以像这样简单地绑定,而不是像这样使用 a

bind(DefaultMessageRepository.class).to(MessageRepository.class).in(..);

这样框架就创建了实例。Factory应该只在无法以上述方式绑定的情况下使用,例如您需要进行一些初始化。我们甚至可以不创建Factoryfor the SessionFactoryFactory,而是在别处进行所有会话工厂配置,然后绑定实例。例如

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

我们只是绑定一个SessionFactory单例。

于 2015-08-08T04:55:18.880 回答