Spring AmazonS3 客户端序列化错误

Spring serialization error for AmazonS3 client

我遇到了一个我无法真正理解的奇怪问题。

我有一个代码可以使用 AmazonS3 客户端将文件上传到 AWS S3 存储桶。我有一个 try-catch 块,我在其中捕获任何异常,在 catch 块中,我抛出了我定义的异常,该异常由控制器方法 @ExceptionHandler 处理并且应该 return thymeleaf 模板 errorUpload.html.

@Service
public class UploadService {
    private final AmazonS3 amazonS3;
    private final String bucketName;

    public UploadService(AmazonS3 amazonS3, @Value("${aws.bucketName}") String bucketName) {
        this.amazonS3 = amazonS3;
        this.bucketName = bucketName;
    }

    public void manualUpload(MultipartFile file) {
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentType(file.getContentType());
        metadata.setContentLength(file.getSize());

        try {
            amazonS3.putObject(new PutObjectRequest(bucketName, file.getName(), file.getInputStream(), metadata));
        } catch (Exception e) {
            throw new UploadException(e.getMessage());
        }
    }
}

异常处理程序:

@ExceptionHandler({UploadException.class})
public String uploadError() {
    return "errorPages/errorUpload";
}

当我在没有任何逻辑的情况下简单地在 manualUpload 方法中抛出异常时,它工作正常。但是,当使用 AmazonS3 对象并抛出异常时,逻辑会转到 ExceptionHandler,但会出现以下错误:

org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.Object] to type [byte[]] for value 'com.amazonaws.services.s3.AmazonS3Client@4b0ac28c'; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.amazonaws.services.s3.AmazonS3Client]
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:192) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.session.jdbc.JdbcIndexedSessionRepository.serialize(JdbcIndexedSessionRepository.java:623) ~[spring-session-jdbc-2.5.1.jar:2.5.1]
    at org.springframework.session.jdbc.JdbcIndexedSessionRepository.access0(JdbcIndexedSessionRepository.java:133) ~[spring-session-jdbc-2.5.1.jar:2.5.1]
    at org.springframework.session.jdbc.JdbcIndexedSessionRepository.setValues(JdbcIndexedSessionRepository.java:487) ~[spring-session-jdbc-2.5.1.jar:2.5.1]
    at org.springframework.jdbc.core.JdbcTemplate.lambda$batchUpdate(JdbcTemplate.java:1042) ~[spring-jdbc-5.3.9.jar:5.3.9]
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:651) ~[spring-jdbc-5.3.9.jar:5.3.9]
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:691) ~[spring-jdbc-5.3.9.jar:5.3.9]
    at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:1034) ~[spring-jdbc-5.3.9.jar:5.3.9]
    at org.springframework.session.jdbc.JdbcIndexedSessionRepository.insertSessionAttributes(JdbcIndexedSessionRepository.java:479) ~[spring-session-jdbc-2.5.1.jar:2.5.1]
    at org.springframework.session.jdbc.JdbcIndexedSessionRepository.access00(JdbcIndexedSessionRepository.java:133) ~[spring-session-jdbc-2.5.1.jar:2.5.1]
    at org.springframework.session.jdbc.JdbcIndexedSessionRepository$JdbcSession.lambda$save(JdbcIndexedSessionRepository.java:854) ~[spring-session-jdbc-2.5.1.jar:2.5.1]
    at org.springframework.transaction.support.TransactionOperations.lambda$executeWithoutResult[=13=](TransactionOperations.java:68) ~[spring-tx-5.3.9.jar:5.3.9]
    at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140) ~[spring-tx-5.3.9.jar:5.3.9]
    at org.springframework.transaction.support.TransactionOperations.executeWithoutResult(TransactionOperations.java:67) ~[spring-tx-5.3.9.jar:5.3.9]
    at org.springframework.session.jdbc.JdbcIndexedSessionRepository$JdbcSession.save(JdbcIndexedSessionRepository.java:836) ~[spring-session-jdbc-2.5.1.jar:2.5.1]
    at org.springframework.session.jdbc.JdbcIndexedSessionRepository$JdbcSession.access0(JdbcIndexedSessionRepository.java:665) ~[spring-session-jdbc-2.5.1.jar:2.5.1]
    at org.springframework.session.jdbc.JdbcIndexedSessionRepository.save(JdbcIndexedSessionRepository.java:422) ~[spring-session-jdbc-2.5.1.jar:2.5.1]
    at org.springframework.session.jdbc.JdbcIndexedSessionRepository.save(JdbcIndexedSessionRepository.java:133) ~[spring-session-jdbc-2.5.1.jar:2.5.1]
    at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.commitSession(SessionRepositoryFilter.java:226) ~[spring-session-core-2.5.1.jar:2.5.1]
    at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.access0(SessionRepositoryFilter.java:193) ~[spring-session-core-2.5.1.jar:2.5.1]
    at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:145) ~[spring-session-core-2.5.1.jar:2.5.1]
    at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82) ~[spring-session-core-2.5.1.jar:2.5.1]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.3.9.jar:5.3.9]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:190) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:163) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:711) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:461) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:385) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:313) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:398) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:257) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:352) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:177) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1723) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.50.jar:9.0.50]
    at java.base/java.lang.Thread.run(Thread.java:831) ~[na:na]
Caused by: org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.amazonaws.services.s3.AmazonS3Client]
    at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:64) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:33) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.convert.support.GenericConversionService$ConverterAdapter.convert(GenericConversionService.java:386) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) ~[spring-core-5.3.9.jar:5.3.9]
    ... 47 common frames omitted
Caused by: java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.amazonaws.services.s3.AmazonS3Client]
    at org.springframework.core.serializer.DefaultSerializer.serialize(DefaultSerializer.java:43) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.serializer.Serializer.serializeToByteArray(Serializer.java:56) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.core.serializer.support.SerializingConverter.convert(SerializingConverter.java:60) ~[spring-core-5.3.9.jar:5.3.9]
    ... 50 common frames omitted

我认为这可能与将 SPRING_SECURITY_CONTEXT 存储在 spring_session_attributes table 中有关,因此由于 AmazonS3 未序列化,因此会引发该错误。知道如何解决这个问题吗?

错误确实很奇怪。在我看来,您正在为 Java (https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-s3), so I would suggest you updating it to the new version (https://mvnrepository.com/artifact/software.amazon.awssdk/s3) 使用旧版本的 AWS SDK 并检查问题是否仍然存在。

有关更多详细信息,请查看有关如何迁移的 AWS 文档 https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/migration.html

我找到了解决办法。 任何不可序列化的对象都不能创建为 bean 并使用,因为在这种情况下,当这个特定的 bean 将存储在 SPRING_SECURITY_CONTEXT 中时,会发生序列化异常。 因此,解决方案不是简单地创建 Bean,而是在我们想要使用它时使用“new”关键字创建对象。

@Service
public class UploadService {
    private final String bucketName;
    private final S3Client s3Client;

    public UploadService(@Value("${aws.bucketName}") String bucketName,
                         @Value("${aws.accessKey}") String awsAccessKey,
                         @Value("${aws.secretKey}") String awsSecretKey) {
        this.s3Client = S3Client.builder()
                .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(awsAccessKey, awsSecretKey)))
                .region(Region.US_EAST_1)
                .build();
        this.bucketName = bucketName;
    }

    public void manualUpload(MultipartFile file) {
        try {
            s3Client.putObject(PutObjectRequest.builder().bucket(bucketName).key(file.getName()).build(),
                    RequestBody.fromBytes(file.getBytes()));
        } catch (Exception e) {
            throw new UploadException(e.getMessage());
        }
    }
}