Spring <mvc:resources ... /> request routing

1.5k views Asked by At

TL;DR: I'm writing a simple Tomcat/Spring/Freemarker webapp and seem to be having a lot of trouble getting Spring's DispatcherServlet to honor <mvc:resources...> configuration.

PLEASE NOTE: AFAICT this is not a duplicate of other questions since I've tried the solutions given in other answers. If someone finds an existing question and that question provides a solution I missed, I'll gladly VTC as a dup (or delete this question if desired).

I have a very simple webapp based on Freemarker. That part is working fine. I have a single request-handler method to handle all requests (@RequestMapping("/**")) but wish to serve static resources /${contextPath}/static/... using the <mvc:resources.../> facility. The resources are located in subdirectories of the top-level webapp directory.

Based on reading other questions on SO I added

<mvc:resources mapping="/static/**" location="/" /> 

to my Spring configuration.

No matter what I do, requests that I expect to be resolved as a static resource file are being sent to the request handler method of my controller instead. The only thing I can think of is that the @RequestMapping annotation has precedence over mvc:resources, but that doesn't make a lot of sense.

I have verified that the resource URLs are being generated correctly, i.e. the template line

<link rel="stylesheet" href="${contextPath}/static/css/gallery.css">

generates

<link rel="stylesheet" href="/gallery/static/css/gallery.css">

and the request is being received by the Tomcat server, just routed to the wrong place.

I've read most of the questions on SO about this subject and I believe I'm doing it right (see for example Trying to get mvc resources to serve my static resources), but clearly I'm missing something obvious.

Environment

  • Eclipse Luna
  • Java 8
  • Tomcat 8
  • Freemarker 2.3.23
  • Spring 4.2.0
  • Windows 7 SP1

Deployed Layout

Standard Java EE Tomcat directory structure

webapps
 |- gallery
     |- css
     |- images
     |- js
     |- META-INF
     |- WEB-INF
         |- classes
         |- lib
         |- views

Tomcat Context Definition

<?xml version="1.0" encoding="UTF-8"?>
<Context docBase="D:\dev\tools\apache-tomcat-8.0.24\wtpwebapps\gallery" 
         path="/gallery" reloadable="true" 
         source="org.eclipse.jst.j2ee.server:gallery"/>

Spring Servlet Config

WEB-INF/main-servlet.xml

...
<mvc:resources mapping="/static/**" location="/" />
<mvc:annotation-driven/>
<context:component-scan base-package="com.mysite.gallery"/>
...

I've tried all possible orderings of these statements but that seems to have no effect. I also tried adding

<mvc:default-servlet-handler/>

with no effect.

WEB-INF/web.xml

Standard Spring MVC DispatcherServlet config

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

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

  <servlet-mapping>
      <servlet-name>main</servlet-name>
      <url-pattern>/*</url-pattern>
  </servlet-mapping>
  ...

RequestMapping method

...
@RequestMapping("/**")
public String gallery(ModelMap modelMap, HttpServletRequest req, HttpServletResponse resp)
{
    etc...

Freemarker Template

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="${contextPath}/static/css/photoswipe.css">
    <link rel="stylesheet" href="${contextPath}/static/css/default-skin/default-skin.css">
    <link rel="stylesheet" href="${contextPath}/static/css/gallery.css">
    <script src="${contextPath}/static/js/photoswipe.min.js"></script>
    <script src="${contextPath}/static/js/photoswipe-ui-default.min.js"></script>
  </head>
  <body>
    <div>
...
1

There are 1 answers

4
Serge Ballesta On BEST ANSWER

You gave the answer in your question: The only thing I can think of is that the @RequestMapping annotation has precedence over mvc:resources, but that doesn't make a lot of sense.

You may think it does not make sense, but is is how Spring fellow decided it should be, at least by default...

But you have different ways for static ressources to have precedences on the @RequestMapping annotated controller.

The simplest way is to add an "order=0" attribute to the <mvc:resources.../> tag :

<mvc:resources mapping="/static/**" location="/" order="0"/>

and to write it in WEB-INF/main-servlet.xml before <mvc:annotation-driven/>

I tested in with Spring 3.2.4 and it works like you want. But it looks like driving spring framework where it was not designed for, so I cannot guarantee that it will work on other versions.


You could also map the dispatcher servlet to / instead of /*. That way all static resources (and also JSP) that can be served directly by the servlet container will be. The main caveat with that mapping, is that root will no longer be served by Spring dispatcher servlet. One possible workaround it to use a /home URL for the root controller, and put a welcome file in web xml :

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

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

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

That way, you to not even need to use /mappings/images/img.gif, just use /images/img.gif/.

But there is another caveat, that I have always accepted (because I could not find any workaround): if /url is served by one controller, /url.jsp or /url.jpeg will give a 404 error. As the container default servlet knows about .jpeg or .jsp files it will catch the request and fail.