Developers mailing list: [email protected]
Spring MVC 4 handles requests mapping
with RequestMappingHandlerMapping and RequestMappingHandlerAdapter beans (that's the "out-of-the-box" configuration that comes with your springmvc application).
But you may want to use a request Router for your application:
- Route Configuration is centralized in one place (you don't need to look into your controllers anymore)
 - URL Refactoring is easier
 - Many web frameworks use that system (Rails, PlayFramework and many others)
 - Handles routes priority
 
Define your application routes like this!
GET     /user/?                 userController.listAll
GET     /user/{<[0-9]+>id}      userController.showUser
DELETE  /user/{<[0-9]+>id}      userController.deleteUser
POST    /user/add/?             userController.createUser
Warning: this project is currently tested on Spring 4.0.x, should work on 3.2.x but is not compatible with Spring 3.0.x.
Your project needs these dependencies (Hint: the new "Bill of materials") is really handy:
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-framework-bom</artifactId>
            <version>4.0.2.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
...
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
  </dependency>
...
  <dependency>
    <groupId>org.resthub</groupId>
    <artifactId>springmvc-router</artifactId>
    <version>1.2.0</version>
  </dependency>
...
</dependencies>
If you want to use SNAPSHOTs, add oss.sonatype.org as a repository.
<repositories>
  <repository>
    <id>sonatype.oss.snapshots</id>
      <name>Sonatype OSS Snapshot Repository</name>
      <url>http://oss.sonatype.org/content/repositories/snapshots</url>
      <releases>
        <enabled>false</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
  </repository> 
</repositories>
In your *-servlet.xml file, add the following beans:
 <?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:context="http://www.springframework.org/schema/context"
     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">
    <!--
      Enable bean declaration by annotations, update base package according to your project
    -->
    <context:annotation-config/>
	<!--
		Package to scan for Controllers.
		All Controllers with @Controller annotation are loaded as such.
	-->
	<context:component-scan base-package="com.example.yourproject.controllers" />
	<!-- 
		Choose HandlerMapping.
		RouterHandlerMapping loads routes configuration from a file.
		Router adapted from Play! Framework.
		
		@see http://www.playframework.org/documentation/1.2.4/routes#syntax
		for route configuration syntax.
		Example:
		GET    /home          PageController.showPage(id:'home')
		GET    /page/{id}     PageController.showPage
	-->		 
	<bean id="handlerMapping"
          class="org.resthub.web.springmvc.router.RouterHandlerMapping">
            <property name="routeFiles">
                <list>
                    <value>routes.conf</value>
                <!--
                    Router will *append* routes declared in additional files
                    <value>addroutes.conf</value>
                -->
                </list>
            </property>
            <!-- 
                Uncomment the following configuration line
                if you want routes to be dynamically reloaded when
                route files are modified.
                Can be a good idea in dev mode, not so much in production!
            -->
            <!-- <property name="autoReloadEnabled" value="true" /> -->
    </bean>
</beans>
Or you can achieve the same thing with a Javaconfig class like this:
@Configuration
@ComponentScan(basePackages = "com.example.yourproject.controllers")
// You should not use the @EnableWebMvc annotation
public class WebAppConfig extends RouterConfigurationSupport {
  @Override
  public List<String> listRouteFiles() {
    List<String> routeFiles = new ArrayList<String>();
    routeFiles.add("routes.conf");
    return routeFiles;
  }
}    
The example above will load the configuration file using Spring ResourceLoader - so create a new file in your project src/main/resources/routes.conf.
The router maps HTTP request to a specific action (i.e. a public method of a Controller class handling requests).
Controllers can use Spring MVC annotations and conventions - only the @RequestMapping annotation is useless.
@Controller
public class HelloController {
  public void simpleAction() {
  
  }
	
  public @ResponseBody String sayHelloTo(@PathVariable(value = "name") String name) {
    return "Hello "+name+" !";	  
  }
}
Warning: in the route configuration file, Controller names are case sensitive, and should always start with a lower case letter.
# this is a comment
GET     /simpleaction                  helloController.simpleAction
GET     /hello/{<[a-zA-Z]+>name}       helloController.sayHelloTo
For more details on routes syntax, check out the PlayFramework documentation.
Routing requests to actions is one thing. But refactoring routes can be a real pain if all your URLs are hard coded in your template views. Reverse routing is the solution.
Example route file:
GET     /user/?                 userController.listAll
GET     /user/{<[0-9]+>id}      userController.showUser
DELETE  /user/{<[0-9]+>id}      userController.deleteUser
POST    /user/add/?             userController.createUser
Reverse routing in your Java class:
import org.resthub.web.springmvc.router.Router;
public class MyClass {
  public void myMethod() {
    
    ActionDefinition action = Router.reverse("userController.listAll");
    // logs "/user/"
    logger.info(action.url);
    HashMap<String, Object> args = new HashMap<String, Object>();
    args.put("id",42L);
    ActionDefinition otherAction = Router.reverse("userController.showUser", args);
    // logs "/user/42"
    logger.info(otherAction.url);
  }
}
First, add the RouteDirective to your Velocity Engine configuration:
<!--
  Configure your Velocity Template engine.
  Add the custom directive to the engine.  
-->
<bean id="velocityConfig"
  class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
  <property name="resourceLoaderPath" value="classpath:velocity" />
  <property name="preferFileSystemAccess" value="false"/>
  <property name="velocityProperties">
    <props>
      <prop key="userdirective">org.resthub.web.springmvc.view.velocity.RouteDirective</prop>
    </props>
  </property>
</bean>
Then use the #route directive within your .vm file:
<a href="#route("userController.listAll")">List all users</a>
<a href="#route("userController.showUser(id:'42')")">Show user 42</a>
In your Spring MVC context add the following:
<mvc:interceptors>
    <bean class="org.resthub.web.springmvc.view.freemarker.RouterModelAttribute"/>
</mvc:interceptors>
This will inject a model attribute called "route" to every model. The attribute name can be modified by setting the property "attributeName".
<mvc:interceptors>
    <bean class="org.resthub.web.springmvc.view.freemarker.RouterModelAttribute">
        <property name="attributeName" value="myAttributeName"/>
    </bean>
</mvc:interceptors>
Then use the Router instance within your .ftl files:
<a href="${route.reverse('userController.listAll')}">List all users</a>
<#assign params = {"id":42}/>
<a href="${route.reverse('userController.showUser', params)}">Show user 42</a>
In your JSP, declare the taglib:
<%@ taglib prefix="route" uri="/springmvc-router" %>
Then use the reverse method to generate URLs:
<a href="<route:reverse action="userController.listAll" />">List all users</a>
Dynamic parameters can also be used:
<a href="<route:reverse action="userController.showUser" userId="42" />">Show user 42</a>
SpringMVC Router has its own LinkBuilder implementation to work with Spring HATEOAS.
springmvc-router-ide is a Maven plugin to generate template files that assist IDEs in autocompleting reverse routing with this project.
This project can be used as an addon to RESThub framework.
