How to handle static content in Spring MVC?

Viewed 222064

I am developing a webapp using Spring MVC 3 and have the DispatcherServlet catching all requests to '/' like so (web.xml):

  <servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

Now this works as advertised, however how can I handle static content? Previously, before using RESTful URLs, I would have caught all *.html for example and sent that to the DispatcherServlet, but now it's a different ball game.

I have a /static/ folder which includes /styles/, /js/, /images/ etc and I would like to exclude /static/* from the DispatcherServlet.

Now I could get static resources working when I did this:

  <servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/app/</url-pattern>
  </servlet-mapping>

But I want it to have nice URLs (the point of me using Spring MVC 3) not the landing page being www.domain.com/app/

I also don't want a solution coupled to tomcat or any other servlet container, and because this is (relatively) low traffic I don't need a webserver (like apache httpd) infront.

Is there a clean solution to this?

23 Answers

Since I spent a lot of time on this issue, I thought I'd share my solution. Since spring 3.0.4, there is a configuration parameter that is called <mvc:resources/> (more about that on the reference documentation website) which can be used to serve static resources while still using the DispatchServlet on your site's root.

In order to use this, use a directory structure that looks like the following:

src/
 springmvc/
  web/
   MyController.java
WebContent/
  resources/
   img/
    image.jpg
  WEB-INF/
    jsp/
      index.jsp
    web.xml
    springmvc-servlet.xml

The contents of the files should look like:

src/springmvc/web/HelloWorldController.java:

package springmvc.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HelloWorldController {
 
 @RequestMapping(value="/")
 public String index() {
  return "index";
 }
}

WebContent/WEB-INF/web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
         http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

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

 <servlet-mapping>
  <servlet-name>springmvc</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>
</web-app>

WebContent/WEB-INF/springmvc-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:context="http://www.springframework.org/schema/context"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
 http://www.springframework.org/schema/mvc
 http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
 http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- not strictly necessary for this example, but still useful, see http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-controller for more information -->
 <context:component-scan base-package="springmvc.web" />

    <!-- the mvc resources tag does the magic -->
 <mvc:resources mapping="/resources/**" location="/resources/" />

    <!-- also add the following beans to get rid of some exceptions -->
 <bean      class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
 <bean
class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
 </bean>

    <!-- JSTL resolver -->
 <bean id="viewResolver"
  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="viewClass"
   value="org.springframework.web.servlet.view.JstlView" />
  <property name="prefix" value="/WEB-INF/jsp/" />
  <property name="suffix" value=".jsp" />
 </bean>

</beans>

WebContent/jsp/index.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h1>Page with image</h1>
<!-- use c:url to get the correct absolute path -->
<img src="<c:url value="/resources/img/image.jpg" />" />

If I understand your issue correctly, I think I have found a solution to your problem:

I had the same issue where raw output was shown with no css styles, javascripts or jquery files found.

I just added mappings to the "default" servlet. The following was added to the web.xml file:

 <servlet-mapping>
  <servlet-name>default</servlet-name>
  <url-pattern>*.css</url-pattern>
 </servlet-mapping>

 <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
 </servlet-mapping>

This should filter out the javascript and css file requests from the DispatcherRequest object.

Again, not sure if this is what you are after, but it worked for me. I think "default" is the name of the default servlet within JBoss. Not too sure what it is for other servers.

I've just been grappling with this issue in Spring MVC 3.0 and I initially went with the UrlRewriteFilter option. However I was not happy with this solution as it "didn't feel right" (I'm not the only one - see the link above to the Spring Forums where the word "hack" appears a few times).

So I came up with a similar solution to "Unknown (Google)" above but borrowed the idea of having all static content served from /static/ (taken from the Spring Roo version of the Pet Store app). The "default" servlet did not work for me but the Spring Webflow ResourceServlet did (also taken from Spring Roo generated app).

Web.xml:

<servlet>
    <servlet-name>mainDispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
</servlet>

<servlet>
    <servlet-name>Resource Servlet</servlet-name>
    <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>mainDispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<servlet-mapping>
    <servlet-name>Resource Servlet</servlet-name>
    <url-pattern>/static/*</url-pattern>
</servlet-mapping>

The only change I made to JSPs was to add the /static/ path to URLs for CSS, JS and images. E.g. "${pageContext.request.contextPath}/static/css/screen.css".

for Maven users the dependency for "org.springframework.js.resource.ResourceServlet" is:

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.js</artifactId>
    <version>2.0.8.RELEASE</version>
</dependency>

I know there are a few configurations to use the static contents, but my solution is that I just create a bulk web-application folder within your tomcat. This "bulk webapp" is only serving all the static-contents without serving apps. This is pain-free and easy solution for serving static contents to your actual spring webapp.

For example, I'm using two webapp folders on my tomcat.

  1. springapp: it is running only spring web application without static-contents like imgs, js, or css. (dedicated for spring apps.)
  2. resources: it is serving only the static contents without JSP, servlet, or any sort of java web application. (dedicated for static-contents)

If I want to use javascript, I simply add the URI for my javascript file.

EX> /resources/path/to/js/myjavascript.js

For static images, I'm using the same method.

EX> /resources/path/to/img/myimg.jpg

Last, I put "security-constraint" on my tomcat to block the access to actual directory. I put "nobody" user-roll to the constraint so that the page generates "403 forbidden error" when people tried to access the static-contents path.

So far it works very well for me. I also noticed that many popular websites like Amazon, Twitter, and Facebook they are using different URI for serving static-contents. To find out this, just right click on any static content and check their URI.

Place static contents like css ,js in following path

resources 
        ->static
               ->css
               ->js
(or) 
resources 
        ->public
               ->css
               ->js
Related