1

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>
...
Community
  • 1
  • 1
Jim Garrison
  • 85,615
  • 20
  • 155
  • 190

1 Answers1

3

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.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • I'll play with this tomorrow, and keep in mind that it's venturing outside normal configuration. I may have to rethink my approach. Thanks, will accept tomorrow. – Jim Garrison Aug 13 '15 at 03:37
  • Your suggestion of `order="0"` works. Can you clarify why you think this goes against the way Spring MVC is supposed to work, and why it's not a long-term solution? – Jim Garrison Aug 13 '15 at 20:11
  • @JimGarrison: Well I'm not really sure, but all examples I found in Spring docs had separate URL spaces for resources and controllers. And in my tests I found a configuration where when giving the correct URL I got the resource, next I gave same URL without the extension and hit the controllers. No real surprise till here. But since that moment, even the correct URL hit the controller. That's the reason why I think that this solution is on the edge. It works, but no doc specifies how and why it does, so a different implementation on a later version could break it. – Serge Ballesta Aug 13 '15 at 20:58
  • I see. After digging around I found [this](http://stackoverflow.com/questions/870150) which seems helpful too. – Jim Garrison Aug 13 '15 at 21:07