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)
问题原因及解决方案
核心问题
- 构造函数中依赖注入未完成:Spring的
@Autowired注入是在对象构造完成后才执行的,直接在构造函数中调用env.getProperty()时,env还未被注入,必然触发NullPointerException。 - 手动实例化绕开Spring容器:
getInstance方法中用new PostgresConnector()创建实例,脱离了Spring的管理,导致依赖无法注入。 - 静态变量滥用:类中大量使用静态变量,但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扫描到。
额外优化建议
- 使用Spring官方多数据源方案:推荐用
@ConfigurationProperties配合DataSourceBuilder配置多数据源,避免手动管理JDBC连接,更符合Spring生态。 - 移除System.exit():异常中调用
System.exit(0)会直接终止应用,改用日志记录并抛出业务异常,让Spring容器统一处理错误。
内容的提问来源于stack exchange,提问作者P. Gauthier
相关产品推荐
相关产品推荐

