NullPointerException—配置属性为null

1. 问题描述

空指针异常,获取属性配置类AliOssProperties中的endpoint属性时,为空。

配置文件中正确配置了相关的属性,并且AliOssProperties上加了@ConfigurationProperties,启动类上通过@EnableConfigurationProperties(AliOssProperties.class)启用了配置属性支持。

Error starting ApplicationContext. 
To display the conditions report re-run your application with 'debug' enabled.
Failed to instantiate [com.cyt.utils.AliOssUtil]: 
Constructor threw exception; 
nested exception is java.lang.NullPointerException: 
Cannot invoke "com.cyt.properties.AliOssProperties.getEndpoint()" 
because "this.aliOssProperties" is null

2. 问题分析

原因是在 @Autowired 注入 AliOssProperties 时,注入发生在 Spring 容器初始化的过程中,而 endpointaccessKeyId 等这些字段是在 aliOssProperties 注入之前初始化的,所以它们在被注入之前仍然是 null

@Autowired
private AliOssProperties aliOssProperties;

private String endpoint = aliOssProperties.getEndpoint();  // 这里可能会导致 NPE
private String accessKeyId = aliOssProperties.getAccessKeyId();
private String accessKeySecret = aliOssProperties.getAccessKeySecret();
private String bucketName = aliOssProperties.getBucketName();

这些字段的赋值依赖于 aliOssProperties,但是由于 aliOssProperties 还未完成注入,所以当 Spring 初始化这些字段时,aliOssProperties 仍然是 null,因此会导致 NullPointerException

3. 知识扩展

在 Spring 中,@Autowired 注解和属性的初始化顺序是由 Java 类的生命周期以及 Spring 容器的管理流程决定的。以下是注入和属性初始化的先后过程,解释为什么在使用 @Autowired 注入时,直接给属性赋值可能导致 NullPointerException

3.1  Java 类的加载与初始化顺序

Java 类的加载和初始化是 JVM 管理的一个流程,通常包括以下几个步骤:

  1. 加载类(Class Loading):JVM 会加载类的字节码到内存中。
  2. 链接类(Class Linking):JVM 会对类的静态部分进行解析和验证。
  3. 类初始化(Class Initialization)
    • 静态变量初始化:静态变量初始化是类加载的一部分,优先级最高。
    • 实例变量初始化:接下来是非静态实例变量的初始化。
    • 构造器初始化:最后执行构造方法,完成类的实例化。

3.2 Spring Bean 的初始化顺序

在 Spring 中,Bean 的生命周期由 Spring 容器管理,Bean 初始化时会经历以下几个重要步骤:

  1. Bean 实例化(Instantiation):Spring 容器首先根据配置文件或注解扫描生成类的实例,即通过无参构造函数创建对象(等同于 new 操作)。

  2. 依赖注入(Dependency Injection):此时,Spring 会为类中的 @Autowired@Value 注解的字段注入依赖项。比如,将 AliOssProperties 对象注入到 AliOssUtil 类中。

  3. 初始化回调(Initialization Callback):如果类实现了 InitializingBean 接口或使用了 @PostConstruct 注解,Spring 会在依赖注入完成后调用相关方法进行初始化。

  4. Bean 可用:完成依赖注入和初始化后,Bean 才真正可以被使用。

3.3 发生 NullPointerException 的原因

  1. Bean 实例化:Spring 首先实例化 AliOssUtil 类,也就是执行 new AliOssUtil() 操作。此时,Spring 容器还没有为 aliOssProperties 注入值。

  2. 属性初始化:在实例化过程中,类中的实例变量会被初始化。此时,Java 会尝试为 endpointaccessKeyId 等字段赋值。这些字段是基于 aliOssProperties 进行初始化的,而 aliOssProperties 还没有被注入,因此它的值是 null。调用 aliOssProperties.getEndpoint() 就会抛出 NullPointerException

  3. 依赖注入:实例化和属性初始化完成后,Spring 才会执行依赖注入操作,将 aliOssProperties 注入到 AliOssUtil 类中。这时 aliOssProperties 才不再是 null,但是已经错过了在声明时直接赋值的时机。

4. 解决方案

4.1 在方法中使用

不要在属性声明时使用 aliOssProperties,而是在方法中延迟使用它。

@Component
@Slf4j
public class AliOssUtil {

    @Autowired
    private AliOssProperties aliOssProperties;

    public String upload(byte[] bytes, String objectName) {
        // 在使用时再获取 aliOssProperties 的值,确保此时它已经注入完成
        String endpoint = aliOssProperties.getEndpoint();
        String accessKeyId = aliOssProperties.getAccessKeyId();
        String accessKeySecret = aliOssProperties.getAccessKeySecret();
        String bucketName = aliOssProperties.getBucketName();

        // 上传逻辑...
    }
}

4.2 手动配置 AliOssUtil的Bean对象

4.2.1 实现

通过 Java Config 类 使用 @Bean 注解手动创建 AliOssUtil 实例,而不是通过默认的 Spring 容器自动扫描和注入。它解决了依赖注入和属性初始化顺序的问题,因为显式地将AliOssProperties 的属性传递给 AliOssUtil。

/**
 * 配置类,用于创建AliOssUtil对象
 */
@Configuration
@Slf4j
public class OssConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);
        return new AliOssUtil(aliOssProperties.getEndpoint(),
                aliOssProperties.getAccessKeyId(),
                aliOssProperties.getAccessKeySecret(),
                aliOssProperties.getBucketName());
    }
}

@ConditionalOnMissingBean 表示当 Spring 容器中不存在 AliOssUtil 类型的 Bean 时,才会创建这个 Bean。这样可以避免重复创建 Bean 的问题,防止冲突。这对于大型项目特别有用,因为不同的模块可能会有不同的配置来源,如果某个模块已经定义了 AliOssUtil,这个配置类就不会重复创建。

4.2.2 分析

由于 Spring 的依赖注入机制,Spring 会按照依赖顺序来解析每个 Bean。当 AliOssUtil 依赖于 AliOssProperties 时,Spring 会确保在创建 AliOssUtil 实例之前,AliOssProperties 已经完成初始化。因此:

  • 当 Spring 看到 aliOssUtil 方法的参数是 AliOssProperties,它会先去查找并创建(如果尚未创建)一个 AliOssProperties 实例。
  • 之后,Spring 会将这个 AliOssProperties 实例传递给 aliOssUtil 方法。
  • 由于 AliOssProperties 已经正确初始化并注入,AliOssUtil 的构造函数可以安全地使用这些属性进行初始化。

5. 总结

  • Spring 的依赖注入是在 Bean 实例化后进行的,因此如果在 Bean 实例化时就使用未注入的依赖项(比如 @Autowired 的属性),可能会导致 NullPointerException
  • 为了解决这个问题,可以延迟对依赖项的使用(即在方法中使用),或者手动管理Bean对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cyt涛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值