Thursday, 17 November 2011

Using Spring Expression Language for Server Specific Properties

Spring 3.0 introduced Spring Expression Language (SpEL). This post will describe how to use SpEL to load property files which are different for each server your application runs on. It also describes how server specific properties can used without having to use -D.

The problem solved was how can a WAR be deployed onto different servers without having to package up a server specific property file in each archive (or indeed bundle them all in the same WAR file.) Ideally, you would want to drop the WAR file into any environment without having to configure the container or amend the WAR, and if you wanted to change a property, you would change the property file on the server and redeploy the app (or dynamically refresh the cache of properties.)

In this example, our application needs to access a different remote server registry for each env: Dev, Test and Prod. (For ease in this example, our server names are the same as our envionments!)

Therefore we have a properties file for each env/server (dev.properties, test.properties and prod.properties). Could have a local.properties file on each server but prefixing them with a server name helps distinguish them. An example property file is shown below:

rmiRegistryHost=10.11.12.13
rmiRegistryPort=1099

We have a bean which requires these properties, so it's constructor args contain property placeholders:

<bean id="lookupService" class="com.city81.rmi.LookupService" scope="prototype">
  <constructor-arg index="0" value="${rmiRegistryHost}" /> 
  <constructor-arg index="1" value="${rmiRegistryPort}" />
</bean>


For the above bean to be loaded, the properties need to be loaded themselves and this is done via the PropertyPlaceholderConfigurer class. The location and name of the server specific property file is added as one of the locations the configurer uses to search for properties.

The file is in the same location on each server but in order to know what the name is, SpEL is used. By creating a java.net.InetAddress bean, we can access the hostName of the server the application is running on by using SpEL ie #{inetAddress.hostName}. Therefore, this config doesn't have to change between environments.

<bean id="inetAddress" class="java.net.InetAddress" factory-method="getLocalHost">
</bean>
    
<bean id="propertyBean" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreResourceNotFound" value="false"/>
    <property name="ignoreUnresolvablePlaceholders" value="false"/>
    <property name="locations">
        <list>
            <value>file:/home/city81/resources/#{inetAddress.hostName}.properties</value>
        </list>
    </property>
</bean>


This is a very specific example where the property files are prefixed with the server names but it shows how SpEL can be used to solve a problem which would have taken a lot more work pre Spring 3.0.

Wednesday, 16 November 2011

Spring MVC - A Bare Essentials Example Using Maven

Spring's MVC is a request based framework like Struts but it clearly separates the presentation, request handling and model layers.

In this post, I'll describe how to get the most simple of examples up and running using Maven, therefore providing a basis upon which to add more features of Spring MVC like handler mappings, complex controllers, commons validator etc..

Let's start with the pom.xml file. This will package up the project as a war file and only requires three dependencies namely the artifacts spring-webmvc, servlet-api and jstl. The spring-webmvc artifact will pull in all the other required spring jars like core, web, etc.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.city81</groupId>
    <artifactId>spring-mvc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1-beta-1</version>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>3.0.5.RELEASE</version>
            <optional>false</optional>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>
</project> 


Note that the scope of the servlet-api is provided and therefore excluded from the war file. If deployed as part of the war, there'll be conflict with the servlet jar of the container, and there'll be an error similar to the one below when deploying to Tomcat:

INFO: Deploying web application archive spring-mvc-0.0.1-SNAPSHOT.war
15-Nov-2011 16:05:27 org.apache.catalina.loader.WebappClassLoader validateJarFile
INFO: validateJarFile(C:\apache-tomcat-6.0.33\webapps\spring-mvc-0.0.1-SNAPSHOT\WEB-INF\lib\servlet-api-2.5.jar) - jar not loaded. See Servlet Spec 2.3, section
 9.7.2. Offending class: javax/servlet/Servlet.class


Next the web.xml located in the /webapp/WEB-INF folder:

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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"
 version="2.5">

    <display-name>Spring MVC Example</display-name>

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

    <servlet>
        <servlet-name>springMVCExample</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springMVCExample</servlet-name>
        <url-pattern>*.htm</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>home.jsp</welcome-file>
    </welcome-file-list>

</web-app>

The context loader is a listener class called ContextLoaderListener. By default this will load the config in the /WEB-INF/applicationContext.xml but you can specify more files by adding a contextConfigLocation param and list one or more xml files. For example,


    contextConfigLocation    /WEB-INF/example-persistence.xml
        /WEB-INF/example-security.xml


If an applicationContext.xml file isn't present when not using a context param list, then the WAR won't deploy properly. An example empty applicationContext.xml is below:

<?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:aop="http://www.springframework.org/schema/aop"
 xsi:schemaLocation="http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/util 
    http://www.springframework.org/schema/util/spring-util.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop    
    http://www.springframework.org/schema/aop/spring-aop.xsd">

</beans>


The servlet's configuration does not need to be explicitly specified as it can be loaded by following the same naming convention of the servlet, in this case springMVCExample-servlet.xml. The servlet is the front controller which delegates requests to other parts of the system.

The servlet-mapping tags in the web.xml denote what URLs the DispatcherServlet will handle. In this example HTML.

Also included in the web.xml by way of the welcome-file, is a default home page. This doesn't have to be included but the page can be used to forward a request as can be seen later in the post.

As mentioned previously, the servlet's config is in it's own XML file. This describes the mapping between the a URL and the Controller which will handle the request. It also contains the a ViewResolver which maps the view name in the ModelAndView to an actual view. The springMVCExample-servlet.xml is shown below:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 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-3.0.xsd">

    <bean name="/example.htm" class="com.city81.spring.mvc.ExampleController">
    </bean>

    <bean id="viewResolver" 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>


The ExampleController class is shown below. It extends AbstractController therefore must implement the method handleRequestInternal(HttpServletRequest request, HttpServletResponse response). The return value of this method is a ModelAndView object. This object is constructed by passing in the view name (example), the model name (message) and the model object (in this case a String)

package com.city81.spring.mvc;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

public class ExampleController extends AbstractController {
  
    protected ModelAndView handleRequestInternal(
        HttpServletRequest request, HttpServletResponse response) 
            throws Exception {
        ModelAndView modelAndView = new ModelAndView("example", "message", "Spring MVC Example");

        return modelAndView;
    }

}


The view jsp will then have the ${message} value populated by the model object from the ModelAndView. The example.jsp is below and resides in the /webapp/WEB-INF/jsp folder:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
    <head>
        <title>Spring MVC Example</title>
    </head>
    <body>
        <h2>Welcome to the Example Spring MVC page</h2>
        <h3>The message text is:</h3>
        <p>${message}</p>
    </body>
</html>


As mentioned previously, a default home page can included in the web.xml and instead of displaying html etc., it can be used to redirect requests to another URL. The below home.jsp redirects requests to example.htm:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:redirect url="/example.htm"/>


Deploying the above to a container like Tomcat should result in a page like the following:



This is just the very basics of Spring MVC but later posts, will expand on the framework and show how it can be used with, for example, web services like REST.