使用Spring Boot创建静态内容的项目,在运行jar包时会出现404错误。

32
最近Spring的博客文章(https://spring.io/blog/2013/12/19/serving-static-web-content-with-spring-boot)提到了在Spring Boot项目中使用静态Web内容时可以使用多个资源目录:
  • /META-INF/resources/
  • /resources/
  • /static/
  • /public/
这要归功于WebMvcAutoConfiguration类,它会自动将这些目录添加到类路径中。当使用spring-boot-maven-plugin spring-boot:run目标运行时,所有静态内容都能正常工作(例如:/index.html)。
当你打包Spring Boot项目并允许spring-boot-maven-plugin创建增强JAR后,尝试使用java -jar my-spring-boot-project.jar运行你的项目时,你会发现静态内容现在返回404错误。
8个回答

48

事实证明,尽管Spring Boot很聪明地将各种资源目录添加到类路径中,但Maven没有这样做,你需要处理这一部分。默认情况下,只有src/main/resources会被包含在JAR文件中。如果你在项目的根目录下创建一个名为/static的文件夹(如博客文章所示),那么在使用spring-boot:run Maven目标时它将正常工作,但一旦你创建了JAR文件就不行了。

最简单的解决方案是在/src/main/resources内部创建/static文件夹,然后Maven会将其包含在JAR文件中。或者,你可以向Maven项目添加其他资源位置:

<resources>
    <resource>
        <directory>src/main/resources</directory>
    </resource>
    <resource>
        <directory>static</directory>
        <targetPath>static</targetPath>
    </resource>
</resources>

我希望这对某些人有用,当你回过头看Maven的工作原理时,它似乎很明显,但对于使用Spring Boot的一些人来说可能会遇到困难,因为它被设计为几乎没有配置。


仅供参考:我认为您误读了博客,因为它根本没有提到Maven,并且也没有使用JAR归档文件。如果您按照Roy在博客中所做的完全相同的步骤进行操作,它应该可以正常工作。 - Dave Syer
你有示例项目吗?这样我就可以看看了...我还是无法让它工作。 - Chris DaMour
1
请问您能否编辑答案,将目录路径前面的“/”删除?应该是<directory>src/main/resources</directory>和<directory>static</directory>。 - hellboy
3
对我来说这并没有起作用,仅在pom.xml中添加资源是不够的。相反,我必须使用<addResources>true</addResources>配置Spring Boot插件。请参见此Stack Overflow答案https://dev59.com/nGAf5IYBdhLWcg3wVxcp - ug_

8
我正在苦苦思考如何在gradle中实现这一点。有什么建议吗?
编辑:我通过将以下内容添加到我的build.gradle文件中使其工作:
// Copy resources into the jar as static content, where Spring expects it.
jar.into('static') {
    from('src/main/webapp')
}

2
Gradle默认也使用“src/main/resources”。使用那个有什么问题吗? - Dave Syer
3
没错,直接把文件放在资源文件夹中会使其被复制到JAR包中。但是,为了让Spring将它们识别并作为静态内容提供,你需要将其打包在名为"static"、"public"、"resources"或"/META-INF/resources/"的目录下。如果你只是简单地把文件放在资源文件夹中,它们都会被复制到JAR包的根目录中,并且当你尝试访问它们时,Spring会返回404错误。 - kgreenek
5
好的,最简单(配置最少,功能最大)的解决方案是将你的静态资源放在“src/main/resources/static”中。这就是我所说的全部意思。 - Dave Syer
如果你让npm/frontend的构建任务依赖于jar,它将已经收集了/resources/static到/build/lib blabla中,因此你需要让它依赖于compileJava,这样你的静态内容就已经构建好了(对于未来的访问者,只是遇到了同样的问题)。 - Busata

4

有两件事需要考虑(Spring Boot v1.5.2.RELEASE)-

1)检查所有Controller类是否有@EnableWebMvc注释,如果有则删除它

2)检查使用的控制器类的注释 - @RestController或@Controller。

不要在一个类中混合Rest API和MVC行为。对于MVC,请使用@Controller,对于REST API,请使用@RestController

Doing above 2 things resolved my issue. Now my spring boot is loading static resources with out any issues.
@Controller => load index.html => loads static files.

@Controller
public class WelcomeController {

    // inject via application.properties
    @Value("${welcome.message:Hello}")
    private String message = "Hello World";

    @RequestMapping("/welcome")
    public String welcome(Map<String, Object> model) {
        model.put("message", this.message);
        return "welcome";
    }

    @RequestMapping("/")
    public String home(Map<String, Object> model) {
        model.put("message", this.message);
        return "index";
    }

}

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />


    <link rel="stylesheet/less" th:href="@{/webapp/assets/theme.siberia.less}"/>

    <!-- The app's logic -->
    <script type="text/javascript" data-main="/webapp/app" th:src="@{/webapp/libs/require.js}"></script>
    <script type="text/javascript">
        require.config({
            paths: { text:"/webapp/libs/text" }
        });
    </script>

     <!-- Development only -->
     <script type="text/javascript" th:src="@{/webapp/libs/less.min.js}"></script>


</head>
<body>

</body>
</html>

我认为你的问题可能是由于使用了@RestController注解导致的 - 这个注解与@Controller相同,但还会在每个方法上添加@ResponseBody,这意味着你从控制器方法返回的任何内容都会出现在响应体中,可能会被转换器转换。在你的情况下,这可能意味着你在浏览器中看到的是作为字符串返回的“welcome”或“index”,而不是加载“welcome.html”或“index.html”视图。 - Robert Hunt
太棒了!!!我在工作中遇到了index.html的问题,我删掉了@EnableWebMvc,问题解决了!!非常感谢! - tm1701

3

Spring Boot使用WebMvcAutoConfiguration来配置静态内容,这个类只在没有自定义的WebMvcConfigurationSupport bean时生效,在我的情况下,我确实有一个自定义的bean。

@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
        WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
        ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {

}

那我只能自己配置了,这是我的解决方案:

@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/static-file/**").addResourceLocations("classpath:/static/");
}

3

我正在浏览一些页面,以了解如何在Spring Boot环境中提供静态内容。大多数建议是将静态文件放置在/static /resources/ src/main/webapp等位置。以下是我的想法。

  1. 允许Spring Boot自动配置Dispatcher Servlet - 确保DispatcherServletAutoConfiguration不在AutoConfiguration的排除列表中。

    @EnableAutoConfiguration(exclude = { //DispatcherServletAutoConfiguration.class, })

  2. 注入外部目录以进行静态内容路由

    @Value("${static-content.locations:file:C:/myprj/static/") private String[] staticContentLocations;

3.使用WebMvcConfigurerAdapter覆盖WebMvcAutoConfiguration,以建议Spring不要使用默认资源位置,而是使用我们指示的位置。就像下面这样:

@Bean
    public WebMvcConfigurerAdapter webMvcConfigurerAdapter()
    {
        return new WebMvcConfigurerAdapter()
        {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry)
            {
                if (!registry.hasMappingForPattern("/**"))
                {
                    // if this is executed spring won't add default resource
                    // locations - add them to the staticContentLocations if
                    // you want to keep them
                    // default locations:
                    // WebMvcAutoConfiguration.RESOURCE_LOCATIONS
                    registry.addResourceHandler("/**").addResourceLocations(
                            staticContentLocations);
                }
            }
        };
    }

如果C:/myprj/static目录下有index.html文件,那么http://localhost:portno/index.html将会起作用。希望这对您有所帮助。

有一种更简单的方法可以做到这一点,你可以在application.properties/application.yml文件中将你的位置指定为spring.resources.static-locations属性的值,甚至可以将其作为命令行参数传递。 - Robert Hunt

3

我必须在pom.xml中添加thymeleaf依赖项。 没有这个依赖项,Spring Boot将无法找到静态资源。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

谢谢。我试图从Javadoc和JaCoCo托管静态内容。我在本地使其工作,但是一旦我部署后,链接就会显示404错误。我想不出原因,但添加此依赖关系使其起作用。 - Cody Pritchard

0

我在静态目录下有一个资产文件夹,并在扩展了WebSecurityConfigurerAdapter的SpringConfig中进行了允许。

http.authorizeRequests().antMatchers("/", "/login", "/assets/**")
            .permitAll()

0

我在尝试创建一个沙盒示例作为模板(https://github.com/fluentcodes/sandbox/tree/java-spring-boot)时,意外地遇到了这个404主题:我的现有工作解决方案中没有提供静态内容index.html。

由于示例相当简单,现有解决方案也能正常工作,所以我没有深入研究提到的spring解决方案。

我查看了创建的目标类,但并没有像预期的那样找到static/index.html。

原因相当简单:我将资源创建在了src/而不是src/main/下。需要一些时间来寻找,因为提到了许多复杂的解决方案。对我来说,它有一个相当琐碎的原因。

另一方面,在启动spring boot时:为什么我没有得到任何提示,表明无法找到来自location1、location2....locationx的静态内容呢?我认为这是一个话题。


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