Skip to content

Spring 依赖注入(Dependency Injection)深度解析 🚀

什么是依赖注入?为什么需要它?

NOTE

依赖注入(DI)是 Spring 框架的核心特性之一,它解决了传统面向对象编程中对象间紧耦合的问题。

想象一下,你在开发一个电影推荐系统。传统的做法可能是这样的:

kotlin
class MovieRecommendationService {
    // 直接在类内部创建依赖对象
    private val movieFinder = DatabaseMovieFinder() 
    fun getRecommendations(): List<Movie> {
        return movieFinder.findPopularMovies()
    }
}
kotlin
class MovieRecommendationService(
    private val movieFinder: MovieFinder
) {
    fun getRecommendations(): List<Movie> {
        return movieFinder.findPopularMovies()
    }
}

传统方式的痛点 ⚠️

  1. 测试困难:无法轻松替换 DatabaseMovieFinder 为测试用的 Mock 对象
  2. 扩展性差:如果要切换到其他数据源(如 Redis、API),需要修改源代码
  3. 违反开闭原则:对修改开放,对扩展封闭的原则被破坏

DI 的核心价值 ✨

TIP

依赖注入的本质是控制反转(IoC):不再由对象自己创建和管理依赖,而是由外部容器负责注入依赖。

依赖注入的两种主要方式

Spring 提供了两种主要的依赖注入方式,各有其适用场景。

1. 构造器注入(Constructor-based DI)

IMPORTANT

Spring 官方强烈推荐使用构造器注入,特别是对于必需的依赖。

基本用法

kotlin
@Service
class MovieRecommendationService(
    private val movieFinder: MovieFinder, 
    private val userPreferenceService: UserPreferenceService
) {
    fun getPersonalizedRecommendations(userId: String): List<Movie> {
        val preferences = userPreferenceService.getUserPreferences(userId)
        return movieFinder.findMoviesByPreferences(preferences)
    }
}

@Repository
class DatabaseMovieFinder : MovieFinder {
    override fun findMoviesByPreferences(preferences: UserPreferences): List<Movie> {
        // 数据库查询逻辑
        return emptyList()
    }
}

构造器注入的优势

为什么推荐构造器注入?

  1. 不可变性:使用 val 关键字,确保依赖不会被意外修改
  2. 完整初始化:对象创建时就拥有所有必需依赖,避免 null 问题
  3. 循环依赖检测:Spring 能在启动时检测到循环依赖问题
  4. 测试友好:可以直接通过构造器传入 Mock 对象

处理构造器参数歧义

当构造器有多个相同类型的参数时,Spring 提供了几种解决方案:

kotlin
@Service
class ReportService(
    private val startDate: LocalDate,
    private val endDate: LocalDate,
    private val reportType: String
)
xml
<bean id="reportService" class="com.example.ReportService">
    <constructor-arg index="0" value="2024-01-01"/>
    <constructor-arg index="1" value="2024-12-31"/>
    <constructor-arg index="2" value="ANNUAL"/>
</bean>
xml
<bean id="reportService" class="com.example.ReportService">
    <constructor-arg name="startDate" value="2024-01-01"/>
    <constructor-arg name="endDate" value="2024-12-31"/>
    <constructor-arg name="reportType" value="ANNUAL"/>
</bean>
kotlin
@Service
class ReportService(
    @Value("${report.start-date}") private val startDate: LocalDate,
    @Value("${report.end-date}") private val endDate: LocalDate,
    @Value("${report.type}") private val reportType: String
)

2. Setter 注入(Setter-based DI)

WARNING

Setter 注入主要用于可选依赖,对于必需依赖应优先使用构造器注入。

kotlin
@Service
class NotificationService {

    // 必需依赖 - 使用构造器注入
    private val emailService: EmailService

    // 可选依赖 - 使用 Setter 注入
    lateinit var smsService: SmsService

    constructor(emailService: EmailService) {
        this.emailService = emailService
    }

    @Autowired(required = false) 
    fun setSmsService(smsService: SmsService) {
        this.smsService = smsService
    }

    fun sendNotification(message: String, userId: String) {
        // 总是发送邮件(必需功能)
        emailService.sendEmail(message, userId)

        // 如果SMS服务可用,也发送短信(可选功能)
        if (::smsService.isInitialized) { 
            smsService.sendSms(message, userId)
        }
    }
}

Setter 注入的适用场景

Setter 注入的最佳实践

  • 可选依赖:功能增强型的依赖,没有也不影响核心功能
  • 重新配置:运行时可能需要更换的依赖(如通过 JMX 管理)
  • 循环依赖:作为解决循环依赖的备选方案(不推荐)

依赖解析过程深度解析

理解 Spring 如何解析和注入依赖,有助于我们更好地设计应用架构。

实际业务场景示例

让我们通过一个完整的电商订单处理系统来理解依赖注入的实际应用:

完整的订单处理系统示例
kotlin
// 领域接口
interface PaymentService {
    fun processPayment(orderId: String, amount: BigDecimal): PaymentResult
}

interface InventoryService {
    fun reserveItems(items: List<OrderItem>): Boolean
    fun releaseItems(items: List<OrderItem>)
}

interface NotificationService {
    fun sendOrderConfirmation(order: Order)
}

// 核心业务服务 - 使用构造器注入
@Service
class OrderProcessingService(
    private val paymentService: PaymentService,      
    private val inventoryService: InventoryService,  
    private val orderRepository: OrderRepository,    
    private val notificationService: NotificationService
) {
    @Transactional
    fun processOrder(orderRequest: OrderRequest): OrderResult {
        try {
            // 1. 创建订单
            val order = Order.from(orderRequest)
            // 2. 预留库存
            if (!inventoryService.reserveItems(order.items)) {
                return OrderResult.failed("库存不足")
            }

            // 3. 处理支付
            val paymentResult = paymentService.processPayment(
                order.id,
                order.totalAmount
            )

            if (!paymentResult.isSuccess) {
                inventoryService.releaseItems(order.items)
                return OrderResult.failed("支付失败: ${paymentResult.errorMessage}")
            }

            // 4. 保存订单
            val savedOrder = orderRepository.save(order.copy(status = OrderStatus.PAID))

            // 5. 发送确认通知
            notificationService.sendOrderConfirmation(savedOrder)

            return OrderResult.success(savedOrder)

        } catch (e: Exception) {
            // 异常处理和回滚逻辑
            throw OrderProcessingException("订单处理失败", e)
        }
    }
}

// 具体实现
@Service
class AlipayPaymentService : PaymentService {
    override fun processPayment(orderId: String, amount: BigDecimal): PaymentResult {
        // 支付宝支付逻辑
        return PaymentResult.success("支付成功")
    }
}

@Service
class DatabaseInventoryService : InventoryService {
    override fun reserveItems(items: List<OrderItem>): Boolean {
        // 数据库库存操作
        return true
    }
    override fun releaseItems(items: List<OrderItem>) {
        // 释放库存
    }
}

循环依赖问题及解决方案

CAUTION

循环依赖是设计上的问题,应该通过重构来避免,而不是依赖框架的解决方案。

kotlin
// 问题示例:循环依赖
@Service
class UserService(
    private val orderService: OrderService
) {
    fun getUserOrders(userId: String) = orderService.getOrdersByUser(userId)
}

@Service
class OrderService(
    private val userService: UserService
) {
    fun createOrder(userId: String) {
        val user = userService.getUser(userId)
        // 创建订单逻辑
    }
}

解决方案:引入中介者模式或事件驱动

kotlin
// 解决方案1:提取共同依赖
@Service
class UserService(
    private val userRepository: UserRepository
) {
    fun getUser(userId: String) = userRepository.findById(userId)
}

@Service
class OrderService(
    private val orderRepository: OrderRepository,
    private val userRepository: UserRepository
) {
    fun createOrder(userId: String) {
        val user = userRepository.findById(userId) 
        // 创建订单逻辑
    }
    fun getOrdersByUser(userId: String) = orderRepository.findByUserId(userId)
}

// 解决方案2:事件驱动
@Service
class OrderService(
    private val orderRepository: OrderRepository,
    private val applicationEventPublisher: ApplicationEventPublisher
) {
    fun createOrder(userId: String) {
        val order = Order(userId = userId)
        val savedOrder = orderRepository.save(order)
        // 发布事件而不是直接调用其他服务
        applicationEventPublisher.publishEvent(OrderCreatedEvent(savedOrder))
    }
}

最佳实践与选择指南

构造器注入 vs Setter 注入的选择

kotlin
@Service
class EmailService(
    private val emailTemplate: EmailTemplate,
    private val smtpClient: SmtpClient
) {
    // 所有依赖都是必需的,使用构造器注入
    // 对象创建后立即可用,不会有null问题
}
kotlin
@Service
class ReportService(
    private val dataSource: DataSource
) {
    // 可选的缓存服务
    private var cacheService: CacheService? = null
    @Autowired(required = false)
    fun setCacheService(cacheService: CacheService) {
        this.cacheService = cacheService
    }

    fun generateReport(): Report {
        val data = dataSource.fetchData()

        // 如果有缓存服务,使用缓存
        cacheService?.let { cache ->
            cache.put("report_data", data)
        }
        return Report(data)
    }
}

依赖注入的测试策略

kotlin
// 生产代码
@Service
class OrderService(
    private val paymentService: PaymentService,
    private val inventoryService: InventoryService
) {
    fun processOrder(order: Order): Boolean {
        return inventoryService.reserveItems(order.items) &&
               paymentService.processPayment(order.id, order.amount).isSuccess
    }
}

// 测试代码
@ExtendWith(MockitoExtension::class)
class OrderServiceTest {

    @Mock
    private lateinit var paymentService: PaymentService

    @Mock
    private lateinit var inventoryService: InventoryService

    @InjectMocks
    private lateinit var orderService: OrderService

    @Test
    fun `should process order successfully when all services succeed`() {
        // Given
        val order = Order(id = "123", items = listOf(), amount = BigDecimal("100"))
        `when`(inventoryService.reserveItems(any())).thenReturn(true)
        `when`(paymentService.processPayment(any(), any()))
            .thenReturn(PaymentResult.success("OK"))

        // When
        val result = orderService.processOrder(order)

        // Then
        assertTrue(result)
        verify(inventoryService).reserveItems(order.items)
        verify(paymentService).processPayment(order.id, order.amount)
    }
}

总结 🎉

依赖注入是 Spring 框架的核心特性,它通过控制反转的方式解决了传统面向对象编程中的紧耦合问题。

核心要点回顾

关键收获

  1. 构造器注入:适用于必需依赖,保证对象完整性和不可变性
  2. Setter 注入:适用于可选依赖,提供运行时重新配置的能力
  3. 测试友好:通过依赖注入,可以轻松进行单元测试
  4. 避免循环依赖:通过良好的设计避免循环依赖问题

实践建议

  • 优先使用构造器注入
  • 保持依赖关系简单清晰
  • 避免过多的构造器参数(超过 4-5 个考虑重构)
  • 使用接口而不是具体实现作为依赖类型
  • 合理使用 @Qualifier 注解处理同类型多实现的情况

通过掌握依赖注入,你将能够构建更加灵活、可测试、可维护的 Spring 应用程序! 🚀