Sunday, February 12, 2012

Spring 3.0 Framework - Spring Java Configuration of a Spring MVC Application

Disclaimer:
All the software applications that appear below represent trademarks and they are the property of their respective owners.
Use and implement my notes in this article at your own risk.

The following topics are discussed:
1. Goals of Spring JavaConfig;
2. Updates in web.xml
3. Overview of Spring JavaConfig;
4. Sample of AppConfig.java;
5. Sample of a Spring business service.
6. Sample of MvcConfig.java;
7. Sample of a Spring controller.
8. Considerations


(view of Spring app in action)

1. Goals of Spring JavaConfig
The main goal of Spring JavaConfig is to eliminate the XML code used to configure all the Spring components and to keep all the configuration in 2 main JavaConfig classes, namely, for example, AppConfig.java and MvcConfig.java.
This means practically passing all the configuration from XML to Java classes.

However, the Spring 3.0 comes with a limited version of JavaConfig, that allows that only some Spring objects to be configured in Java classes. As we saw in XML configuration, there are many Spring namespaces that defines XML tags like: context, beans, tx, mvc, aop, aso. Practically, in Spring 3.0, only the "bean" XML tag can be mapped in Java classes. The rest of the XML tags that cannot be mapped to Java classes will remain in XML files and will be imported (we'll see later how is done).
Another problem is the component scanning of packages for Spring components, which is not suppoerted by Spring 3.0 JavaConfig.

Note: almost all the JavaConfig problems were solved at Spring Framework 3.1 release.

2 Updates in web.xml.
In order to use the JavaConfig, the following web.xml should be replaced:
<<
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/app-config.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/mvc-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

>>
, with:
<<
    <context-param>
          <param-name>contextClass</param-name>
          <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </context-param>
    <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>blog.configuration.AppConfig</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>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>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>blog.configuration.MvcConfig</param-value>
        </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>

>>
Note: Practically, we told Spring to use JavaConfig classes (AppConfig and MvcConfig) as "contextClass" instead of using app-config.xml and mvc-config.xml.

Also, because the Spring 3.0 JavaConfig doesn't know about <mvc:interceptors /> tag in mvc-config.xml, we may replace the OpenSessionInViewInterceptor with OpenSessionInViewFilter.
So, we may add this in web.xml:
<<
      <filter>
        <filter-name>hibernateFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
        <init-param>    
             <param-name>singleSession</param-name>      
             <param-value>true</param-value>  
        </init-param> 
        <init-param>
            <param-name>sessionFactoryBeanName</param-name>
            <param-value>sessionFactory</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>hibernateFilter</filter-name>
        <url-pattern>/web/*</url-pattern>
    </filter-mapping> 

>>
Note: in this way we assured that we can map all the JavaConfig XML objects from mvc-config.xml (as the other XML tags were "bean").

3. Overview of Spring JavaConfig
A Spring JavaConfig uses a special Spring annotation to mark the Java classes used for Spring Configuration.
This annotation is @Configuration. Any Java class annotated with @Configuration will become a Spring configuration class (JavaConfig class).
Like in the case of XML Configuration where the XML files could be splitted in many small XMLs and then imported in a final one (let's say app-config.xml), the JavaConfig classes can also be imported. This may be done by using @Import annotation.
Because of the limited capabilities of JavaConfig in Spring 3.0 and also to keep the backward compatibility, another annotation was necessaary to be defined: @ImportResource. @ImportResource is capable of importing an XML configuration file (like app-config.xml) in the respective JavaConfig class. This annotation is also necessary for us, because there is no way in Spring 3.0 to map in a JavaConfig class the XML tag <tx:annotation-driven /> or <context:component-scan />.
As we said, in JavaConfig only the "bean" XML tags may be defined. This is done by using a @Bean annotation.


4. Sample of AppConfig.java;
<<
package blog.configuration;

import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.hibernate.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;

import blog.dao.CityDao;
import blog.dao.CountryDao;
import blog.dao.ZoneDao;
import blog.services.CityDaoImpl;
import blog.services.CountryDaoImpl;
import blog.services.ZoneDaoImpl;

@Configuration
@ImportResource({"classpath:config/app-config.xml"})
public class AppConfig {
   
    @Bean(name="dataSource")
    public DataSource dataSource() {
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUsername("root");
        ds.setPassword("");
        ds.setUrl("jdbc:mysql://127.0.0.1:3306/blog");
        return ds;
    }

    @Bean(name="sessionFactory")
    public SessionFactory sessionFactory() {
        AnnotationSessionFactoryBean factoryBean = null;
        try {
            factoryBean = new AnnotationSessionFactoryBean();
            Properties pp = new Properties();
            pp.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
            pp.setProperty("hibernate.max_fetch_depth", "3");
            pp.setProperty("hibernate.show_sql", "false");
   
            factoryBean.setDataSource(dataSource());
            factoryBean.setPackagesToScan(new String[] {"blog.dao.*"});
            factoryBean.setHibernateProperties(pp);
            factoryBean.afterPropertiesSet();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return factoryBean.getObject();
    }
   
    @Bean(name="transactionManager")
    public HibernateTransactionManager transactionManager() {
        return new HibernateTransactionManager(sessionFactory());
    }

    @Bean(name="zoneDao")
    ZoneDao zoneDao() {
        return new ZoneDaoImpl(sessionFactory());
    }

    @Bean(name="countryDao")
    CountryDao countryDao() {
        return new CountryDaoImpl(sessionFactory());
    }

    @Bean(name="cityDao")
    CityDao cityDao() {
        return new CityDaoImpl(sessionFactory());
    }
   
}

>>

Note: we observe in this file all the XML "bean" tags that we defined in app-config.xml. Even if we used in web.xml an "AnnotationConfigWebApplicationContext" to tell Spring to use the AppConfig as configuration clas, we cannot use the component scanning and driven annotations, so we have to define entries for all the business components used (this is dued to the Spring 3.0 JavaConfig limitations).

Also, because in AppConfig.java we cannot include the "<tx:annotation-driven />" XML tag, we imported the app-config.xml file from "classpath:config/app-config.xml", which looks like:
<<
<?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:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                            http://www.springframework.org/schema/tx
                            http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

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

</beans>

>>

5. Sample of a Spring business service.
We will pass here the Java code used to create the Zone Spring service (namely ZoneDaoImpl), as an implementation of a user created ineterface (namely ZoneDao):
<<
package blog.services;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.transaction.annotation.Transactional;

import blog.dao.ZoneDao;
import blog.dao.entities.Zone;

public class ZoneDaoImpl implements ZoneDao {

    private SessionFactory sessionFactory;

    public ZoneDaoImpl(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Transactional(readOnly = true)
    @SuppressWarnings("unchecked")
    public List<Zone> getZones() {
        return (List<Zone>) getCurrentSession().createQuery("from Zone").list();
    }

    @Transactional(readOnly = true)
    public Zone getZoneByCod(String cod) {
        return (Zone) getCurrentSession().createQuery("from Zone z where z.cod=?").setParameter(0, (String) cod).uniqueResult();
    }

    @Transactional(readOnly = true)
    public Zone getZoneByName(String name) {
        return (Zone) getCurrentSession().createQuery("from Zone z where z.name=?").setParameter(0, (String) name).uniqueResult();
    }

    protected Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }
}

>>
Note: we observe here that @Service and @Autowired are not used, as ZoneDao Spring service was defined in AppConfig.java.

6. Sample of MvcConfig.java;
<<
package blog.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import blog.controllers.CityController;
import blog.controllers.CountryController;
import blog.controllers.ZoneController;
import blog.dao.CityDao;
import blog.dao.CountryDao;
import blog.dao.ZoneDao;

@Configuration
public class MvcConfig {

    @Autowired
    private ZoneDao zoneDao;

    @Autowired
    private CountryDao countryDao;

    @Autowired
    private CityDao cityDao;

    @Bean
    InternalResourceViewResolver irResolver() {
        InternalResourceViewResolver ir = new InternalResourceViewResolver();
        ir.setPrefix("/views/");
        ir.setSuffix(".jsp");
        return ir;
    }

    @Bean(name="/zoneList")
    ZoneController zoneController() {
        return new ZoneController(zoneDao);
    }

    @Bean(name="/countryList")
    CountryController countryController() {
        return new CountryController(countryDao);
    }

    @Bean(name="/cityList")
    CityController cityController() {
        return new CityController(cityDao);
    }
}

>>

Note: we observe in this file all the XML "bean" tags that we defined in mvc-config.xml. Even if we used in web.xml an "AnnotationConfigWebApplicationContext" to tell Spring to use the MvcConfig as configuration clas, we cannot use the component scanning and driven annotations, so we have to define entries for all the business components used (this is dued to the Spring 3.0 JavaConfig limitations).
Note: Because we replaced OpenSessionInViewInterceptor with OpenSessionInViewFilter and moved it in web.xml, we do not need anymore the mvc-config.xml file.

7. Sample of a Spring controller.
We will pass here the code used to create the Zone controller (namely ZoneController):
<<
package blog.controllers;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import blog.dao.ZoneDao;

public class ZoneController {
   
    private ZoneDao zoneDao;
   
    public ZoneController(ZoneDao zoneDao) {
        this.zoneDao = zoneDao;
    }
   
    @RequestMapping("/zoneList")
    public String zoneList(Model model) {
        model.addAttribute("zones", zoneDao.getZones());
        return "zoneList";
    }

}

>>
Note: we observe here that @Controller and @Autowired are not used, as ZoneController was defined in MvcConfig.java.

8. Considerations
The Spring JavaConfig may be useful as:
- we may keep all the configuration in 2 Java classes;
- no XML code for XML haters;

However, the Spring 3.0 does not provide full support for JavaConfig, so it's better to use Spring 3.1 if JavaConfig is really wanted.
Also, for the guys that likes to open a WAR archive and to look inside to see the app behaviour, they may have a surprise to see a black-box.
You may find here a Spring 3.1 Java Configuration to which the current Spring 3.0 Java Configuration serves as a basis.

1 comment: