I'm creating a spring boot function and running it on aws lambda, it is working with spring boot 1.5.9, but when I upgraded to 2.x, aws is throwing this exception while the application runs fine on local.
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
I have looked into it for several days, still couldn't find what the problem is, could anyone please help me with it? My code is: Configuration:
package com.ciq.backend.config;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
@Configuration
@EnableWebMvc
@Profile("lambda")
public class Configurations{
/**
* Create required HandlerMapping, to avoid several default HandlerMapping instances being created
*/
@Bean
public HandlerMapping handlerMapping() {
return new RequestMappingHandlerMapping();
}
/**
* Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created
*/
@Bean
public HandlerAdapter handlerAdapter() {
return new RequestMappingHandlerAdapter();
}
/**
* optimization - avoids creating default exception resolvers; not required as the serverless container handles
* all exceptions
*
* By default, an ExceptionHandlerExceptionResolver is created which creates many dependent object, including
* an expensive ObjectMapper instance.
*/
@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
return new HandlerExceptionResolver() {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
return null;
}
};
}
}
DynamoDBConfigProd:
package com.ciq.backend.config;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Profile("prod")
@Configuration
@EnableDynamoDBRepositories(basePackages = "com.ciq.backend.repositories")
public class DynamoDBConfigProd {
@Bean
public DynamoDBMapperConfig dynamoDBMapperConfig() {
return DynamoDBMapperConfig.DEFAULT;
}
@Bean
public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB, DynamoDBMapperConfig config) {
return new DynamoDBMapper(amazonDynamoDB, config);
}
@Bean
public AmazonDynamoDB amazonDynamoDB() {
return AmazonDynamoDBClientBuilder
.standard()
.withRegion(Regions.US_WEST_2)
.build();
}
@Bean
public DynamoDB dynamoDB() {
return new DynamoDB(amazonDynamoDB());
}
}
Application:
package com.ciq.backend;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
import com.amazonaws.services.dynamodbv2.model.ResourceInUseException;
import com.ciq.backend.entity.Are;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import javax.annotation.PostConstruct;
@SpringBootApplication
public class ApiApplication extends SpringBootServletInitializer {
private DynamoDBMapper dynamoDBMapper;
@Autowired
private AmazonDynamoDB amazonDynamoDB;
@PostConstruct
public void init() {
createLanguageTable();
}
public void createLanguageTable() {
try {
dynamoDBMapper = new DynamoDBMapper((amazonDynamoDB));
CreateTableRequest tableRequest = dynamoDBMapper.generateCreateTableRequest(Are.class);
tableRequest.setProvisionedThroughput(new ProvisionedThroughput(1L, 1L));
amazonDynamoDB.createTable(tableRequest);
} catch(ResourceInUseException e){
}
}
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}
LambdaHandler:
package com.ciq.backend;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
public class LambdaHandler implements RequestStreamHandler {
private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
static {
try {
handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(ApiApplication.class);
handler.activateSpringProfiles("lambda");
} catch (ContainerInitializationException e) {
// Re-throw the exception to force another cold start
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);
// just in case it wasn't closed by the mapper
outputStream.close();
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ciq.backend</groupId>
<artifactId>cameraiq-spring-lambda</artifactId>
<version>1.0.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<aws-java-sdk-dynamodb.version>1.11.408</aws-java-sdk-dynamodb.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws.serverless</groupId>
<artifactId>aws-serverless-java-container-springboot2</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-dynamodb</artifactId>
<version>${aws-java-sdk-dynamodb.version}</version>
</dependency>
<dependency>
<groupId>com.github.spring-data-dynamodb</groupId>
<artifactId>spring-data-dynamodb</artifactId>
<version>5.0.3</version>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>boot</id>
<activation>
<property>
<name>boot</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>org.apache.tomcat.embed:*</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Comment From: mbhave
Unfortunately it is hard to tell what might be going on from a few code snippets. Your @Configuration
class has @EnableWebMvc
on it. Adding this annotation switches off Spring Boot's WebMvcAutoConfiguration
which is not recommended for most cases. Are you sure that's what you intended to do? Another thing to note is that Spring Boot 2.0.x
has reached end of life. Please upgrade to Spring Boot 2.2.x.
If none of those two things solve your issue, we would need a small sample that we can run to reproduce the issue in the form of a zip file or a github repository. Since the application runs fine locally, the issue might not be in Spring Boot.
Comment From: xiuyangsun
Hi @mbhave, I have resolved the issue, I used AWS serverless spring boot 2 prototypes to rebuild the project, it is working now. This is the Github repo I'm referencing https://github.com/awslabs/aws-serverless-java-container
The @EnableWebMvc
is required by AWS lambda function to run spring boot rest API, but I move the annotation to the Controller rather than using the configuration file. And now it is working for Sping boot from version 2.0.X to 2.2.4. Thanks!
Comment From: wilkinsona
I'm glad to hear that you've got it working, @xiuyangsun. Thanks for letting us know.
By moving @EnableWebMvc
to your Controller
, I suspect you've hidden it such that Spring Boot's auto-configuration of Spring MVC is now taking effect. As @mbhave explained above, not using @EnableWebMvc
is what we recommend. If you really want to use @EnableWebMvc
, it should go on a @Configuration
class as noted in its javadoc.