Spring Boot子类依赖注入失效:调用时属性未初始化
问题根源分析
- Spring Bean管理范围限制:父类
Integration标注了@Service,但密封类无法被实例化,且你通过copy方法或手动构造创建的子类实例不属于Spring容器管理对象,Spring不会为这些对象执行依赖注入,导致@Autowired标注的smsClient、emailClient始终未初始化。 - 违反单一职责原则:将子类不需要的依赖放在父类中,比如
SlackIntegration完全不需要smsClient和emailClient,却被迫持有这些冗余依赖,增加了代码复杂度和维护成本。
重构方案:工厂模式+构造注入
核心思路是:将依赖注入到各自需要的子类中,通过Spring管理的工厂类来创建子类实例,确保依赖能正确传递,同时保持子类的状态灵活性。
步骤1:重构父类Integration
移除父类上的@Service和@Autowired字段,回归纯抽象密封类:
sealed class Integration { abstract val reference: UUID abstract val integrationType: IntegrationType abstract fun send(incomingMessage: String) abstract fun getContactPoints(): List<String> }
步骤2:修改子类,构造注入专属依赖
每个子类只注入自身需要的依赖,通过构造函数传入:
SmsIntegration
@Schema(description = "Integration of sms notifications.", allOf = [Integration::class]) class SmsIntegration( override val reference: UUID, val countryCode: String, val phoneNumbers: List<String>, private val smsClient: SmsClient // 仅注入自身需要的依赖 ) : Integration() { override val integrationType: IntegrationType = IntegrationType.SMS_INTEGRATION override fun send(incomingMessage: String) { phoneNumbers.forEach { smsClient.postSms(SmsRequest(countryCode, it, incomingMessage)) } } override fun getContactPoints(): List<String> = phoneNumbers fun copy(phoneNumbers: List<String>): SmsIntegration = SmsIntegration(reference, countryCode, phoneNumbers, smsClient) }
EmailIntegration
@Schema(description = "Integration of email notifications.", allOf = [Integration::class]) class EmailIntegration( override val reference: UUID, val emails: List<String>, val subject: String, private val emailClient: EmailClient // 仅注入自身需要的依赖 ) : Integration() { override val integrationType: IntegrationType = IntegrationType.EMAIL_INTEGRATION override fun send(incomingMessage: String) { val emailAddresses = emails.map { EmailAddress(email = it) } val email = Email( from = EmailAddress(email = "test@test.org"), to = emailAddresses, subject = subject, body = incomingMessage ) emailClient.sendEmail(email) } override fun getContactPoints(): List<String> = emails fun copy(emails: List<String>): EmailIntegration = EmailIntegration(reference, emails, subject, emailClient) }
SlackIntegration
无需修改依赖相关逻辑,保持原有实现即可:
@Schema(description = "Integration of slack notifications.", allOf = [Integration::class]) class SlackIntegration( override val reference: UUID, val channelIds: List<String> ) : Integration() { override val integrationType: IntegrationType = IntegrationType.SLACK_INTEGRATION override fun send(incomingMessage: String) { channelIds.forEach { SlackBot.sendMessage(channelId = it, message = incomingMessage) } } override fun getContactPoints(): List<String> = channelIds fun copy(channelIds: List<String>): SlackIntegration = SlackIntegration(reference, channelIds) }
步骤3:创建Spring管理的工厂类
通过工厂类统一创建子类实例,由Spring注入所需的客户端依赖:
@Service class IntegrationFactory( private val smsClient: SmsClient, private val emailClient: EmailClient ) { fun createSmsIntegration( countryCode: String, phoneNumbers: List<String>, reference: UUID = UUID.randomUUID() ): SmsIntegration { return SmsIntegration(reference, countryCode, phoneNumbers, smsClient) } fun createEmailIntegration( emails: List<String>, subject: String, reference: UUID = UUID.randomUUID() ): EmailIntegration { return EmailIntegration(reference, emails, subject, emailClient) } fun createSlackIntegration( channelIds: List<String>, reference: UUID = UUID.randomUUID() ): SlackIntegration { return SlackIntegration(reference, channelIds) } }
使用方式
在需要创建Integration子类实例的地方,注入IntegrationFactory并调用对应的创建方法:
@Service class NotificationService(private val integrationFactory: IntegrationFactory) { fun createSmsNotification(countryCode: String, phones: List<String>) { val smsIntegration = integrationFactory.createSmsIntegration(countryCode, phones) smsIntegration.send("测试短信") } }
方案优势
- 职责清晰:每个子类仅持有自身业务所需的依赖,消除冗余依赖。
- 依赖安全:通过构造注入保证依赖不可为空,避免未初始化导致的空指针异常。
- 可测试性:单元测试时可轻松传入Mock客户端,无需依赖Spring容器。
- 扩展性:新增
Integration子类时,只需在工厂类中添加对应的创建方法,无需修改父类逻辑。
内容的提问来源于stack exchange,提问作者Premer
相关产品推荐
相关产品推荐

