如何在Spring MVC中处理静态内容?

203

我正在使用Spring MVC 3开发Web应用程序,并且使用DispatcherServlet捕获所有对'/'的请求,如下所示(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>

现在这个程序可以正常运行,但是我如何处理静态内容呢?之前在使用RESTful URLs之前,例如我会捕获所有的*.html并将其发送到DispatcherServlet,但现在情况不同了。

我有一个/static/文件夹,其中包括/styles/、/js/、/images/等,我希望从DispatcherServlet中排除/static/*。

当我这样做时,我可以使静态资源工作:

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

但是我希望它具有良好的URL(这也是我使用Spring MVC 3的原因),而不是着陆页是www.domain.com/app/。

我也不想要一个与tomcat或任何其他servlet容器耦合的解决方案,并且由于这个网站流量相对较低,我不需要像Apache httpd这样的Web服务器。

是否有一个干净的解决方案?


@hamo 相关的讨论串:https://dev59.com/IJLea4cB1Zd3GeqPxQ_p - smwikipedia
23个回答

271

我在这个问题上花费了很多时间,所以我想分享我的解决方案。自从spring 3.0.4以来,有一个被称为<mvc:resources/>的配置参数(更多信息请参见参考文档网站),可以用于在仍然使用站点根目录下的DispatchServlet的同时提供静态资源。

为了使用它,请使用类似以下的目录结构:

src/
 springmvc/
  web/
   MyController.java
WebContent/
  resources/
   img/
    image.jpg
  WEB-INF/
    jsp/
      index.jsp
    web.xml
    springmvc-servlet.xml
文件内容应该看起来像这样:

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:

=>

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" />" />

13
这个例子应该出现在Spring用户指南中——这是我在这个主题上看到的最好的例子。谢谢Joris! - Paul
2
正如@Bane所指出的,<c:url value=... />是此解决方案的关键部分。您(或任何人)能告诉我为什么吗?谢谢! - Mark
您还可以使用Spring标签:<spring:url var="search" value="/resources/images/search.png" /> - sam
4
您也可以使用 <mvc:resources mapping="/**" location="/resources/" />,它将映射到根目录。(即:根目录将包含资源和 jsps)。这可以避免您在各处使用 c:url。 - efaj
@Mark 关于 <c:url>,请参考这个链接:在JSP中添加外部资源(CSS/JavaScript/images等) - informatik01
显示剩余5条评论

46

该问题在Spring 3.0.4.RELEASE中得到解决,您可以在Spring分发器配置文件中使用 <mvc:resources mapping="..." location="..."/> 配置元素。

请查看Spring文档


5
虽然这个回答并没有实际上“错误”,但它过于简略了,因为Spring自己的文档(你引用作为你的回答)似乎漏掉了一些内容。请查看Joris的回答以获得更完整的答案……不是因为它很冗长,而是因为他提到了<c:url ...>的使用,而你的回答和Spring的文件都没有提到——这被证明是解决方案中至关重要的一部分。 - Bane

38

在Spring 3.0.x中,将以下内容添加到您的servlet-config.xml文件中(该文件在web.xml中配置为contextConfigLocation)。您还需要添加mvc命名空间,但如果您不知道如何添加,只需使用Google搜索即可!

这对我有效。

<mvc:default-servlet-handler/>

当我添加这行代码时,出现以下错误:org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: 来自类路径资源[META-INF/spring/application-context.xml]的XML文档中第31行无效;嵌套异常是org.xml.sax.SAXParseException;行号:31;列号:35;cvc-complex-type.2.4.c:匹配通配符是严格的,但找不到元素“mvc:default-servlet-handler”的声明。 - Alex Worden
当您还有其他视图解析器时,请务必处理处理程序的顺序。 - phoenix

20

如果我正确理解了您的问题,我认为我已经找到了解决方案:

我曾经遇到同样的问题,即原始输出显示没有css样式、javascript或jquery文件。

我只是向“default”servlet添加了映射。以下内容已添加到web.xml文件中:

 <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>

这应该会从DispatcherRequest对象中过滤掉JavaScript和CSS文件请求。

再次说明,我不确定这是否符合您的要求,但它对我有用。 我认为“default”是JBoss中默认servlet的名称。 对于其他服务器,我不太确定。


1
我其实不想使用默认的servlet,因为那会让我与jboss/tomcat耦合。 - hamo
@hamo,这是一个问题吗?(这是一个真正的问题,而不是争论性的反驳)。无论如何,您都需要运行服务器(jboss/tomcat/jetty)才能使Spring运行,对吧? - Manav
3
你可以将所有的<url-pattern>标签添加到同一个<servlet-mapping>中。 - Milanka

16

有另一篇Stack Overflow的帖子提供了一个极好的解决方案

它似乎不是特定于Tomcat的,而且简单易用。我已经尝试了本帖中的几个解决方案,但在使用Spring MVC 3.1时遇到了问题,无法获取我的动态内容。

简而言之,它说要添加以下servlet映射:

<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/images/*</url-pattern>
</servlet-mapping>

11
我找到了一个方法,使用Tuckey的urlrewritefilter绕过了它。如果你有更好的答案,请随意提出! 在 web.xml 文件中:
<filter>
    <filter-name>UrlRewriteFilter</filter-name>
    <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>UrlRewriteFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

  <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>/app/*</url-pattern>
  </servlet-mapping>
在urlrewrite.xml中:
<urlrewrite default-match-type="wildcard">
<rule>
    <from>/</from>
    <to>/app/</to>
</rule>
<rule match-type="regex">
    <from>^([^\.]+)$</from>
    <to>/app/$1</to>
</rule>
<outbound-rule>
    <from>/app/**</from>
    <to>/$1</to>
</outbound-rule>    

这意味着任何带有“.”的URI(例如style.css)都不会被重写。


5
更好的答案是Spring 3的<mvc:resources/>,正如@Joris所演示的那样。 - Paul

11

我刚在Spring MVC 3.0中遇到了这个问题,最初我选择了UrlRewriteFilter。但是我对这种解决方案并不满意,因为它“感觉不对”(我不是唯一一个这样认为的人-请参见上面的Spring论坛链接,其中出现了几次“hack”一词)。

因此,我提出了与“Unknown(Google)”类似的解决方案,但借鉴了所有静态内容都从/static/提供的想法(取自Spring Roo版本的Pet Store应用程序)。 “默认”servlet对我没有起作用,但Spring Webflow ResourceServlet有用(也来自Spring Roo生成的应用程序)。

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>
我做了唯一的一件事是将静态文件(CSS、JS和图像)的URL路径添加了"/static/"前缀。例如:"${pageContext.request.contextPath}/static/css/screen.css"。
对于Maven用户,"org.springframework.js.resource.ResourceServlet"的依赖如下:
<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>org.springframework.js</artifactId>
    <version>2.0.8.RELEASE</version>
</dependency>

不错的解决方案,nickdos,谢谢!我仍然不明白为什么核心Spring MVC中没有资源servlet(而不是必须添加另一个带有Web Flow的依赖项)或其他开箱即用的解决方案。Urlrewrite对我来说很好用,所以我会继续使用它!干杯,Hamo - hamo
2
回顾Spring Pet Clinic应用程序的标准(非Roo)版本,我注意到“default”的servlet定义被注释掉,并附有以下注释:“在容器(GlassFish)中取消注释,因为它们没有默认声明此隐式定义”。默认情况下的显式包声明是org.apache.catalina.servlets.DefaultServlet。所以这可能是您的“开箱即用”资源servlet(?)。我在开发工作中使用Jetty,而Jetty似乎不提供隐式的默认servlet(如Glassfish)。 - nickdos

8

我对这个问题的经验如下。大多数与Spring相关的网页和书籍似乎建议使用以下语法。

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

以上语法建议您将静态资源(CSS、JavaScript、图像)放置在名为“resources”的文件夹中,该文件夹位于应用程序的根目录下,即/webapp/resources/。

然而,根据我的经验(我正在使用Eclipse和Tomcat插件),唯一有效的方法是将您的资源文件夹放置在WEB-INF(或META-INF)内部。因此,我建议采用以下语法。

    <mvc:resources mapping="/resources/**" location="/WEB-INF/resources/" />

在您的JSP(或类似页面)中,可以按以下方式引用资源。
<script type="text/javascript"
        src="resources/my-javascript.js">
</script>

不用多说,整个问题的起因是因为我想让我的Spring调度器servlet(前置控制器)拦截所有动态请求。所以我在web.xml文件中添加了以下内容。

<servlet>
    <servlet-name>front-controller</servlet-name>
    <servlet-class>
                org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
    <!-- spring automatically discovers /WEB-INF/<servlet-name>-servlet.xml -->
</servlet>

<servlet-mapping>
    <servlet-name>front-controller</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

最后,由于我正在使用当前的最佳实践,因此我的前端控制器servlet xml中包含以下内容(请参见上文)。

<mvc:annotation-driven/>

我在实际控制器中实现了以下内容,以确保有一个默认方法来处理所有传入的请求。

@RequestMapping("/")

这是唯一对我有效的解决方案。我注意到一个有趣的事情,就是在我启动Web应用程序之后添加的新资源直到重新启动后才能找到。从用户角度来看没有意义,但大多数情况下不应该是一个主要问题。 - Rafael Steil
你从Tomcat的角度来讲述,太棒了 :) - omkar sirra
我尝试了这页上的每个答案。幸运的是,当我看到这一个时,我不必再尝试其他的了。 - TimeTrax

7

我遇到了同样的问题,发现Joris的答案非常有帮助。但是,我还需要添加以下内容:

<mvc:annotation-driven /> 

将资源映射到servlet配置文件中。如果没有这个资源映射,所有处理程序都将停止工作。


2
我是这样解决的:
<servlet-mapping>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.css</url-pattern>
</servlet-mapping>

这适用于Tomcat和Jboss。然而最终我决定使用Spring提供的解决方案(正如rozky所提到的),它更加便携。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接