使用外部文件配置SMTP时JavaMailSender构建报错问题
我实现了一个标注@Bean的MailConfig类,通过外部文件配置SMTP参数,替代项目内置的application.properties。项目启动后邮件可正常发送,但构建阶段会报错,栈信息显示创建javaMailSender Bean时抛出IllegalStateException: SMTP preferences are not set correctly异常。
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'javaMailSender' defined in class path resource [com/IOTProjectUI/config/MailConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.mail.javamail.JavaMailSender]: Factory method 'javaMailSender' threw exception; nested exception is java.lang.IllegalStateException: SMTP preferences are not set correctly. at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:486) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) 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.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1389) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:656) ... 100 more Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.mail.javamail.JavaMailSender]: Factory method 'javaMailSender' threw exception; nested exception is java.lang.IllegalStateException: SMTP preferences are not set correctly. at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ... 113 more Caused by: java.lang.IllegalStateException: SMTP preferences are not set correctly. at com.IOTProjectUI.config.MailConfig.javaMailSender(MailConfig.java:44) at com.IOTProjectUI.config.MailConfig$$EnhancerBySpringCGLIB$$8d71899e.CGLIB$javaMailSender$0(<generated>) at com.IOTProjectUI.config.MailConfig$$EnhancerBySpringCGLIB$$8d71899e$$FastClassBySpringCGLIB$$22288389.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) at com.IOTProjectUI.config.MailConfig$$EnhancerBySpringCGLIB$$8d71899e.javaMailSender(<generated>) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ... 114 more
@Configuration public class MailConfig { private static final Logger logger = LoggerFactory.getLogger(MailConfig.class); @Bean public JavaMailSender javaMailSender() { JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); CiPreferences.readPreferences(); String host = CiPreferences.properties.get("smtpHost"); String portStr = CiPreferences.properties.get("smtpPort"); String username = CiPreferences.properties.get("smtpUsername"); String password = CiPreferences.properties.get("smtpPassword"); String auth = CiPreferences.properties.get("smtpAuth"); String starttls = CiPreferences.properties.get("smtpStarTtls"); if (host == null || portStr == null || username == null || password == null || auth == null || starttls == null) { throw new IllegalStateException("SMTP preferences are not set correctly."); } int port; try { port = Integer.parseInt(portStr); } catch (NumberFormatException e) { throw new IllegalStateException("Invalid SMTP port: " + portStr, e); } mailSender.setHost(host); mailSender.setPort(port); mailSender.setUsername(username); mailSender.setPassword(password); Properties props = mailSender.getJavaMailProperties(); props.put("mail.smtp.auth", Boolean.parseBoolean(auth)); props.put("mail.smtp.starttls.enable", Boolean.parseBoolean(starttls)); props.put("mail.debug", "true"); return mailSender; } }
smtpHost=stest@test.com smtpPort=25 smtpUsername=test@test.com smtpPassword= smtpAuth=false smtpStarTtls=true smtpSender=test@test.com
构建阶段(如Maven/Gradle的compile/package流程)Spring容器初始化时,外部配置文件可能未被加载(比如构建环境下不存在该文件,或者自定义的CiPreferences加载逻辑在构建阶段无法访问外部路径),导致从CiPreferences.properties获取的参数为null,触发了代码中的非空判断,抛出异常。而项目启动时,外部配置文件存在且可被读取,所以能正常运行。
方案1:改用Spring原生配置加载机制(推荐)
替换自定义的CiPreferences加载逻辑,使用Spring的@PropertySource注解加载外部配置,并为参数设置默认值,确保构建阶段有可用的默认配置,避免null。
修改后的MailConfig:
@Configuration // 优先加载外部文件,不存在则忽略 @PropertySource(value = "file:./external-mail.properties", ignoreResourceNotFound = true) // 加载classpath下的默认配置,构建阶段必须存在 @PropertySource(value = "classpath:default-mail.properties", ignoreResourceNotFound = false) public class MailConfig { private static final Logger logger = LoggerFactory.getLogger(MailConfig.class); @Value("${smtpHost}") private String host; @Value("${smtpPort:25}") // 默认端口25 private int port; @Value("${smtpUsername}") private String username; @Value("${smtpPassword:}") // 默认空字符串 private String password; @Value("${smtpAuth:false}") // 默认关闭认证 private boolean auth; @Value("${smtpStarTtls:true}") // 默认开启STARTTLS private boolean starttls; @Bean public JavaMailSender javaMailSender() { JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); // 仅在运行时校验核心参数(构建阶段用默认值不会触发) if (host == null || username == null) { throw new IllegalStateException("SMTP preferences are not set correctly."); } mailSender.setHost(host); mailSender.setPort(port); mailSender.setUsername(username); mailSender.setPassword(password); Properties props = mailSender.getJavaMailProperties(); props.put("mail.smtp.auth", auth); props.put("mail.smtp.starttls.enable", starttls); props.put("mail.debug", "true"); return mailSender; } }
同时在src/main/resources下创建default-mail.properties作为构建阶段的默认配置:
smtpHost=default-smtp.test.com smtpUsername=default@test.com
方案2:调整参数校验时机
将参数非空校验从Bean创建阶段(构建时)移到运行时,比如在邮件发送服务中校验,而不是在Bean初始化时。这样构建阶段即使参数为null也不会报错,仅在实际发送邮件时校验。
方案3:修改自定义加载逻辑
调整CiPreferences.readPreferences()方法,确保构建阶段能读取到默认配置文件(比如在classpath下放置一份默认配置,当外部文件不存在时加载默认值)。
内容的提问来源于stack exchange,提问作者Salvatore Montagna

