Spring Boot+Angular单WAR部署Tomcat9 访问路由返回JSON异常
问题背景
- 技术栈基于Spring Boot 2.6.6、Angular 6.1.0开发,目标是将前后端合并打包为单个WAR文件部署
- 本地验证正常:Eclipse中以Spring Boot应用形式直接启动、单独启动Angular开发服务时,配置上下文路径后全流程运行无异常
- 异常触发场景:将构建生成的WAR包部署到Tomcat 9.0.30容器后,应用加载出现两类异常
- 访问URL
localhost:8080/Demo/#/login时,浏览器未加载预期的登录页面,直接返回Spring Data REST接口索引JSON,示例内容如下:
- 访问URL
{ "_links": { "users": { "href": "http://localhost:8080/Demo/index.html/users{?page,size,sort}", "templated": true }, "country": { "href": "http://localhost:8080/Demo/index.html/country{?page,size,sort}", "templated": true } } }
- 直接访问
localhost:8080/Demo/index.html时可以正常加载前端UI,但刷新页面后URL会自动变为localhost:8080/Demo/#/login,页面随即再次返回上述JSON内容,应用无法正常使用。
问题根因
- Angular构建时的
base href配置错误,和Tomcat部署的上下文路径不匹配,导致路径拼接时把index.html当成了路径层级,从返回JSON里的href字段也能看到这个问题 - Spring MVC的请求映射优先级设置不当,Spring Data REST的默认根路径处理器优先级高于静态资源映射,根路径请求被后端接口拦截,没有返回前端入口文件
- 前后端接口路径没有做明确前缀隔离,静态资源请求和后端接口请求出现路径冲突
- WAR包打包配置不符合外部Tomcat部署要求,静态资源存放位置或启动类配置有误
修复方案
1. 调整Angular构建配置
修改Angular项目src/index.html中的base标签,和部署上下文路径保持一致:
<!-- *注意:路径末尾必须加斜杠,否则静态资源路径拼接会出错* --> <base href="/Demo/">
也可以直接在构建命令中指定base路径,避免手动修改漏改:
ng build --prod --base-href=/Demo/
构建完成后,将Angular打包输出的dist目录下所有文件,全部复制到Spring Boot项目的src/main/resources/static/目录下,确保index.html直接位于static目录根层级。
2. 修正Spring Boot配置
首先在application.yml中正确配置上下文路径和静态资源映射规则:
server: servlet: context-path: /Demo spring: mvc: static-path-pattern: /** web: resources: static-locations: classpath:/static/
新增WebMVC配置类,将非接口路径的请求统一转发到前端index.html,避免被后端处理器拦截:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class FrontendRouteConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // 匹配所有非接口层级的路径,统一转发到前端入口 registry.addViewController("/{path:[\\w\\-]+}") .setViewName("forward:/index.html"); registry.addViewController("/{prefix:^(?!api$).*$}/**/{path:[\\w\\-]+}") .setViewName("forward:/index.html"); } }
如果项目引入了Spring Data REST,单独给数据接口配置统一前缀,和前端路径彻底隔离,避免冲突:
import org.springframework.context.annotation.Configuration; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer; import org.springframework.web.servlet.config.annotation.CorsRegistry; @Configuration public class RestRepositoryConfig implements RepositoryRestConfigurer { @Override public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) { // 所有Spring Data REST接口统一走/api前缀 config.setBasePath("/api"); } }
3. 修正WAR包部署配置
确保Spring Boot启动类继承SpringBootServletInitializer,适配外部Tomcat容器启动逻辑:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; @SpringBootApplication public class DemoApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(DemoApplication.class); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
调整pom.xml打包配置,设置打包方式为war,同时将内置Tomcat依赖设置为provided范围,避免和外部Tomcat的jar包冲突:
<packaging>war</packaging> <dependencies> <!-- 其余业务依赖保持不变 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> </dependencies>
按以上配置重新构建WAR包部署后,路径冲突问题会完全解决,前端路由、后端接口均可正常访问。
内容的提问来源于stack exchange,提问作者Chitrajan Dhiman
相关产品推荐
相关产品推荐

