Creating RESTful Urls with Spring MVC 3.1 Part One: default-servlet-handler

This blog entry describes how you can create a web application which has restful url addresses by using Spring MVC 3.1 and the default-servlet-handler element of MVC XML namespace. The requirements for RESTful urls are given in following:

  • An url address must no have suffix in it (in other words, an url address most not contain a suffix like '.action').
  • The context path of the web application must not start with a prefix like 'app'.

I have figured out three alternative solutions to this problem and first of them is described in following.

Required Steps

You can fulfill the given requirements by implementing the following steps:

  • Configuring the application context
  • Configuring the web application and the dispatcher servlet

I will describe these steps with more details in following Sections.

Configuring the Application Context

First, you have to configure the application context of your application. This step consists of three smaller steps:

  1. Configure the location of static resources such as css files.
  2. Ensure that static resources are served by the container's default servlet
  3. Create an application context configuration class.

The first two steps can be implemented by creating an application context configuration file which content is given in following (NOTE: As Rossen pointed out, you can use Java configuration as well. See his comment for more details):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       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-3.1.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd">
    <!--
        Configures the location of static resources such as css files.
        Requires Spring Framework 3.0 or higher.
     -->
    <mvc:resources mapping="/static/**" location="/static/"/>

    <!--
        Ensures that dispatcher servlet can be mapped to '/' and that static resources 
        are still served by the containers default servlet. Requires Spring Framework
        3.0 or higher.
     -->
    <mvc:default-servlet-handler/>
</beans>

The third step of the application context configuration can be implemented by using a simple Java configuration class which enables the Spring MVC support, imports the application context configuration file, sets the component scan base package and configures a view resolver bean. The source code of this configuration class is given in following:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

/**
 * An application context Java configuration class. The usage of Java configuration
 * requires Spring Framework 3.0 or higher with following exceptions:
 * <ul>
 *     <li>@EnableWebMvc annotation requires Spring Framework 3.1</li>
 * </ul>
 * @author Petri Kainulainen
 */
@Configuration
@ComponentScan(basePackages = {"net.petrikainulainen.spring.restful.controller"})
@EnableWebMvc
@ImportResource("classpath:applicationContext.xml")
public class ApplicationContext {
    
    private static final String VIEW_RESOLVER_PREFIX = "/WEB-INF/jsp/";
    private static final String VIEW_RESOLVER_SUFFIX = ".jsp";
    
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();

        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix(VIEW_RESOLVER_PREFIX);
        viewResolver.setSuffix(VIEW_RESOLVER_SUFFIX);

        return viewResolver;
    }
}

Configuring the Web Application and the Dispatcher Servlet

Second, you must configure your web application and map the dispatcher servlet to url pattern '/'. This example configures the web application by using Java configuration which was introduced by the Servlet 3.0 API. However, it should be fairly easy to create a web.xml configuration file based on this example if you cannot use Spring Framework 3.1 and Servlet 3.0. Configuring a web application by using Java is easy. All you have to do is to implement the WebApplicationInitializer interface. My implementation of that interface is described in following:

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.*;

/**
 * Web application Java configuration class. The usage of web application
 * initializer requires Spring Framework 3.1 and Servlet 3.0.
 * @author Petri Kainulainen
 */
public class RestfulInitializer implements WebApplicationInitializer {
    
    private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
    private static final String DISPATCHER_SERVLET_MAPPING = "/";
    
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(ApplicationContext.class);

        ServletRegistration.Dynamic dispatcher = servletContext.addServlet(DISPATCHER_SERVLET_NAME, new DispatcherServlet(rootContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping(DISPATCHER_SERVLET_MAPPING);

        servletContext.addListener(new ContextLoaderListener(rootContext));
    }
}

The End

I have now described to you how you can configure your web application to use RESTful url addresses. This solution seems pretty clean but for some reason I was unable to get it to work with Jetty 8.1.0.RC0. This seems pretty weird to me because the mvc:default-servlet-handler Subsection of Spring Framework 3.1 reference documentation states that:

The caveat to overriding the "/" Servlet mapping is that the RequestDispatcher for the default Servlet must be retrieved by name rather than by path. The DefaultServletHttpRequestHandler will attempt to auto-detect the default Servlet for the container at startup time, using a list of known names for most of the major Servlet containers (including Tomcat, Jetty, Glassfish, JBoss, Resin, WebLogic, and WebSphere).

Since I was able to run my example application without any problems with Tomcat 7.0.23, I am wondering if the problem is somehow related to my configuration. At the moment I am suspecting that the problem is related to the default servlet name of Jetty 8.1.0.RC0 because the Spring Framework's reference documentation also states that:

If the default Servlet has been custom configured with a different name, or if a different Servlet container is being used where the default Servlet name is unknown, then the default Servlet's name must be explicitly provided

I decided to publish blog entry anyway because I feel that this might be valuable to someone. I had also a selfish motive since I love to use Jetty during development phase and I am hoping that someone can help to find a solution to my problem. I am going to offer you one in the next part of this series but I am not totally satisfied with it. It just does not feel right.

P.S. I have also created an example application which you can use to test the advice given in this blog entry. The source of this application is available at GitHub. If you have any advice concerning the problem I am having with Jetty 8.1.0.RC0, I be would interested to hear it out.

22 comments… add one
  • Rossen Stoyanchev Jan 10, 2012 @ 15:58

    Just one small suggestion. You don't actually need to use the MVC namespace (i.e. mvc:resources, etc.). Since you're already using the MVC Java config you can configure your resource paths and the default servlet handling in Java config like it's done here:

    https://github.com/SpringSource/spring-test-mvc/blob/master/src/test/java/org/springframework/test/web/server/samples/context/WebConfig.java

    As for Jetty you haven't mentioned any details on what the exact problem you see is. Have you tried specifying the name of the default servlet?

  • Petri Jan 10, 2012 @ 17:22

    Thanks for the tip! That is very useful since I am not anymore a fan of XML configuration files. My problem with Jetty Maven Plugin 8.1.0.RC2 (It does not work this version either) is that when I start the web application and write the url localhost:8080 to my browser, it shows a list of files instead of the home page. I presume that this means that the request is handled by the default servlet of Jetty instead of the dispatcher servlet. I have planned to test this with standalone Jetty but I have not got time to do it yet. I did try to change the name of the default servlet to 'default' but this did not help. Anyway, I think that I will try running the web application with standalone Jetty and see how it goes.

  • Tim Jul 18, 2012 @ 16:28

    Hi Petri.

    Thanks for article. But i getting 405 error when trying to get static resource. Can you help me to resolve this?

    • Petri Jul 18, 2012 @ 17:24

      Hi Tim,

      Thanks for your comment. I have few questions regarding to your problem:

      Are you using the XML configuration or Java configuration to configure the location how static resources?

      Are the controller mappings working properly?

      Which servlet container (and version) are you using for running your application?

      I think that this should get us started. Please note that I fixed my example application a moment ago. It now works without problems when using Jetty Maven plugin.

      • Tim Jul 18, 2012 @ 17:45

        >Are you using the XML configuration or Java configuration to configure the location how static resources?
        Both. I start to develop with xml mapping, but after i started to get 405 i moved to annotation config (like in yours and Rossen Stoyanchev example). Error is the same.

        >Are the controller mappings working properly?
        Yes. I getting controllers content, but not resources content. If i turn off controllers in xml config i can get resource files!

        >Which servlet container (and version) are you using for running your application?
        Tomcat 7.0.29

        • Petri Kainulainen Jul 18, 2012 @ 18:02

          I think that we should start tracking this problem from the Spring configuration. Your answers raised a few more questions:

          Are you using XML or annotation for enabling Spring Web MVC?

          Are you using Java configuration or XML to configure the location of static resources? In other words, are you adding the resources element of the mvc namespace to your application context configuration file or are you using the approach mentioned by Rossen?

          Where is your static resource directory located? I assume that is added to web application root directory. Am I correct?

          Also, it would be useful if you could add the configuration of your static resources to either Pastebin or here.

          • Tim Jul 18, 2012 @ 18:40

            Are you using XML or annotation for enabling Spring Web MVC?
            I have both configs. Now i disable all Spring Web MVC xml configurations and made annotation only config.

            Are you using Java configuration or XML to configure the location of static resources?
            As i wrote i have two configs(xml and annotation) for the same things. Lets discus annotation config.

            In other words, are you adding the resources element of the mvc namespace to your application context configuration file or are you using the approach mentioned by Rossen?
            Rossen approach.

            Where is your static resource directory located? I assume that is added to web application root directory. Am I correct?
            Yes you are right.

            Annotation config class snipet:

            @Configuration
            @EnableWebMvc
            @ComponentScan(basePackages = {"my.package"})
            @ImportResource({"classpath:applicationContext.xml", "classpath:mongo-db-config.xml"})
            public class WebConfig extends WebMvcConfigurerAdapter {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/css/**").addResourceLocations("/css/");
            }

            @Override
            public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
            configurer.enable();
            } ....
            }

            WebProject Structure snipet
            /css
            /js
            /WEB-INF/...

            GET http://localhost:8080/css/bootstrap.css

            Response
            WARNING: Request method 'GET' not supported

          • Tim Jul 18, 2012 @ 18:42

            Forgot about WebApplicationInitializer, it's like in yous example.

          • Petri Jul 18, 2012 @ 18:55

            Your annotation configuration and the project structure looks fine to me. However, the error messages gives us a little clue what could be wrong. It looks suspiciously similar than error messages, which I received when I have tried to access a controller that does not support the used request method.

            This would mean that your request is being processed by the dispatcher servlet. This is not the wanted behavior. Have you mapped your dispatcher servlet to root path ('/')?

          • Tim Jul 19, 2012 @ 9:46

            > It looks suspiciously similar than error messages, which I received when I have tried to access a controller that does not support the used request method.
            Yes, it looks like that but don't.

            Have you mapped your dispatcher servlet to root path (‘/’)?
            Yes.

            Look. I just test it (Same changes work in xml config). If i comment just one code line
            @ComponentScan(basePackages = {“my.package”})
            resourse file start working fine. I can get it!

            It's looks like annotated controllers in package "my.package" overrides resource files mapping. In package "my.package" there are no controller mapped to /css.

          • Petri Jul 19, 2012 @ 10:04

            Hmmh. That does indeed look a bit weird.

            Do you have got controllers that are mapped to "/*"? I cannot remember the exact format but I remember reading somewhere that it is possible to use wild cards in controller mappings.

            Also, since everything starts "working" after you remove commented out component scan annotation, you might want to double check that XML configuration file does not contain any configuration that is already present in the Java configuration class.

          • Tim Jul 19, 2012 @ 10:30

            I just found my stupid mistake. It was controller method mapped to
            @RequestMapping(value = {"/{interviewId}/{questionId}"}
            And and that's why i cant get resource file. Thanks for your help.

          • Petri Jul 19, 2012 @ 10:37

            Tim,

            Good to hear that you found the problem! Also, thanks for coming back and telling what the root cause was. Maybe this information will be useful to someone else.

  • Tim Oct 25, 2012 @ 10:28

    Hi Petri.

    I have another problem with annotation config. Maybe you can help me.
    Do you know is it possible to config spring aplication via annotation config, but do not use dynamic servlet registration? Use web.xml for registration DispatcherServlet and use annotation config class for spring config?

  • Dirk Oct 2, 2013 @ 11:00

    The 'basePackages' attribute of @ComponentScan suggerates that it is possible to list more than just one package and it is syntactically correct to comma-separate multiple packages but this always ends in an exception when starting TOMCAT:

    org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.foo.bar.repository.MyRepository] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

    What causes this exception?
    The service (implementation) is annotated with @Service and the controller is annotated with @Controller.

    • Petri Oct 2, 2013 @ 11:57

      It is indeed possible to enter multiple packages as the value of the basePackages attribute. Here is a small example about this:

      
      @ComponenScan(basePackages = {
      	"com.foo.bar.controller",
      	"com.foo.bar.service"
      })
      
      

      The problem seems to be that Spring cannot find the com.foo.bar.repository.MyRepository class. I assume that this should be injected to your service.

      If you are using Spring Data JPA, you should verify that it is configured properly.

      Here is an example how you can configure Spring Data JPA and set the base package of your repositories when you use Java configuration:

      
      @EnableJpaRepositories(basePackages = {
              "com.foo.bar.repository"
      })
      

      Here is an example how you can configure Spring Data JPA repositories and set the base package of your repositories when you use XML configuration:

      
      <jpa:repositories base-package="com.foo.bar.repository"/>
      
      

      If you are not using Spring Data JPA, you should configure the base package of your repositories by using the @ComponentScan annotation.

      I hope that this answered to your question.

      • Dirk Oct 2, 2013 @ 17:15

        Thumb up! That's it! I forgot the @EnableJpaRepositories(...) annnotation.
        Thank you very much, Petri.

        • Petri Oct 2, 2013 @ 19:46

          Thanks! I am happy to hear that you could solve your problem!

Leave a Reply