3
votes

I'm trying to understand spring configuration. I read both articles:

  1. http://www.onjava.com/pub/a/onjava/2006/03/22/advanced-spring-configuration.html?page=1
  2. http://syntx.io/difference-between-loading-context-via-dispatcherservlet-and-contextloaderlistener/

These suggest to have 2 configuration files: “Application Context” and “Web Application Context”.

If you have ever tried to develop a web application using the Spring MVC framework, you know that there are two configuration files that should be used:

/WEB-INF/applicationContext.xml allows you to configure your beans, or to indicate the context of your application. This is the place where you define your business logic beans, resources, and all other beans that are not directly related to the web tier.

/WEB-INF/[servlet-name]-servlet.xml is used to configure the web tier and view resolvers, controllers, validators, and all other beans that you need in the MVC framework. The [servlet-name] refers to the name of the Spring's dispatcher servlet defined in the web.xml deployment descriptor.

According to this, I write my web.xml as follow:

<?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">


    <!-- Application Context -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml,
            /WEB-INF/spring-security.xml</param-value>
    </context-param>


    <!-- Spring MVC -->
    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

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


    <!-- Spring Security -->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>
            org.springframework.web.filter.DelegatingFilterProxy
        </filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- Fin Spring Security -->

</web-app>

This is my applicationContext.xml:

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


  <!-- Look in tom cats context -->
  <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/rhcimax"/>

  <!-- Hibernate Session Factory -->
  <bean id="mySessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="myDataSource"/>
    <property name="packagesToScan">
      <array>
        <value>com.blah.baseProject</value>
      </array>
    </property>
    <property name="hibernateProperties">
      <value>
        hibernate.dialect=org.hibernate.dialect.MySQLDialect
      </value>
    </property>
  </bean>

  <!-- Hibernate Transaction Manager -->
  <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

  <!-- Activates annotation based transaction management -->
  <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

And this is my mvc-dispatcher-servlet.xml:

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

  <!-- Enable @Controller annotation support -->
  <mvc:annotation-driven />

  <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
  <mvc:resources mapping="/resources/**" location="/resources/" />

  <!-- Map simple view name such as "test" into /WEB-INF/views/test.jsp -->
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/view/" />
    <property name="suffix" value=".jsp" />
  </bean>

  <!-- Scan classpath for annotations (eg: @Service, @Repository etc) -->
  <context:component-scan base-package="com.blah.baseProject"/>


</beans>

I want to know if this configuration is almost correct. This configuration runs but I feel that applicationContext.xml is not been called since I get this exception:

org.hibernate.HibernateException: No Session found for current thread

My intention is to keep good practices in spring and to learn the right configuration.

"It is a best practice to keep a clear separation between middle-tier services such as business logic components and data access classes (that are typically defined in the ApplicationContext) and web- related components such as controllers and view resolvers (that are defined in the WebApplicationContext per Dispatcher Servlet)."

2

2 Answers

5
votes

You can change your web.xml context param to:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
</context-param>

And create any count of context files without importing it to root context

enter image description here

Also it is very useful when you have multimodule project and put configuration files to each module, name it applicationContext-.xml and it will be automatically scanned.

It is good practice to declare mvc components in mvc-dispatcher-servlet.xml: interceptors (locale, theme interceptors and your own), view resolvers, resources, exception-handlers, templating-engine configuration and other components that relates with view.

Also it is useful to declare component-scan only for controllers in servlet configuration:

mvc-dispatcher-servlet.xml:

<context:component-scan base-package="by.company.app" use-default-filters="false">
    <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>

When in applicationContext exclude @Controller scan:

applicationContext.xml:

<context:component-scan base-package="by.company.app">
    <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>

That helps to avoid duplicated bean definitions

Example of separate applicationContexts (namespaces declaration omitted):

applicationContext.xml:

<beans>    
    <context:property-placeholder location="classpath*:META-INF/spring/a-*.properties" />

    <task:annotation-driven/>

    <context:spring-configured/>

    <context:component-scan base-package="by.company.app">
        <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
    </context:component-scan>
</beans>

applicationContext-db.xml

<beans>
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="jdbc:mysql://${database.host}:${database.port}/${database.db-path}" />
        <property name="driverClassName" value="${database.driverClassName}" />
        <property name="username" value="${database.username}" />
        <property name="password" value="${database.password}" /> 
    </bean>

    <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>

    <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
        <property name="persistenceUnitName" value="persistenceUnit"/>
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="jpaProperties"> 
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.max_fetch_depth">3</prop> 
                <prop key="hibernate.jdbc.fetch_size">50</prop> 
                <prop key="hibernate.jdbc.batch_size">10</prop> 
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                <prop key="hibernate.connection.charSet">UTF-8</prop>
                <!-- <prop key="hibernate.hbm2ddl.auto">update</prop> -->
            </props> 
        </property>
    </bean>

    <jpa:repositories base-package="by.company.app" />

</beans>
2
votes

In my experience, as long as you don't have any specific reasons to separate core configuration from webapp confiiguration, the best approach is to keep DispatcherServlet's context empty and put everything into the root application context (applicationContext.xml, etc).

By doing so you can avoid many possible problems, in particular:

  • Post-processors such as <tx:annotation-driven> should be declared at per-context basis (that's your problem). If you have no beans in servlet's context, you don't need to duplicate these declarations there
  • Declaring <context:component-scan> for the same package in both contexts can cause serious problems with duplicated bean definitions

If you worry about manageability of a single monolithic context, remember that you still can split it into multiple files, as you already do.

Note that if DispatcherServlet reads context configuration from XML file (as it's configured by default), you still have to create an XML file with a valid root element. However, there is a little trick: if you configure DispatcherServlet to use annotation-based configuration instead, it will be empty by default without any extra effort:

<servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>