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

Spring Boot子类依赖注入失效:调用时属性未初始化

问题根源分析
  1. Spring Bean管理范围限制:父类Integration标注了@Service,但密封类无法被实例化,且你通过copy方法或手动构造创建的子类实例不属于Spring容器管理对象,Spring不会为这些对象执行依赖注入,导致@Autowired标注的smsClient、emailClient始终未初始化。
  2. 违反单一职责原则:将子类不需要的依赖放在父类中,比如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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 06:10:00