下载工作台
Spring Boot Web 开发

IoC、DI 与 Spring 核心

试读上半部分 · 解锁后可读全文

第 2 章 · IoC、依赖注入与 Spring 核心

本章目标:理解 控制反转(IoC)依赖注入(DI);熟练使用 @Component@Service@Repository@Autowired 与构造器注入;编写 @Configuration 配置类@Bean;用 @EnableConfigurationProperties 绑定 application.yml;能对照 Django 的「模块导入 + settings 单例」说明 Spring 容器如何管理对象生命周期;在 shop-spring-demo 中实现可测试的商品目录服务层。

学时建议:4~5 小时(含 2 小时跟练)

前置:完成 spring-boot-web ch01java-dev(OOP、接口、异常、单元测试基础)。


2.1 场景说明:从硬编码到可注入的服务

ch01 的 HealthController 直接返回 Map。现在要引入商品目录服务 ProductCatalogService,供 ch03 的 Controller 调用。若像下面这样写:

public class ProductController {
    private ProductCatalogService service = new ProductCatalogService(); // 紧耦合
}

则难以单元测试、难以切换实现(内存 / 数据库 / Mock)。Spring 的 IoC 容器负责创建对象并注入依赖,类似 Django 中通过 settings 配置 backend、通过 AppConfig 注册组件——但 Spring 更显式、可测试性更强。

问题手动 newSpring DI
替换实现改源码@Service 实现或 @Primary
单例共享自己写单例默认 Singleton Bean
测试难以 Mock@MockBean / 构造器注入 Mock
配置外置散落常量@ConfigurationProperties
说明:本章示例域名、数据均为教学虚构,不涉及真实业务系统。

2.2 IoC 与 DI 核心概念

2.2.1 术语对照

术语含义shop-spring-demo 示例
Bean由容器管理的对象ProductCatalogService 实例
IoC 容器负责创建、装配、销毁 BeanApplicationContext
DI容器把依赖「注入」到使用者Controller 注入 Service
@Component Scan自动发现带注解的类@SpringBootApplication 扫描 com.example.shop

2.2.2 与 Django 对照(概念级)

SpringDjango 近似
@Service Beancatalog.services 模块中的类,在 views 里 import
@Autowired无内置 DI;常用函数参数或手动从 registry 取
@Configuration + @Beansettings.py 里定义 CACHESDATABASES backend
@Profile("dev")settings/dev.py 条件配置
构造器注入显式在 __init__ 传依赖(推荐风格)

Django 更偏「导入即单例」;Spring 强制通过容器管理生命周期,更利于大型 Java 项目

2.2.3 请求链路中的 Bean(预览)

HTTP 请求
    → DispatcherServlet(框架 Bean)
    → ProductController(@RestController Bean)
    → ProductCatalogService(@Service Bean)
    → (ch05)ProductRepository(@Repository Bean)

2.3 分层注解(Stereotype)

注解语义使用场景
@Component通用组件工具类、适配器
@Service业务服务层ProductCatalogService
@Repository持久层(可转异常)ch05 JPA Repository
@Controller / @RestControllerWeb 层ch03

四者本质都是 @Component 的特化,便于分层阅读。

2.3.1 商品服务接口与实现

catalog/ProductCatalogService.java

package com.example.shop.catalog;

import java.util.List;

public interface ProductCatalogService {
    List<ProductDto> listFeatured(int limit);
}

catalog/ProductDto.java(简单 DTO,后续章节再扩展):

package com.example.shop.catalog;

import java.math.BigDecimal;

public record ProductDto(Long id, String sku, String name, BigDecimal price) {}

catalog/InMemoryProductCatalogService.java

package com.example.shop.catalog;

import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;

@Service
public class InMemoryProductCatalogService implements ProductCatalogService {

    private static final List<ProductDto> SAMPLE = List.of(
            new ProductDto(1L, "SKU-001", "示例商品 A", new BigDecimal("99.00")),
            new ProductDto(2L, "SKU-002", "示例商品 B", new BigDecimal("199.50"))
    );

    @Override
    public List<ProductDto> listFeatured(int limit) {
        return SAMPLE.stream().limit(Math.max(limit, 0)).toList();
    }
}

ch05 将替换为 JPA 实现;本章聚焦 接口 + @Service 与注入方式。


2.4 依赖注入的三种方式

2.4.1 构造器注入(推荐)

package com.example.shop.web;

import com.example.shop.catalog.ProductCatalogService;
import com.example.shop.catalog.ProductDto;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class ProductPreviewController {

    private final ProductCatalogService catalogService;

    public ProductPreviewController(ProductCatalogService catalogService) {
        this.catalogService = catalogService;
    }

    @GetMapping("/api/v1/products/featured")
    public List<ProductDto> featured(@RequestParam(defaultValue = "10") int limit) {
        return catalogService.listFeatured(limit);
    }
}

优点:依赖 final、不可变;便于单元测试;Spring 官方推荐。无需@Autowired(单构造器时自动注入)。

2.4.2 字段注入(不推荐)

@RestController
public class BadExample {
    @Autowired
    private ProductCatalogService catalogService; // 难测、隐藏依赖
}

2.4.3 Setter 注入

适用于可选依赖;业务 Bean 较少用。

2.4.4 对照表

方式测试不可变团队规范
构造器默认采用
字段避免
Setter可选依赖

2.5 @Configuration 与 @Bean

当第三方类无法加 @Service 时,用配置类声明 Bean。

config/CatalogConfig.java

package com.example.shop.config;

import com.example.shop.catalog.ProductCatalogService;
import com.example.shop.catalog.InMemoryProductCatalogService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CatalogConfig {

    /**
     * 若去掉 @Service,可在此 @Bean 注册同一实现。
     * 本章保留 @Service,此配置类演示 @Bean 写法。
     */
    @Bean
    public ProductCatalogService productCatalogService() {
        return new InMemoryProductCatalogService();
    }
}

注意:若 @Service@Bean 同时注册两个 ProductCatalogService,会冲突。实际项目二选一;本章可删除 @Service 或删除 @Bean 方法做实验。

注册方式适用
@Component / @Service本项目类
@Bean 方法第三方 SDK、DataSource、RestTemplate

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。