Spring Boot打包JAR运行报错:resourceHandlerMapping创建失败(无ServletContext)
Spring Boot应用在IntelliJ中运行正常,但构建JAR文件运行(包括Docker化后)时出现如下错误:
projekt-backend-1 | 16:06:15.945 [main] ERROR org.springframework.boot.SpringApplication - Application run failed projekt-backend-1 | org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'resourceHandlerMapping' defined in class path resource [org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'resourceHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No ServletContext set projekt-backend-1 | at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) projekt-backend-1 | at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) projekt-backend-1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) projekt-backend-1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) projekt-backend-1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) projekt-backend-1 | at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) projekt-backend-1 | at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) projekt-backend-1 | at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) projekt-backend-1 | at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) projekt-backend-1 | at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) projekt-backend-1 | at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) projekt-backend-1 | at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) projekt-backend-1 | at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) projekt-backend-1 | at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) projekt-backend-1 | at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) projekt-backend-1 | at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) projekt-backend-1 | at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) projekt-backend-1 | at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) projekt-backend-1 | at com.hdmstuttgart.mi.backend.BackendApplication.main(BackendApplication.java:23) projekt-backend-1 | Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'resourceHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No ServletContext set projekt-backend-1 | at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) projekt-backend-1 | at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) projekt-backend-1 | ... 18 common frames omitted projekt-backend-1 | Caused by: java.lang.IllegalStateException: No ServletContext set projekt-backend-1 | at org.springframework.util.Assert.state(Assert.java:76) projekt-backend-1 | at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.resourceHandlerMapping(WebMvcConfigurationSupport.java:591) projekt-backend-1 | at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) projekt-backend-1 | at java.base/java.lang.reflect.Method.invoke(Method.java:577) projekt-backend-1 | at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) projekt-backend-1 | ... 19 common frames omitted projekt-backend-1 exited with code 1
相关配置文件如下:
ApplicationConfig
@Configuration @RequiredArgsConstructor @ComponentScan(basePackages = "com.hdmstuttgart.mi.backend") public class ApplicationConfig { private final UserRepository userRepository; @Bean public JavaMailSender javaMailSender() { JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); mailSender.setHost("smtp.gmail.com"); mailSender.setPort(587); mailSender.setUsername("${mailCredentials.username"); mailSender.setPassword("${mailCredentials.password"); Properties props = mailSender.getJavaMailProperties(); props.put("mail.transport.protocol", "smtp"); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.starttls.enable", "true"); props.put("mail.debug", "true"); return mailSender; } @Bean public MJMLClient mjmlClient() { String appId = "${mjmlSecrets.appId}"; String mailUsername = "${mailCredentials.username"); return MJMLClient.newDefaultClient() .withApplicationID(appId) .withApplicationKey(mailUsername); } @Bean public UserDetailsService userDetailsService() { return username -> userRepository.findByEmail(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); } @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService()); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public ModelMapper modelMapper() { return new ModelMapper(); } }
WebConfig
@Configuration @EnableWebMvc @WebAppConfiguration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("*") .allowedHeaders("*") .exposedHeaders("Authorization") /*.allowCredentials(true)*/ .maxAge(3600L); } }
主类
@SpringBootApplication(exclude = {WebMvcAutoConfiguration.class }) public class BackendApplication { private static final Logger log = LoggerFactory.getLogger(BackendApplication.class); public static void main(String[] args) { SpringApplication.run(BackendApplication.class, args); log.debug("Backend application started"); } }
application.yml
.env: prefix: "" mailCredentials: username: ${MAIL_USERNAME} password: ${MAIL_PASSWORD} mjmlSecrets: appId: ${MJML_APP_ID} appKey: ${MJML_APP_KEY} spring: datasource: url: jdbc:postgresql://localhost:5432/openbarber username: postgres password: admin jpa: show-sql: true # Log each SQL query hibernate: ddl-auto: update # DDL auto (schema change strategy) properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect mail: host: smtp.gmail.com port: 587 username: ${MAIL_USERNAME} password: ${MAIL_PASSWORD} properties: mail: smtp: auth: true starttls: enable: true required: true # hateoas: # use-hal-as-default-json-media-type: false
Dockerfile
FROM openjdk:latest WORKDIR /app COPY . . RUN ./mvnw dependency:go-offline -B RUN ./mvnw package -DskipTests RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) COPY target/Backend.jar . CMD ["java", "-jar", "Backend.jar"]
1. 移除主类中对WebMvcAutoConfiguration的排除
主类上的@SpringBootApplication(exclude = {WebMvcAutoConfiguration.class })是核心问题。Spring Boot的自动配置会自动初始化ServletContext,排除这个类后,Web MVC的自动配置逻辑失效,导致resourceHandlerMapping创建时找不到ServletContext。
修改后主类:
@SpringBootApplication public class BackendApplication { private static final Logger log = LoggerFactory.getLogger(BackendApplication.class); public static void main(String[] args) { SpringApplication.run(BackendApplication.class, args); log.debug("Backend application started"); } }
2. 简化WebConfig配置
@EnableWebMvc会完全接管Spring MVC的配置,关闭自动配置。如果仅需配置CORS,不需要添加@EnableWebMvc和@WebAppConfiguration,直接实现WebMvcConfigurer即可,Spring Boot的自动配置依然生效:
修改后WebConfig:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("*") .allowedHeaders("*") .exposedHeaders("Authorization") /*.allowCredentials(true)*/ .maxAge(3600L); } }
3. 修复配置文件中的语法错误
ApplicationConfig的javaMailSender和mjmlClient方法中存在配置占位符语法错误,缺少闭合的},本地运行可能因IDE补全正常,但打包成JAR后会加载失败:
mailSender.setUsername("${mailCredentials.username");→ 改为"${mailCredentials.username}"mailSender.setPassword("${mailCredentials.password");→ 改为"${mailCredentials.password}"String mailUsername = "${mailCredentials.username");→ 改为"${mailCredentials.username}"
另外,application.yml已配置Spring Mail参数,ApplicationConfig中的javaMailSender Bean可直接移除,复用Spring Boot自动配置的实例,避免重复配置。
4. 优化Docker构建流程
当前Dockerfile存在冗余步骤,且复制整个项目目录会增大镜像体积,优化后:
FROM openjdk:latest WORKDIR /app # 先复制依赖配置,利用Docker缓存 COPY pom.xml ./ COPY ./mvnw ./ COPY ./.mvn ./.mvn RUN ./mvnw dependency:go-offline -B # 复制源代码 COPY src ./src # 打包 RUN ./mvnw package -DskipTests # 复制最终JAR COPY target/Backend.jar ./Backend.jar CMD ["java", "-jar", "Backend.jar"]
同时注意:Docker内部无法通过localhost访问主机PostgreSQL,需将application.yml中的数据库地址修改为主机IP,或使用Docker Compose的服务名(若用Compose编排)。
内容的提问来源于stack exchange,提问作者Haidepzai

