SpringBoot部署API Gateway+Lambda报ClassNotFoundException排查
问题现象
将Spring Boot应用对接API Gateway部署到AWS Lambda时抛出如下类找不到错误:
Class not found: com.myapp.app.LambdaHandler: java.lang.ClassNotFoundException java.lang.ClassNotFoundException: com.myapp.app.LambdaHandler. Current classpath: file:/var/task/
打包生成的Jar包内包含BOOT-INF、META-INF、org三个目录,业务类实际存放路径为BOOT-INF/classes/com/myapp/app/LambdaHandler。
部署方式为通过serverless.yml配置,执行serverless deploy命令完成部署。
相关代码与配置
StreamLambdaHandler.java
public class StreamLambdaHandler implements RequestStreamHandler { private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler; static { try { handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class); } catch (Exception e) { // 初始化失败时重抛异常触发冷重启 e.printStackTrace(); throw new RuntimeException("Could not initialize Spring Boot Application", e); } } @Override public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { handler.proxyStream(inputStream, outputStream, context); } }
Application.java
@SpringBootApplication @Configuration @EnableAutoConfiguration public class Application extends SpringBootServletInitializer { /** * 注册所需HandlerMapping,避免生成多个默认HandlerMapping实例 */ @Bean public HandlerMapping handlerMapping() { return new RequestMappingHandlerMapping(); } /** * 注册所需HandlerAdapter,避免生成多个默认HandlerAdapter实例 */ @Bean public HandlerAdapter handlerAdapter() { return new RequestMappingHandlerAdapter(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
业务控制器代码
@RestController @EnableWebMvc @Profile("lambda") @RequestMapping("/app/v2") public class ClientInformation { @Value("${clientDetails.userName}") private String userName; @Value("${clientDetails.userId}") private int userId; @Value("${clientDetails.userAddress}") private String userAddress; @GetMapping("/") public Map<String, Object> getDetails() { Map<String, Object> rtn = new HashMap<>(); rtn.put("message", "App is working fine !!!"); return rtn; } @GetMapping("/information") public Map<String, Object> getInformation() { Map<String, Object> rtn = new HashMap<>(); rtn.put("name", userName); rtn.put("id", userId); return rtn; } @GetMapping("/ping") public Map<String, Object> getD() { Map<String, Object> rtn = new HashMap<>(); rtn.put("name", "Ping is happening !!!"); rtn.put("status", "200"); return rtn; } }
serverless.yml配置
service: myapplication-api provider: name: aws runtime: java11 memorySize: 512 timeout: 15 profile: default stage: production package: artifact: target/myapp-api-0.0.1-SNAPSHOT-exec.jar functions: catchAllAny: handler: index.catchAllAny events: - httpApi: '*' catchAllMethod: handler: handler.catchAllMethod events: - httpApi: method: '*' path: /any/method
pom.xml配置
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath /> <!-- 从仓库拉取父依赖 --> </parent> <groupId>com.myapp.app</groupId> <artifactId>myapp-api</artifactId> <version>0.0.1-SNAPSHOT</version> <name>myapp-api</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- Spring boot Lambda Serverless 依赖 --> <dependency> <groupId>com.amazonaws.serverless</groupId> <artifactId>aws-serverless-java-container-spring</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-events</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>com.amazonaws.serverless</groupId> <artifactId>aws-serverless-java-container-springboot2</artifactId> <version>1.5.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>net.authorize</groupId> <artifactId>anet-java-sdk</artifactId> <version>2.0.6</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-bean-validators</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> </dependency> <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>5.2</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.7</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.15</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-s3</artifactId> <version>1.12.226</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.15</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <classifier>exec</classifier> <executable>false</executable> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
问题根因
一共存在4个核心配置错误,直接导致类找不到异常:
- Jar包结构不符合Lambda加载要求:当前用Spring Boot默认打包插件生成的是可执行FatJar,所有业务类都放在
BOOT-INF/classes目录下,Lambda运行时的默认类加载器只会扫描Jar包根目录下的类,不会读取BOOT-INF下的内容,自然找不到Handler类。 - serverless.yml中Handler路径配置完全错误:配置里写的
index.catchAllAny、handler.catchAllMethod和实际的Handler类全限定名不匹配,实际编写的Handler类是com.myapp.app.StreamLambdaHandler,根本不存在index、handler包下的对应类。 - Java版本不匹配:provider配置的runtime是
java11,但pom.xml中指定的编译版本是Java 1.8,版本不一致会引发类加载兼容性问题。 - 打包插件配置错误:虽然引入了maven-shade-plugin依赖,但没有在build的plugins节点中配置shade插件的执行规则,实际打包时根本没有生成Lambda可用的扁平化Jar包。另外还同时引入了
aws-serverless-java-container-spring和aws-serverless-java-container-springboot2两个依赖,存在版本冲突。
修复步骤
- 修正pom.xml配置
- 删掉重复冲突的
aws-serverless-java-container-spring依赖,只保留和Spring Boot 2.x匹配的aws-serverless-java-container-springboot2依赖。 - 将java.version改成11,和Lambda runtime版本对齐。
- 移除maven-shade-plugin的dependency声明,把shade插件配置到build->plugins节点下,配置生成扁平化Jar的规则,替换原有的Spring Boot可执行Jar打包逻辑,保证所有类直接放在Jar包根目录,去掉BOOT-INF分层结构。
调整后的build插件配置参考:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.0.0</version> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin> </plugins> </build> - 删掉重复冲突的
- 修正serverless.yml配置
- 把package节点下的artifact路径改成shade插件生成的无classifier后缀的Jar包:
target/myapp-api-0.0.1-SNAPSHOT.jar - 将functions下的handler配置改成实际的Handler类全限定名+方法名,因为StreamLambdaHandler实现了RequestStreamHandler,入口方法是handleRequest,所以配置为
com.myapp.app.StreamLambdaHandler::handleRequest,删掉不存在的两个错误handler配置,只保留一个入口函数即可。
修正后的functions配置参考:
functions: springBootApp: handler: com.myapp.app.StreamLambdaHandler::handleRequest events: - httpApi: '*' - 把package节点下的artifact路径改成shade插件生成的无classifier后缀的Jar包:
- 重新打包部署
执行mvn clean package重新打包,解压生成的Jar包确认所有com包直接在Jar根目录、不存在BOOT-INF分层后,再执行serverless deploy部署即可。
内容的提问来源于stack exchange,提问作者Atique Ahmed
相关产品推荐
相关产品推荐

