You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring+Java多数据源配置读取时Environment空指针问题求助

多数据源配置读取NullPointerException问题排查

问题背景

开发一个需要连接多个数据库的Java应用,多数据源配置存放在/src/main/resources/application.properties中。尝试用Spring的Environment.getProperty()读取配置到PostgresConnector类时触发NullPointerException(提示this.env为null),用@Value注解也未解决问题。

配置文件内容

datasource1.database = mydatabase
datasource1.user = efe
datasource1.password = efe

datasource2.database = security
datasource2.user = scott
datasource2.password = tiger

注:原配置中datasource1.database重复,已删除冗余行

PostgresConnector类代码

package com.sharex19.sswfe.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.HashMap;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

@Configuration
@PropertySource("classpath:application.properties")
public class PostgresConnector extends DatabaseConnector {
    
    @Autowired
    private Environment env;

    private static final Logger log = LogManager.getLogger(PostgresConnector.class);
            
    private static PostgresConnector instance=null;
    private static Connection c=null;
    private static HashMap<String,Datasource> cc;
    
    private static String database1;
    private static String user1;
    private static String password1;
    
    private static String database2;
    private static String user2;
    private static String password2;
    
    
    public PostgresConnector() {
        
        System.out.println("CREATION DES COLLECTIONS DE CONNECTIONS");
        
        String database1 = env.getProperty("datasource1.database");
        String user1 = env.getProperty("datasource1.user");
        String password1 = env.getProperty("datasource1.password");
        
        String database2 = env.getProperty("datasource2.database");
        String user2 = env.getProperty("datasource2.user");
        String password2 = env.getProperty("datasource2.password");
        
        
        System.out.println("ACCES A DATABASE1...");
        
        if ( database1 == null ) {
            System.out.println("DATABASE 1 IS NULL");       
        } else {
            System.out.println("DATABASE1:".concat(database1));
        }
        
        System.out.println("DATABASE1:".concat(user1).concat(password1));
        
        System.out.println("ACCES A DATABASE2...");
        
        System.out.println("DATABASE2:".concat(user2).concat(password2));
        
        cc = new HashMap();
        
        cc.put(database1,new Datasource(user1,password1));
        cc.put(database2,new Datasource(user2,password2));
        
    }
    
    public static Connection getInstance(String databasename){
        log.info("APPEL PostgresConnector.getInstance()");
        if (instance==null) {
            instance = new PostgresConnector();
            c=PostgresConnector.connect(databasename,
                cc.get(databasename).user,
                cc.get(databasename).password);
        }
        return  c;
    }
    
    public static Connection connect(String databasename, 
        String user, String password) {
        
        System.out.println("database:".concat(databasename));
        System.out.println("user:".concat(user));
        System.out.println("password:".concat(password));
        
          Connection c = null;
          try {
             Class.forName("org.postgresql.Driver");
             c = DriverManager
            .getConnection("jdbc:postgresql://localhost:5432/".
                concat(databasename),
                    user, password);
          } catch (Exception e) {
             e.printStackTrace();
             System.err.println(e.getClass().getName()+": "+e.getMessage());
             System.exit(0);
          }
          System.out.println("Opened database successfully");
          return c;
       }
}

报错堆栈信息

juil. 17, 2024 9:49:23 PM org.apache.catalina.core.StandardContext loadOnStartup
SEVERE: Le Servlet [SecureSpringWebFlow50] dans l'application web [/SSA19] a retourné une exception lors de son chargement
java.lang.NullPointerException: Cannot invoke "org.springframework.core.env.Environment.getProperty(String)" because "this.env" is null
    at com.sharex19.sswfe.utils.PostgresConnector.<init>(PostgresConnector.java:52)
    at com.sharex19.sswfe.utils.PostgresConnector$$EnhancerBySpringCGLIB$$b5f344d4.<init>(<generated>)
    at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:67)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:483)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:211)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1326)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:921)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583)
    at org.springframework.web.servlet.FrameworkServlet.configureAndRefreshWebApplicationContext(FrameworkServlet.java:702)
    at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:668)
    at org.springframework.web.servlet.FrameworkServlet.createWebApplicationContext(FrameworkServlet.java:716)
    at org.springframework.web.servlet.FrameworkServlet.initWebApplicationContext(FrameworkServlet.java:591)
    at org.springframework.web.servlet.FrameworkServlet.initServletBean(FrameworkServlet.java:530)
    at org.springframework.web.servlet.HttpServletBean.init(HttpServletBean.java:170)
    at javax.servlet.GenericServlet.init(GenericServlet.java:158)
    at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1164)
    at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1117)
    at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1010)
    at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:4957)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5264)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1396)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1386)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:919)
    at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:835)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1396)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1386)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:919)
    at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:265)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.StandardService.startInternal(StandardService.java:432)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:930)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.startup.Catalina.start(Catalina.java:772)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
    at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)

问题原因及解决方案

核心问题

  1. 构造函数中依赖注入未完成:Spring的@Autowired注入是在对象构造完成后才执行的,直接在构造函数中调用env.getProperty()时,env还未被注入,必然触发NullPointerException。
  2. 手动实例化绕开Spring容器:getInstance方法中用new PostgresConnector()创建实例,脱离了Spring的管理,导致依赖无法注入。
  3. 静态变量滥用:类中大量使用静态变量,但Spring的依赖注入是针对实例的,静态成员无法直接通过Spring获取注入值。

修复步骤

步骤1:用@PostConstruct替代构造函数读取配置

将配置读取逻辑移到@PostConstruct注解的方法中,该方法会在Spring完成所有依赖注入后自动执行:

@Configuration
@PropertySource("classpath:application.properties")
public class PostgresConnector extends DatabaseConnector {
    
    @Autowired
    private Environment env;

    private static final Logger log = LogManager.getLogger(PostgresConnector.class);
            
    private HashMap<String,Datasource> cc;
    
    // 空构造函数
    public PostgresConnector() {
        System.out.println("CREATION DES COLLECTIONS DE CONNECTIONS");
    }

    @PostConstruct
    public void init() {
        String database1 = env.getProperty("datasource1.database");
        String user1 = env.getProperty("datasource1.user");
        String password1 = env.getProperty("datasource1.password");
        
        String database2 = env.getProperty("datasource2.database");
        String user2 = env.getProperty("datasource2.user");
        String password2 = env.getProperty("datasource2.password");
        
        // 原打印逻辑保留
        System.out.println("ACCES A DATABASE1...");
        if ( database1 == null ) {
            System.out.println("DATABASE 1 IS NULL");       
        } else {
            System.out.println("DATABASE1:".concat(database1));
        }
        
        System.out.println("DATABASE1:".concat(user1).concat(password1));
        System.out.println("ACCES A DATABASE2...");
        System.out.println("DATABASE2:".concat(user2).concat(password2));
        
        cc = new HashMap<>();
        cc.put(database1, new Datasource(user1,password1));
        cc.put(database2, new Datasource(user2,password2));
    }

步骤2:让Spring管理单例,移除手动实例化逻辑

@Configuration注解的类默认是单例,直接通过Spring注入使用即可,修改连接获取方式为实例方法:

// 移除静态instance和getInstance方法
public Connection getConnection(String databasename){
    log.info("APPEL PostgresConnector.getConnection()");
    Datasource datasource = cc.get(databasename);
    if (datasource == null) {
        throw new IllegalArgumentException("未知数据库: " + databasename);
    }
    return connect(databasename, datasource.user, datasource.password);
}

// 将connect改为非静态方法(或保持静态,但确保参数正确传入)
private Connection connect(String databasename, String user, String password) {
    // 原连接逻辑保留,注意移除System.exit(),改用日志记录异常
    Connection c = null;
    try {
        Class.forName("org.postgresql.Driver");
        c = DriverManager.getConnection("jdbc:postgresql://localhost:5432/".concat(databasename), user, password);
    } catch (Exception e) {
        log.error("数据库连接失败", e);
        throw new RuntimeException("数据库连接失败", e);
    }
    System.out.println("Opened database successfully");
    return c;
}

步骤3:@Value注解的正确使用方式(可选)

如果偏好@Value,直接注入到实例变量,在@PostConstruct中使用:

@Value("${datasource1.database}")
private String database1;

@Value("${datasource1.user}")
private String user1;

@Value("${datasource1.password}")
private String password1;

@Value("${datasource2.database}")
private String database2;

@Value("${datasource2.user}")
private String user2;

@Value("${datasource2.password}")
private String password2;

@PostConstruct
public void init() {
    // 直接使用上述变量初始化cc即可
}

步骤4:确保Spring扫描到该类

在Spring启动类上添加@ComponentScan("com.sharex19.sswfe"),确保PostgresConnector所在包被Spring扫描到。

额外优化建议

  1. 使用Spring官方多数据源方案:推荐用@ConfigurationProperties配合DataSourceBuilder配置多数据源,避免手动管理JDBC连接,更符合Spring生态。
  2. 移除System.exit():异常中调用System.exit(0)会直接终止应用,改用日志记录并抛出业务异常,让Spring容器统一处理错误。

内容的提问来源于stack exchange,提问作者P. Gauthier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 20:02:03