如何在将全局前端控制器servlet映射到/*时访问静态资源

61

我将Spring MVC Dispatcher映射为一个全局的前置控制器Servlet在/*上。

<servlet>       
  <servlet-name>home</servlet-name>         
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>     
</servlet>  
<servlet-mapping>       
  <servlet-name>home</servlet-name>         
  <url-pattern>/*</url-pattern>     
</servlet-mapping>

然而,这种映射停止了对静态文件的访问,如CSS、JS、图片等,它们都在/res/文件夹中。

我该如何访问它们?

19个回答

75

将控制器Servlet映射到更具体的 url-pattern ,例如/pages/*,将静态内容放在特定文件夹中,例如/static,并创建一个过滤器(Filter)监听/*,它透明地继续处理任何静态内容并将请求分派给控制器Servlet来处理其他内容。

简言之:

<filter>
    <filter-name>filter</filter-name>
    <filter-class>com.example.Filter</filter-class>
</filter>
<filter-mapping>
    <filter-name>filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
    <servlet-name>controller</servlet-name>
    <servlet-class>com.example.Controller</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>controller</servlet-name>
    <url-pattern>/pages/*</url-pattern>
</servlet-mapping>

在过滤器的doFilter()中使用以下内容:

HttpServletRequest req = (HttpServletRequest) request;
String path = req.getRequestURI().substring(req.getContextPath().length());

if (path.startsWith("/static")) {
    chain.doFilter(request, response); // Goes to default servlet.
} else {
    request.getRequestDispatcher("/pages" + path).forward(request, response);
}
不,这不会在浏览器地址栏中显示“/pages”。这是完全透明的。如果有必要,您可以将“/static”和/或“/pages”设置为过滤器的“init-param”。

2
@MStodd:不要将视图转发回控制器,它已经完成了它的工作。将视图隐藏在/WEB-INF的某个地方,例如/WEB-INF/pages/default.jsp。更多提示请参见答案。 - BalusC
1
这是解决这个问题的一个杰出方案。 - sksamuel
因此,换句话说:在默认servlet前放置一个过滤器,以决定是否将请求重新映射为控制器请求。很不错。 - David Carboni
欺骗路径并在过滤器中删除/pages是我完美的解决方案,它允许在浏览器中仍然保留一个空的“可见”路径。非常感谢这个想法。 - BxlSofty
我按照你说的做了,结果在/pages servlet中出现了请求分派器的无限循环:request.getRequestDispatcher("WEB-INF/some.jsp").forward(request, response); - ErShakirAnsari
显示剩余9条评论

43

使用 Spring 3.0.4.RELEASE 或更高版本,你可以使用

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

正如在 Spring参考文档中所述。


21

你需要在web.xml中添加一个欢迎页面。

<welcome-file-list>
    <welcome-file>index.html</welcome-file>
</welcome-file-list>

然后将以下内容添加到您的servlet映射中,以便当某人转到应用程序的根目录时,他们会被内部发送到index.html,然后映射将在内部将它们发送到您将其映射到的servlet。

<servlet-mapping>
    <servlet-name>MainActions</servlet-name>
    <url-pattern>/main</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>MainActions</servlet-name>
    <url-pattern>/index.html</url-pattern>
</servlet-mapping>

最终结果:您访问/Application,但会呈现/Application/MainActions servlet,而不会干扰任何其他根请求。

明白了吗?所以您的应用程序仍然位于子URL,但当用户转到站点的根目录时,它会自动显示。这允许您将/images/bob.img仍然发送到常规位置,但'/'是您的应用程序。


我在Tomcat上找到了这个解决方案,它很适合我想要自己的index.template文件由自己的Servlet处理的情况。但我不知道这是否是Servlet规范所要求的解释 - 如果有什么的话,第10.10节关于欢迎文件似乎要求容器在检查映射之前提供静态资源...但我不确定我是否理解正确。 - David Bullock
这也是一个相关的地方,需要补充的是,在早期,JavaEE规范预见了“部署者”、“开发者”和“主机管理员”之间的角色分离。如今,Docker已经成为“可部署单元”,因此运行应用程序的Tomcat实例(或Jetty等)本身就是在开发时选择的组件。所以,如果它与您的Servlet容器一起工作,适用于您的应用程序,那就高兴吧。只有当您将Servlet作为可重用组件发布到“任何地方”时,才需要担心互操作性。而这几乎从不会发生。 - David Bullock

17
如果您使用Tomcat,您可以将资源映射到默认servlet:
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/static/*</url-pattern>
</servlet-mapping>

通过URL http://{上下文路径}/static/res/... 访问您的资源。

也适用于Jetty,其他Servlet容器不确定。


这是在App Engine上,正如原提问者所说,并不是在Tomcat或Jetty上。 - Nick Johnson
7
这是Tomcat中的一个安全漏洞(可以通过这种方式访问WEB-INF和META-INF内容),它已在7.0.4中修复(并将被移植到5.x和6.x版本)。 - BalusC
@BalusC 值得注意的是,Tomcat 安全修复仍允许此技术运行,但映射应该是 /res/* 而不是 /static/res/*,资源应该通过 http://{context path}/res/ 访问(这实际上更接近提问者所寻找的内容,尽管是在错误的 servlet 容器上)。 - CupawnTae

17

通过在多个servlet-mapping定义中使用适当后缀名服务静态内容,解决了一个评论中提到的安全问题。如下所引用:

这是Tomcat中的一个安全漏洞(可以通过这种方式访问WEB-INF和META-INF内容),已经在7.0.4中修复(也将移植到5.x和6.x)。– BalusC Nov 2 '10 at 22:44

这对我帮助很大。以下是我是如何解决它的:

<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>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
</servlet-mapping>

2
你可以将此内容添加到应用程序的web.xml文件中,无需编辑全局web.xml文件,该文件位于${TOMCAT_HOME}/conf/web.xml。 - mert inan
3
是的,这取决于您的需求。虽然我没有提到全局或应用程序级别的配置。因此,您的评论与此无关。 - GO.exe

12

我也遇到了这个问题,但从未找到一个好的解决方案。最终,我将我的servlet映射到URL层次结构中更高的一级:

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

现在您的基本上下文(以及/res目录中的所有内容)都可以由您的容器提供。


18
我真的很希望能看到更好的解决方案。在每个地方都带有/app/前缀真的很让人不爽。 - pjesi
1
@pjesi 你可以使用Tuckey URL Rewrite来解决这个问题。 - Adam Gent
你可能指的是http://tuckey.org/urlrewrite/ - 它还不错,但在appengine上它会提供您的静态内容,因此消耗您的实例时间; 我猜@Rahul想要一个servlet来服务“除静态文件之外的所有内容”,这样只有在需要时才会消耗实例时间。 - Petr Kozelka

9

6
碰撞的原因似乎是因为默认情况下,上下文根“/”由org.apache.catalina.servlets.DefaultServlet处理。这个servlet旨在处理静态资源请求。
如果您决定使用自己的servlet将其排除在外,并打算处理动态请求,则顶级servlet还必须执行catalina的原始“DefaultServlet”处理程序完成的任何任务。
如果您阅读Tomcat文档,它们提到真正的Apache(httpd)比Apache Tomcat更适合处理静态内容,因为它专门用于处理静态内容。我猜测这是因为Tomcat默认使用org.apache.catalina.servlets.DefaultServlet来处理静态请求。由于它全部包装在JVM中,并且Tomcat旨在作为Servlet/JSP容器,因此他们可能没有将该类编写为超级优化的静态内容处理程序。它在那里。它可以完成工作。足够好。
但那是处理静态内容的东西,而它位于“/”。因此,如果您放置其他任何内容,并且该内容不处理静态请求,那么您的静态资源就会消失。
我一直在寻找同样的答案,我到处得到的答案是“如果您不想那样做,请不要那样做”。
长话短说,您的配置正在将默认静态资源处理程序替换为根本不是静态资源处理程序的东西。您需要尝试不同的配置才能获得您要寻找的结果(我也需要)。

3
在App Engine中,“静态”文件不能直接被应用程序访问。你需要将它们上传两次,或者自己提供静态文件服务,而不是使用静态处理程序。

这对于应用引擎仍然适用吗? - Dave
@Dave 是的。这是一项基本的设计特性 - 静态文件由不涉及您的应用程序的路径提供服务。 - Nick Johnson
2
@Nick - 你说的上传两次是什么意思?我们需要单独的应用实例和单独的部署吗? - Derrick

2
处理这个问题的最佳方法是使用某种URL重写。通过这种方式,您可以拥有干净的RESTful URL,而不是带有任何扩展名,例如abc.com/welcom/register,而不是abc.com/welcome/resister.html。
我使用的是Tuckey URL,非常酷。
它有关于如何设置Web应用程序的说明。我已经将其与我的Spring MVC Web应用程序一起设置。当然,在我想要为Spring 3验证使用注释(例如@Email或@Null)来进行域对象时,一切都很好,直到我添加了Spring mvc指令:
< mvc:annotation-driven  /> 
< mvc:default-servlet-handler />

“它破坏了良好的Tuckey代码。显然,< mvc:default-servlet-handler />取代了Tuckey,我仍在努力解决。”

2
我将其编辑为更具回答性的内容。如果您想提出新问题,请随意 - 但是回答不是提问的地方。如果您解决了该问题,请返回并更新此内容。 - Tim Post

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