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:
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?
Main problem:
[java.lang.NullPointerException]
Look at your repository 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();
...
}
...
}
You are shadowing the injected field session
. So you are just using the local session
, which is null. So in your method, just get rid of Session session = null;
. The injection should work fine, and you should be able to just use the session
field.
Another Problem:
The same Session
is being used for all request. Since DefaultMessagFactory
is a singleton, HK2 asserts that Session
should also be a singleton, since it is contained within a singleton. So instead of making the SFFactory
the default @PerLookup
scope, you get a factory that is a singleton, and so only one Session
will be created. This is not the behavior you should want. You can test this by putting some print statements inside your factory class.
One way to make sure that a new Session
is created for each request is to:
Use javax.inject.Provider<Session>
, to lazily load the session, which will allow use to keep it in the scope we configured.
@Inject
private javax.inject.Provider<Session> session;
@Override
public void saveEvent(Event event) {
Session s = session.get();
Transaction tx = s.beginTransaction();
Configure the SFFactory
in a request scope
@Override
protected void configure() {
bindFactory(SFFactory.class)
.to(Session.class)
.in(RequestScoped.class);
}
The other thing to fix is that SessionFactory
should be a single instance through the application. For that we can create a single factory for it, and inject it into the SFFactory
. For example
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();
}
}
The in your 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();
}
}
}
You configuration would change to look like
@Override
protected void configure() {
bindFactory(SessionFactoryFactory.class)
.to(SessionFactory.class)
.in(Singleton.class);
bindFactory(SFFactory.class)
.to(Session.class)
.in(RequestScoped.class);
}
So it seems aside from the shadowing, another main problem was in the MessageRepositoryFactory
that the OP didn't show (in github project from comments).
public class MessageRepositoryFactory implements Factory<MessageRepository> {
@Override
public MessageRepository provider() {
return new DefaultMessageRepository();
}
...
}
When you instantiate the object your self, by default, the framework doesn't handle the injection. So that's why the injection of the Session
didn't happen.
Instead of using the a Factory
like this, you could simply bind like this
bind(DefaultMessageRepository.class).to(MessageRepository.class).in(..);
This way the framework creates the instance. The Factory
should really only be used where it is not possible to bind the above way, for instance you have some initializations to do. We could have even not created the Factory
for the SessionFactoryFactory
and instead did all the session factory configuration elsewhere and just binded the instance. For example
SessionFactory sessionFactory =...
...
bind(sessionFactory).to(SessionFactory.class);
We just bind a SessionFactory
singleton.