Spring Boot静态成员注入导致的NullPointerException(NPE) 问题

问题场景

有一个工具类, 用于对支付参数进行签名, 其中使用了 @ConfigurationProperties 配置类. 签名工具类如下:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

@Slf4j
public class SignUtil {
    private static PayProperties payProperties;
    @Autowired
    public static void setPayProperties(PayProperties payProperties) {
        SignUtil.payProperties = payProperties;
    }
    public static String signParams(PayOrderRequest payOrderRequest, String apiKey) {
        
        ////////////////////////////////////////////////////////////////////
        // 这里 payProperties 的输出为 null, 访问它将会导致 NullPointerException
        ////////////////////////////////////////////////////////////////////
                
        log.info("============================: {}", payProperties);
        
        // 排序
        Map<String, String> map = new TreeMap<>();
        map.put("merchantOrderId", payOrderRequest.getMerchantOrderId());
        map.put("createdAt", payOrderRequest.getCreatedAt());
        ...
        ...
        ...
        
        List<String> params = new ArrayList<>();
        for (String key : map.keySet()) {
            log.debug("参数: {}", String.format("%s=%s", key, map.get(key)));
            params.add(map.get(key));
        }
        params.add(apiKey);
        // 拼接
        String plainTextParams = String.join("|", params);
        log.info("支付签名参数拼接: {}", plainTextParams);
        
        // 签名并返回
        return DigestUtils.md5DigestAsHex(plainTextParams.getBytes()).toUpperCase();
    }
}

这样是不行的,
PayProperties 是一个静态成员, Spring 容器在初始化过程中如果看到这是一个静态的成员, 它会直接跳过这个成员字段, 处理下一个字段.

解决办法

使用一个代理Bean类对静态成员进行初始化, 这个代理Bean可以叫做
StaticContextInitializer(静态山下文初始化器)

首先, 这个代理类需要使用 @Component 进行注解
其次, 删除上述代码中静态方法 setPayProperties 的注解 @Autowired,
然后, 创建这个代理类, 并使用PostConstructSignUtil 工具类的 PayProperties 成员进行手工注入:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class StaticContextInitializer {
    private PayProperties anyiProperties;
    @Autowired
    public void setAnyiProperties(PayProperties anyiProperties) {
        this.anyiProperties = anyiProperties;
    }
    @PostConstruct
    public void init() {
        SignUtil.setPayProperties(anyiProperties);
    }
}

可测试的示例代码

StaticContextInitializer.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class StaticContextInitializer {
    private PayProperties anyiProperties;
    @Autowired
    public void setAnyiProperties(PayProperties anyiProperties) {
        this.anyiProperties = anyiProperties;
    }
    @PostConstruct
    public void init() {
        SignUtil.setPayProperties(anyiProperties);
    }
}

SignUtil.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

@Slf4j
public class SignUtil {
    private static PayProperties payProperties;

    @Autowired
    public static void setPayProperties(PayProperties payProperties) {
        SignUtil.payProperties = payProperties;
    }
    
    public static String signParams(PayOrderRequest payOrderRequest, String apiKey) {
        
        ////////////////////////////////////////////////////////////////////
        // payProperties 字段已经通 StaticContextInitializer.init() 进行注入
        // 不会再出现 NPE 问题
        ////////////////////////////////////////////////////////////////////
                
        log.info("============================: {}", payProperties);
        
        // 排序
        Map<String, String> map = new TreeMap<>();
        map.put("merchantOrderId", payOrderRequest.getMerchantOrderId());
        map.put("createdAt", payOrderRequest.getCreatedAt());
        
        List<String> params = new ArrayList<>();
        for (String key : map.keySet()) {
            log.debug("参数: {}", String.format("%s=%s", key, map.get(key)));
            params.add(map.get(key));
        }
        params.add(apiKey);
        // 拼接
        String plainTextParams = String.join("|", params);
        log.info("支付签名参数拼接: {}", plainTextParams);
        
        // 签名并返回
        return DigestUtils.md5DigestAsHex(plainTextParams.getBytes()).toUpperCase();
    }
}
    原文作者:developerworks
    原文地址: https://segmentfault.com/a/1190000017103671
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞