Java使用饿汉式如何使用同类中从yml文件获取的配置?

二手代码程序员
  • 22

题目描述

Java使用饿汉式如何使用同类中从yml文件获取的配置?

题目来源及自己的思路

准备测试文件上传功能,使用的是MinIo,想要编写一个工具类,来生成MinIoClient,然后获取到yml配置文件中MinIo相关的配置

相关代码

工具类

@Component
@Data
@Slf4j
public class MinioTool {

    public static String URL;
    public static String KEY;
    public static String SECRET;

    @Value(value = "${minio.url}")
    public void setUrl(String url) {
        URL = url;
    }

    @Value(value = "${minio.key}")
    public void setkey(String key) {
        KEY = key;
    }

    @Value(value = "${minio.secret}")
    public void setBucketName(String secret) {
        SECRET = secret;
    }



    private static MinioClient minioClient = new MinioClient.Builder().endpoint(URL).credentials(KEY, SECRET).build();

    public static MinioClient getInstance() {
        return minioClient;
    }



    public boolean bucketExists(String bucketName) {

        boolean found = false;
        try {
            found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("系统异常");
        }
        return found;
    }


}

接口调用

@RestController
@RequestMapping("/image")
public class UploadController {


    MinioClient minioClient = MinioTool.getInstance();


    @PostMapping
    public void uploads(@RequestParam("file") MultipartFile... multipartFiles) {

        InputStream inputStream;
        try {
            for (MultipartFile multipartFile : multipartFiles) {
                inputStream = multipartFile.getInputStream();

                ObjectWriteResponse test = minioClient.putObject(PutObjectArgs.builder()
                        .bucket("test").object(multipartFile.getOriginalFilename())
                        .contentType(multipartFile.getContentType())
                        .stream(multipartFile.getInputStream(), -1, 10485760).build());
                inputStream.close();
                System.out.println(test);
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("文件上传失败");
        }
    }
}

配置文件

spring:
  application:
    name: imagebed
  servlet:
    multipart:
      max-file-size: 2MB
      max-request-size: 10MB

minio:
  key: key
  secret: secret
  url: url
  bucket_name: imagebed

你期待的结果是什么?实际看到的错误信息又是什么?

我期待的结果是程序能正常启动,实际看到的错误信息如下:

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-05-19 10:38:40.522 ERROR 3816 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'uploadController' defined in file [D:\imagebed\target\classes\com\demo\imagebed\controller\UploadController.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.demo.imagebed.controller.UploadController]: Constructor threw exception; nested exception is java.lang.ExceptionInInitializerError
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1318) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1213) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:897) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879) ~[spring-context-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551) ~[spring-context-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143) ~[spring-boot-2.3.7.RELEASE.jar:2.3.7.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) ~[spring-boot-2.3.7.RELEASE.jar:2.3.7.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750) ~[spring-boot-2.3.7.RELEASE.jar:2.3.7.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:405) ~[spring-boot-2.3.7.RELEASE.jar:2.3.7.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.3.7.RELEASE.jar:2.3.7.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237) ~[spring-boot-2.3.7.RELEASE.jar:2.3.7.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.3.7.RELEASE.jar:2.3.7.RELEASE]
    at com.demo.imagebed.ImagebedApplication.main(ImagebedApplication.java:10) ~[classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.demo.imagebed.controller.UploadController]: Constructor threw exception; nested exception is java.lang.ExceptionInInitializerError
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:217) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1310) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    ... 18 common frames omitted
Caused by: java.lang.ExceptionInInitializerError: null
    at com.demo.imagebed.controller.UploadController.<init>(UploadController.java:21) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:na]
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490) ~[na:na]
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:204) ~[spring-beans-5.2.12.RELEASE.jar:5.2.12.RELEASE]
    ... 20 common frames omitted
Caused by: java.lang.IllegalArgumentException: endpoint must not be null.
    at io.minio.MinioClient$Builder.validateNotNull(MinioClient.java:2741) ~[minio-8.2.1.jar:8.2.1]
    at io.minio.MinioClient$Builder.validateNotEmptyString(MinioClient.java:2746) ~[minio-8.2.1.jar:8.2.1]
    at io.minio.MinioClient$Builder.getBaseUrl(MinioClient.java:2790) ~[minio-8.2.1.jar:8.2.1]
    at io.minio.MinioClient$Builder.endpoint(MinioClient.java:2803) ~[minio-8.2.1.jar:8.2.1]
    at com.demo.imagebed.utils.MinioTool.<clinit>(MinioTool.java:40) ~[classes/:na]
    ... 26 common frames omitted

与目标 VM 断开连接, 地址为: ''127.0.0.1:13553',传输: '套接字''

进程已结束,退出代码为 1
回复
阅读 949
3 个回答

原因: 执行方法 MinioClient::Builder::endpointURL变量为null

private static MinioClient minioClient = new MinioClient.Builder().endpoint(URL).credentials(KEY, SECRET).build();

根据 https://www.cnblogs.com/zemli... 所言:

static 语句的执行时机在第一次加载类信息(如调用类的静态方法,访问静态成员,或者调用构造函数)的时候, 所以static 语句和 static 成员变量的初始化会先于其他语句执行.

因此在Spring尝试实例化MinioTool时, 会先执行以上静态语句, 而此时 URL, KEY, SECRET这些变量皆为null, 又因为MinioClient::Builder::endpoint方法有空值校验,所以抛出异常

Caused by: java.lang.IllegalArgumentException: endpoint must not be null.
    at io.minio.MinioClient$Builder.validateNotNull(MinioClient.java:2741) ~[minio-8.2.1.jar:8.2.1]
    at io.minio.MinioClient$Builder.validateNotEmptyString(MinioClient.java:2746) ~[minio-8.2.1.jar:8.2.1]
    at io.minio.MinioClient$Builder.getBaseUrl(MinioClient.java:2790) ~[minio-8.2.1.jar:8.2.1]
    at io.minio.MinioClient$Builder.endpoint(MinioClient.java:2803) ~[minio-8.2.1.jar:8.2.1]
    at com.demo.imagebed.utils.MinioTool.<clinit>(MinioTool.java:40) ~[classes/:na]
    ... 26 common frames omitted

一般排错, Caused by 语句是最重要的, 需要多关注.

至于修改也很简单.
将静态调用更换为实例调用, 并取消 @Value 注解, 改为使用构造器初始化.
不使用 @Value 的原因是 @Value 注入晚于Bean实例化, 因此会依然抛如上异常,
不过不用担心, application.yml/application.properties读取是早于Bean初始化的,
参数可以通过Environment实例获取到.

@Component
@Data
@Slf4j
public class MinioTool {
    
    public String URL;
    public String KEY;
    public String SECRET;
    private MinioClient minioClient;
    
    
    public MinioTool(Environment env){
        URL = env.getProperty("minio.url");
        KEY = env.getProperty("minio.key");
        SECRET = env.getProperty("minio.secret");
    }
    
    public MinioClient getInstance() {
        if (minioClient==null){
            minioClient = new MinioClient.Builder().endpoint(URL).credentials(KEY, SECRET).build();
        }
        return minioClient;
    }
    
    
    public boolean bucketExists(String bucketName) {
        
        boolean found = false;
        try {
            found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            log.error("系统异常");
        }
        return found;
    }
    
}
@RestController
@RequestMapping("/image")
public class UploadController {

    private final MinioClient minioClient;
    
    public UploadController(MinioTool minioTool) {
        minioClient = minioTool.getInstance();
    }
    
    @PostMapping
    public void uploads(@RequestParam("file") MultipartFile... multipartFiles) {
        
        InputStream inputStream;
        try {
            for (MultipartFile multipartFile : multipartFiles) {
                inputStream = multipartFile.getInputStream();
                
                ObjectWriteResponse test = minioClient.putObject(PutObjectArgs.builder()
                        .bucket("test").object(multipartFile.getOriginalFilename())
                        .contentType(multipartFile.getContentType())
                        .stream(multipartFile.getInputStream(), -1, 10485760).build());
                inputStream.close();
                System.out.println(test);
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("文件上传失败");
        }
    }
}

需要注意的是Spring的核心在于控制反转, 普遍情况下, 工具类使用静态字段和方法会比较方便, 但若是用到了框架相关, 如依赖注入, 属性注入等, 此时便需要和整体框架保持一致, 以方便操作.

检查下你的endpoint呢,你看你的嵌套异常的root cause提示:endpoint must not be null.

问题分析

问题出在这行代码

private static MinioClient minioClient = new MinioClient.Builder().endpoint(URL).credentials(KEY, SECRET).build();

你这样写,是拿不到Spring注入的配置的,你也可以打断点验证

原理解释

@Value这个注解,确实是会根据application.yml文件中的配置给MinioClient注入值的,但是你使用的时候,并没有使用到Spring的动态代理对象。

解决方法

去掉

    private static MinioClient minioClient = new MinioClient.Builder().endpoint(URL).credentials(KEY, SECRET).build();

    public static MinioClient getInstance() {
        return minioClient;
    }

UploadController中使用@Autowired或者@Resource直接通过Spring注入MinioClient即可

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
你知道吗?

宣传栏