Spring Boot迁移OCI对象存储上传报错:UnsatisfiedDependencyException
迁移OCI对象存储时的HK2依赖注入错误解决
我们正尝试从AWS迁移至OCI,基于Spring Boot框架开发。此前上传文件到AWS S3完全正常,但切换到OCI对象存储后触发了如下HK2依赖注入错误:
org.glassfish.hk2.api.MultiException: A MultiException has 1 exceptions. They are: 1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=int,parent=DaemonClientAsyncExecutorProvider,qualifiers={@javax.inject.Named("ClientAsyncThreadPoolSize")},position=0,optional=false,self=false,unqualified=null,2072183845) at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:51) at org.jvnet.hk2.internal.Utilities.justCreate(Utilities.java:1055) at org.jvnet.hk2.internal.ServiceLocatorImpl.create(ServiceLocatorImpl.java:968) at org.jvnet.hk2.internal.ServiceLocatorImpl.createAndInitialize(ServiceLocatorImpl.java:1072) at org.jvnet.hk2.internal.ServiceLocatorImpl.createAndInitialize(ServiceLocatorImpl.java:1064) at org.glassfish.jersey.inject.hk2.AbstractHk2InjectionManager.createAndInitialize(AbstractHk2InjectionManager.java:189) at org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager.createAndInitialize(ImmediateHk2InjectionManager.java:30) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) at java.base/java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1689) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) at java.base/java.util.stream.StreamSpliterators$WrappingSpliterator.forEachRemaining(StreamSpliterators.java:310) at java.base/java.util.stream.Streams$ConcatSpliterator.forEachRemaining(Streams.java:734) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) at org.glassfish.jersey.process.internal.AbstractExecutorProvidersConfigurator.registerExecutors(AbstractExecutorProvidersConfigurator.java:65) at org.glassfish.jersey.client.ClientExecutorProvidersConfigurator.init(ClientExecutorProvidersConfigurator.java:139) at org.glassfish.jersey.client.ClientConfig$State.initRuntime(ClientConfig.java:452) at org.glassfish.jersey.internal.util.collection.Values$LazyValueImpl.get(Values.java:317) at org.glassfish.jersey.client.ClientConfig.getRuntime(ClientConfig.java:814) at org.glassfish.jersey.client.ClientRequest.getConfiguration(ClientRequest.java:263) at org.glassfish.jersey.client.JerseyInvocation.validateHttpMethodAndEntity(JerseyInvocation.java:121) at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:90) at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:86) at org.glassfish.jersey.client.JerseyInvocation.<init>(JerseyInvocation.java:77) at org.glassfish.jersey.client.JerseyInvocation$AsyncInvoker.method(JerseyInvocation.java:578) at org.glassfish.jersey.client.JerseyCompletionStageRxInvoker.method(JerseyCompletionStageRxInvoker.java:31) at com.oracle.bmc.http.client.jersey.JerseyHttpRequest.execute(JerseyHttpRequest.java:239) at com.oracle.bmc.http.internal.ClientCall.callAsyncImpl(ClientCall.java:959) at com.oracle.bmc.http.internal.ClientCall.callAsyncTokenRefresh(ClientCall.java:902) at com.oracle.bmc.retrier.BmcGenericRetrier.doFunctionCall(BmcGenericRetrier.java:175) at com.oracle.bmc.retrier.BmcGenericRetrier.doFunctionCallAsync(BmcGenericRetrier.java:181) at com.oracle.bmc.retrier.BmcGenericRetrier.lambda$executeAsync$3(BmcGenericRetrier.java:119) at com.oracle.bmc.waiter.GenericWaiter.executeAsync(GenericWaiter.java:109) at com.oracle.bmc.waiter.GenericWaiter.executeAsync(GenericWaiter.java:98) at com.oracle.bmc.retrier.BmcGenericRetrier.executeAsync(BmcGenericRetrier.java:112) at com.oracle.bmc.http.internal.ClientCall.callAsyncWithRetrier(ClientCall.java:892) at com.oracle.bmc.http.internal.ClientCall.callAsync0(ClientCall.java:839) at com.oracle.bmc.http.internal.ClientCall.callSync(ClientCall.java:1032) at com.oracle.bmc.objectstorage.ObjectStorageClient.putObject(ObjectStorageClient.java:1745)
当前代码实现
OsClientConfiguration配置类
@Configuration public class OsClientConfiguration { // OCI配置文件路径 String configurationFilePath = "ociconfig"; String profile = "DEFAULT"; public ObjectStorage getObjectStorage() throws IOException{ // 加载OCI配置文件 final ConfigFileReader.ConfigFile configFile = ConfigFileReader.parse(configurationFilePath, profile); final ConfigFileAuthenticationDetailsProvider provider = new ConfigFileAuthenticationDetailsProvider(configFile); // 构建并返回客户端 return ObjectStorageClient.builder() .build(provider); } }
服务类上传逻辑
@Autowired private OsClientConfiguration configuration; @Override public void upload(String path, String fileName, Optional<Map<String, String>> optionalMetaData, InputStream inputStream) throws Exception { PutObjectRequest putObjectRequest = PutObjectRequest.builder() .namespaceName(namespaceName) .bucketName(path) .objectName(fileName) .contentType(optionalMetaData.get().get("Content-Type")) .contentLength(Long.valueOf(optionalMetaData.get().get("Content-Length"))) .putObjectBody(inputStream) .build(); try { configuration.getObjectStorage().putObject(putObjectRequest); } catch (Exception e) { e.printStackTrace(); throw e; }finally{ try { configuration.getObjectStorage().close(); inputStream.close(); } catch (Exception e) { throw new IllegalStateException("上传失败:无法关闭OCI对象存储连接", e); } } }
依赖配置
implementation 'com.oracle.oci.sdk:oci-java-sdk-addons-resteasy-client-configurator:3.1.0' implementation 'com.oracle.oci.sdk:oci-java-sdk-objectstorage:3.1.0' implementation 'com.oracle.oci.sdk:oci-java-sdk-bom:3.1.0' implementation 'com.oracle.oci.sdk:oci-java-sdk-common-httpclient-jersey:3.1.0'
解决方案
1. 修复客户端构建,显式配置HTTP客户端
错误根源是Jersey客户端的HK2容器缺少ClientAsyncThreadPoolSize参数注入,需显式构建HttpClient并配置线程池:
修改OsClientConfiguration,将客户端实例改为Spring管理的单例Bean:
@Configuration public class OsClientConfiguration { String configurationFilePath = "ociconfig"; String profile = "DEFAULT"; @Bean public ObjectStorage objectStorage() throws IOException { ConfigFileReader.ConfigFile configFile = ConfigFileReader.parse(configurationFilePath, profile); ConfigFileAuthenticationDetailsProvider provider = new ConfigFileAuthenticationDetailsProvider(configFile); // 构建带线程池配置的HttpClient HttpClient httpClient = JerseyHttpClient.builder() .connectionTimeout(Duration.ofSeconds(10)) .readTimeout(Duration.ofSeconds(30)) .asyncThreadPoolSize(10) // 显式设置异步线程池大小 .build(); return ObjectStorageClient.builder() .httpClient(httpClient) .build(provider); } }
2. 优化客户端实例管理
原代码每次上传都创建新的ObjectStorage客户端,既浪费资源又容易引发容器冲突,改为直接注入Spring管理的单例客户端:
修改服务类:
@Autowired private ObjectStorage objectStorage; // 直接注入ObjectStorage实例 @Override public void upload(String path, String fileName, Optional<Map<String, String>> optionalMetaData, InputStream inputStream) throws Exception { PutObjectRequest putObjectRequest = PutObjectRequest.builder() .namespaceName(namespaceName) .bucketName(path) .objectName(fileName) .contentType(optionalMetaData.get().get("Content-Type")) .contentLength(Long.valueOf(optionalMetaData.get().get("Content-Length"))) .putObjectBody(inputStream) .build(); try { objectStorage.putObject(putObjectRequest); } catch (Exception e) { throw new IllegalStateException("上传OCI对象存储失败", e); } finally { inputStream.close(); // 客户端由Spring管理,无需手动关闭 } }
3. 调整依赖,排除冲突
通过BOM统一管理所有OCI SDK版本,避免Jersey依赖冲突:
// 使用BOM统一管理版本 implementation platform('com.oracle.oci.sdk:oci-java-sdk-bom:3.1.0') // 仅声明需要的模块,无需指定版本 implementation 'com.oracle.oci.sdk:oci-java-sdk-objectstorage' implementation 'com.oracle.oci.sdk:oci-java-sdk-common-httpclient-jersey' // 移除可能引发冲突的resteasy配置器,除非明确需要 // implementation 'com.oracle.oci.sdk:oci-java-sdk-addons-resteasy-client-configurator:3.1.0'
内容的提问来源于stack exchange,提问作者Suneet Pathak
相关产品推荐
相关产品推荐

