6

I am developing a little web application in Spring MVC. I get the exception every time I try to get my custom class in any controller, in case if this class uses another custom class. It is easier to show on example:

Controller, where I'm trying to get an object of custom class WireTest:

@Controller
@RequestMapping("/offices")
public class OfficesController {

  @Autowired
  private WireTest wt;

  @RequestMapping("")
  public String offices(Model model) {
    model.addAttribute("test", wt.getString());
    return "offices";
  }
}

The problem happens always, whether I'm creating an object directly or use @Autowired. In code here, I'm showing the case of @Autowired, but that doesn't matter - I could write private WireTest wt = new WireTest(), and the exception would be the same.

WireTest.java class:

@Service
public class WireTest {
  public String getString() {return (DBhelper.getString());}
}

Part of DBhelper.java class (There are also other static members, the full code is below):

public class DBhelper {
    public static String getString() {return "Hi!";}
}

And the exception:

HTTP Status 500 - Handler processing failed; nested exception is         
java.lang.NoClassDefFoundError: Could not initialize class org.sher.wtpractice.dao.DBhelper

I also can use these classes without any problems in console application, so it works outside Spring.

My Spring configuration:

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <!-- Processes application requests -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
    </context-param>

    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
</web-app>

dispatcher-servlet.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<context:component-scan base-package="org.sher.wtpractice.spring, org.sher.wtpractice.dao" />
    <mvc:annotation-driven />
    <mvc:resources mapping="/css/**" location="/css/"/>
    <mvc:resources mapping="/js/**" location="/js/"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/jsp/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>
</beans>

I'm using Maven for building and Tomcat 7 as a server, if that matters. Spring version is 4.0.1.RELEASE

Update: Complete code of DBhelper.java

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

import java.util.Calendar;
import java.util.Date;
public class DBhelper {
    private static final SessionFactory sessionFactory = createSessionFactory();

    private static SessionFactory createSessionFactory() {
        Configuration configuration = new Configuration();
        configuration.configure();
        ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(
                configuration.getProperties()).build();
        return configuration.buildSessionFactory(serviceRegistry);
    }

    public static Object queryWrapper(DBoperation op) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;
        Object result = null;
        try {
            transaction = session.beginTransaction();
            result = op.operation(session);
            session.getTransaction().commit();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
//            throw e;
        } finally {
            if (session != null && session.isOpen())
                session.close();
        }
        return result;
    }
    public static Date normalizeCal(Calendar cal) {
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }
    public static String getString() {return "Hi!";}
}

Any help would be greatly appreciated!

ars
  • 1,509
  • 3
  • 18
  • 29
  • Is your DBhelper class a part of any jar file? – ajay.patel Sep 06 '14 at 14:44
  • No. The only difference from this point of view between DBhelper and WireTest classes is that they are situated in different packages - WireTest is one level deeper. – ars Sep 06 '14 at 14:47
  • Can you please unzip the war file and check if this class was packaged by maven as a part of war? – ajay.patel Sep 06 '14 at 14:52
  • Yes, they was: [DBhelper](https://dl.dropboxusercontent.com/u/47315162/11.jpg) [WireTest](https://dl.dropboxusercontent.com/u/47315162/12.jpg). I suppose, I would get ClassNotFound exception otherwise, not NoClassDefFoundError – ars Sep 06 '14 at 15:00
  • Can you paste DBhelper totally? I guess maybe there is something wrong with its initialization. – Loki Sep 06 '14 at 16:23
  • Yes, you are right..I have just noticed that some static fields cause this exception, but I've no idea how to work around this. I will update question with DBhelper in a minute. Do you know how to fix it? – ars Sep 06 '14 at 16:34
  • Maybe you use the spring context in createSessionFactory(). Just let the spring container do this. Can you paste all import lines? The methods which are not static are useless, you can remove them for clarity. – Loki Sep 06 '14 at 16:48
  • "Maybe you use the spring context in createSessionFactory() Just let the spring container do this" -- what do you mean? This is impossible, I wrote all this Hibernate code earlier then started working with Spring things. I posted import lines as you asked, and there are no not-static methods in DBhelper. In fact, this is really full DBhelper.java without just package line. – ars Sep 06 '14 at 16:56
  • You use the hibernate, maybe that need runtime context. – Loki Sep 06 '14 at 17:05
  • I'm not familiar with hibernate, you can check this configuration, http://stackoverflow.com/questions/13576777/hibernate-4-calling-dao-and-initializing-sessionfactory-bean – Loki Sep 06 '14 at 17:07

2 Answers2

6

Try lazy-loading the session factory instead of initializing it statically by assigning to a final field.

For example, create a method such as the following, which returns the session factory, creating it if necessary. Whenever you want to use the session factory, call this method instead of referring directly to the field:

private static SessionFactory getSessionFactory() {
    if (sessionFactory == null) {
        sessionFactory = createSessionFactory();
    }
    return sessionFactory;
}

Personally I dislike static initialization for anything non-trivial, as you're out of control of when it happens. With the above method you are in control: the session factory gets created the first time you need to use it.

If you want to know what the problem is, I have two suggestions. Firstly, restart your web application container and see if you get a different exception message first time. (The 'first time' is important here: Could not initialize class means that the JVM has already tried and failed to initialize the class.) Secondly, try wrapping the contents of the createSessionFactory method in a try-catch block such as the following:

try {
    ...
} catch (Throwable e) {
    e.printStackTrace();
    throw new RuntimeException(e);
}

However, I can't guarantee that either approach will offer you much enlightenment.

EDIT: rather than just speculate, I decided to actually try this out and see what happens. So I knocked up a small web application using Spring and Hibernate and deployed it to Tomcat. While attempting to get it working I encountered a few issues when attempting to read the Hibernate configuration, caused by me making the following mistakes:

  • I hadn't included the hibernate.cfg.xml file in the .war file,
  • I hadn't included the database's JDBC driver JAR in the .war file,
  • the database I was trying to connect to was down.

In each case my first approach worked. The first request made following a Tomcat restart gave me an ExceptionInInitializerError with further details below it. The second and subsequent requests gave me NoClassDefFoundErrors which didn't tell me much about the problem.

The problem is that once an ExceptionInInitializerError has been thrown for a class, the JVM blacklists this class and will refuse to do anything with it subsequently. So you only get one chance to see what goes wrong in static initialization code. If you initialized Hibernate lazily as I recommended above, you would get the detailed exception message every time. Consider this another reason to avoid static initialization.

Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
  • In last hours, I have rewritten all the Hibernate code in Spring-manner - with beans, autowiring and database connection declared in web.xml (honestly, I still don't get why Spring devs suppose me to do so). It didn't help, but the error was different - Tomcat just couldn't start the application. So I looked into logs and finally understood that Tomcat was unable to find the JDBC driver. After some googling I found out that Tomcat searches for JDBC driver in it's own lib folder, not WAR's, and the problem was solved. – ars Sep 06 '14 at 22:01
  • Thank you for your recommendation. I think that if my `createSessionFactory` had been written in lazy style, the exception would be eloquent enough to get the reason of it, so the problem would be solved instantly. So I mark your answer as accepted. – ars Sep 06 '14 at 22:01
0

Check the static initialization of DBhelper.

"NoClassDefFoundError: Could not initialize class" error

In this case, instantiate sessionFactory as spring bean, and let the spring container assemble it to DBhelper.

Community
  • 1
  • 1
Loki
  • 931
  • 8
  • 13
  • Well, I am very new to Spring, and don't quite understand how to do this. Should I make separate class with static final SessionFactory object, mark it as a Bean (@Service, I suppose) and autowire it to DBhelper? Also, I don't understand why should I change code related to Hibernate due to Spring features. Is there a more elegant way? How usually people handle this? – ars Sep 06 '14 at 17:07
  • Your link to spring-hibernate question is really helpful, I will try to do like there. But I still don't get the core of the problem. Why Spring can't do what a simple console application can? – ars Sep 06 '14 at 17:24
  • Because the hibernate and such things always need configuration, and spring IoC extract the configuration from your business code. You can read some documents about that. http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/beans.html – Loki Sep 06 '14 at 17:37
  • In my opinion, these things should be totally independent. I have just a set of DAO interfaces which can be used by ANY framework, by any application. I also have their implementation which uses Hibernate. But this implementation knows nothing about the framework, and the framework knows nothing about the implementation of these interfaces - that's how I imagine it. Is there a philosophy I don't understand? Anyway, thank you very much. – ars Sep 06 '14 at 18:01
  • Are you able to run the createSessionFactory() in a static main method? If you can, I can not figure out why the spring throw out exception. – Loki Sep 06 '14 at 18:22
  • If I make it public, yes, I can. I also have a plenty of unit tests, they work with the same DAO implementation perfectly. I agree that this is a really weird problem... – ars Sep 06 '14 at 18:35
  • So this must be the difference of runtime environment. Make sure the xml and properties files in classpath of war. – Loki Sep 06 '14 at 18:44