58

我正在为基于 react 的单页应用程序开发一个 spring 后端,在该应用程序中我使用 react-router 进行客户端路由。

除了 index.html 页面,后端还提供 path 上的数据/api/**

为了在我的应用程序src/main/resources/public/index.html的根路径上提供我的 index.html,/我添加了一个资源处理程序

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/").addResourceLocations("/index.html");
}

我想要的是在没有其他路由匹配时为 index.html 页面提供服务,例如,当我调用除/api.

如何在春季配置这样的包罗万象的路线?

4

8 回答 8

45

由于我的反应应用程序可以使用根作为前向目标,这最终对我有用

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
      registry.addViewController("/{spring:\\w+}")
            .setViewName("forward:/");
      registry.addViewController("/**/{spring:\\w+}")
            .setViewName("forward:/");
      registry.addViewController("/{spring:\\w+}/**{spring:?!(\\.js|\\.css)$}")
            .setViewName("forward:/");
  }
}

老实说,我不知道为什么它必须完全采用这种特定格式以避免无限转发循环。

于 2017-03-24T11:51:54.207 回答
29

我在 Spring Boot 应用程序中托管了一个基于 Polymer 的 PWA,以及图像等静态 Web 资源,以及“/api/...”下的 REST API。我希望客户端应用程序处理 PWA 的 URL 路由。这是我使用的:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    /**
     * Ensure client-side paths redirect to index.html because client handles routing. NOTE: Do NOT use @EnableWebMvc or it will break this.
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // Map "/"
        registry.addViewController("/")
                .setViewName("forward:/index.html");

        // Map "/word", "/word/word", and "/word/word/word" - except for anything starting with "/api/..." or ending with
        // a file extension like ".js" - to index.html. By doing this, the client receives and routes the url. It also
        // allows client-side URLs to be bookmarked.

        // Single directory level - no need to exclude "api"
        registry.addViewController("/{x:[\\w\\-]+}")
                .setViewName("forward:/index.html");
        // Multi-level directory path, need to exclude "api" on the first part of the path
        registry.addViewController("/{x:^(?!api$).*$}/**/{y:[\\w\\-]+}")
                .setViewName("forward:/index.html");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/webapp/");
    }
}

这也适用于 Angular 和 React 应用程序。

于 2018-06-05T22:26:20.287 回答
13

避免使用@EnableWebMvc

默认情况下,Spring-Boot 在以下位置提供静态内容src/main/resources

  • /META-INF/资源/
  • /资源/
  • /静止的/
  • /上市/

看看这个这个

或者保留 @EnableWebMvc 并覆盖 addViewControllers

你指定了@EnableWebMvc吗?看看这个:Java Spring Boot:如何将我的应用程序根(“/”)映射到 index.html?

要么删除@EnableWebMvc,要么重新定义addViewControllers

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("forward:/index.html");
}

或者定义一个Controller来捕捉/

您可以在 github 上查看这个 spring-boot-reactjs 示例项目

它使用控制器做你想做的事:

@Controller
public class HomeController {

    @RequestMapping(value = "/")
    public String index() {
        return "index";
    }

}

index.htmlsrc/main/resources/templates

于 2016-09-05T20:12:07.217 回答
9

我在我的 Spring Boot 应用程序中使用 react 和 react-router,它就像创建一个控制器一样简单,该控制器具有/我网站的映射和子树,例如/users/** Here is my solution

@Controller
public class SinglePageAppController {
    @RequestMapping(value = {"/", "/users/**", "/campaigns/**"})
    public String index() {
        return "index";
    }
}

此控制器不会捕获 Api 调用,并且会自动处理资源。

于 2016-11-04T13:57:14.393 回答
2

通过查看这个问题找到了答案

@Bean
public EmbeddedServletContainerCustomizer notFoundCustomizer() {
    return new EmbeddedServletContainerCustomizer() {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/"));
        }
    };
}
于 2016-09-06T15:04:56.437 回答
1

另一种解决方案(更改/添加/删除myurl1, myurl2, ... 与您的路线):

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
public class SinglePageAppController {

    /**
     * If the user refreshes the page while on a React route, the request will come here.
     * We need to tell it that there isn't any special page, just keep using React, by
     * forwarding it back to the root.
     */
    @RequestMapping({"/myurl1/**", "/myurl2/**"})
    public String forward(HttpServletRequest httpServletRequest) {
        return "forward:/";
    }
}

注意:使用public String index()也可以正常工作,但前提是您使用模板。并且WebMvcConfigurerAdapter不推荐使用。

于 2020-12-16T23:19:01.403 回答
0

为了回答您涉及在所有情况下提供单页应用程序 (SPA) 的具体问题,除了/ api路线,这是我为修改 Petri 的答案所做的。

我有一个名为 polymer 的模板,其中包含我的 SPA 的 index.html。所以挑战变成了让我们将除 /api 和 /public-api 之外的所有路由转发到该视图。

在我的 WebMvcConfigurerAdapter 中,我覆盖了 addViewControllers 并使用了正则表达式:^((?!/api/|/public-api/).)*$

在您的情况下,您需要正则表达式: ^((?!/api/).)*$

public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/{spring:^((?!/api/).)*$}").setViewName("polymer");
    super.addViewControllers(registry);
}

这导致能够点击http://localhosthttp://localhost/community来提供我的 SPA 以及 SPA 进行的所有其余调用都成功路由到http://localhost/api/postshttp ://localhost/public-api/posts

于 2018-06-01T17:10:08.523 回答
0

经过多次尝试,我发现以下解决方案是最简单的解决方案。它基本上会绕过所有很难处理的 Spring 处理。

@Component
public class StaticContentFilter implements Filter {
    
    private List<String> fileExtensions = Arrays.asList("html", "js", "json", "csv", "css", "png", "svg", "eot", "ttf", "woff", "appcache", "jpg", "jpeg", "gif", "ico");
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    }
    
    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String path = request.getServletPath();
        
        boolean isApi = path.startsWith("/api");
        boolean isResourceFile = !isApi && fileExtensions.stream().anyMatch(path::contains);
        
        if (isApi) {
            chain.doFilter(request, response);
        } else if (isResourceFile) {
            resourceToResponse("static" + path, response);
        } else {
            resourceToResponse("static/index.html", response);
        }
    }
    
    private void resourceToResponse(String resourcePath, HttpServletResponse response) throws IOException {
        InputStream inputStream = Thread.currentThread()
                .getContextClassLoader()
                .getResourceAsStream(resourcePath);
        
        if (inputStream == null) {
            response.sendError(NOT_FOUND.value(), NOT_FOUND.getReasonPhrase());
            return;
        }
        
        inputStream.transferTo(response.getOutputStream());
    }
}
于 2021-07-02T13:25:52.253 回答