文章目录

1. CBSE(Component-based Software Engineering)的简介

这一部分其实继续接着软件复用。CBSE 也是软件复用的一种。

CBSE 是一种软件工程实践,它主要基于软件组件来开发软件。
我们将重点关注 CBSE 的三个核心原则:组件、接口和组合(Components, Interfaces, and Composition)。
除了这些核心原则,要真正理解 CBSE,还需要进一步了解组件、组件模型、中间件,以及它们之间是如何相互关联的。

1.1 CBSE(基于组件的软件工程)的优点和挑战

优点:

  1. 可复用性可以减少开发时间和成本。
  2. 模块化可以提高可维护性和可扩展性。
  3. 独立组件更容易测试和调试。

挑战:

  1. 依赖管理和版本冲突。
  2. 组件之间通信会带来额外开销。
  3. 设计既可复用、又足够具体能实际使用的组件很困难。

1.2 Components(组件)

在 CBSE 中,组件是一种模块化的、可复用的、可以独立部署的软件单元,它封装了特定的功能和数据。
比如在电商系统里:

组件 功能
Login Component 登录功能
Payment Component 支付功能
Order Component 订单功能
Search Component 搜索功能

每个组件负责一部分功能,不是整个系统都写在一起。

组件通过清晰定义好的接口,向系统的其他部分提供服务,同时隐藏自己的内部实现细节。
这种模块化可以提高软件的可复用性和可维护性。

一个好的软件组件,通常要具备下面几个特点。

  1. Self-contained(自包含 / 独立完整):
    每个组件都封装了自己的逻辑和数据。
  2. Reusable(可复用):
    组件被设计成可以在不同系统或不同场景中重复使用。
  3. Replaceable(可替换):
    只要另一个组件提供相同的接口,就可以替换原来的组件。
  4. Interface-driven(接口驱动):
    组件之间只通过定义好的接口进行交互,从而实现低耦合和高内聚。

1.2.1 Spring Boot 里的 component(组件)

在 Spring Boot 中,组件通常是一些被注解标记的类,比如:@Component、@Service、@Repository、@Controller。

注解 中文含义 常见用途
@Component 通用组件 普通功能类
@Service 服务组件 业务逻辑层
@Repository 数据访问组件 数据库操作
@Controller 控制器组件 接收网页/API 请求

例如:

@Service
public class UserService {
    public User getUserById(Long id) {
        return new User(id, "John Doe");
    }
}

这里的 UserService 就是一个 服务组件。
这些注解会告诉 Spring 的 IoC 容器 来管理这个类。
IoC 容器就像 Spring Boot 里的“对象管理器”。
正常情况下,我们写代码可能要自己创建对象:

UserService userService = new UserService();

但是在 Spring Boot 里,Spring 会自动帮你创建和管理对象。

所以Spring 会负责实例化、依赖注入、生命周期。
例如 UserController 需要用到 UserService,你不用手动 new,Spring 可以自动注入:

@Controller
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }
}

这里的 UserService 就是被 Spring 自动传进来的,这叫 dependency injection(依赖注入)。

UserService 是一个自包含的组件,它提供一个具体服务:获取用户数据。
应用程序的其他部分可以使用 UserService,但不需要知道它是如何获取数据的。

1.3 Interfaces(接口)

CBSE 中的接口定义了组件必须遵守的规则。
接口说明一个组件提供什么服务,有时候也说明它需要什么服务,但不会暴露组件内部是怎么实现的。
这种抽象可以实现低耦合,因为只要新组件遵守相同接口,就可以替换旧组件。

1.3.1 Spring Boot 里的 interfaces(接口)

在 Spring 中,接口经常用来定义服务的规则或合同,具体的实现类会在程序运行时被注入进来。

UserRepository 接口就像一个合同,允许不同的实现方式互相替换使用。

public interface UserRepository {
    User findById(Long id);
}

然后下面这个类实现了它:

@Repository
public class JpaUserRepository implements UserRepository {
    @Override
    public User findById(Long id) {
        return new User(id, "Jane Doe");
    }
}

JpaUserRepository 是 UserRepository 的一种具体实现方式。

这体现了 CBSE 强调“通过清晰定义的接口来实现组件交互”的思想。

1.4 Composition(组合)

在 CBSE 中,组合就是通过接口把多个组件连接起来,组装成一个完整的软件系统。
这个过程利用组件之间的依赖关系,创建一个功能完整的整体,并确保组件能够顺畅地协同工作。

1.3.1 Spring Boot 里的 composition(组合)

Spring Boot 使用依赖注入(DI,dependency injection) 来组合组件。
也就是说,一个组件如果需要使用另一个组件,不需要自己手动创建,而是由 Spring 自动把需要的组件传进来。

UserService 通过构造函数注入的方式,和 UserRepository 组合在一起。

@Service
public class UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUserById(Long id) {
        return userRepository.findById(id);
    }
}

UserService 这个组件需要使用 UserRepository 来查找用户数据。
所以 Spring 会自动把 UserRepository 传给 UserService。

UserService 负责业务逻辑,比如“根据用户 id 获取用户信息”。
但是它自己不直接查数据库,而是调用:userRepository.findById(id)
所以 UserService 和 UserRepository 被组合在一起,形成一个更完整的功能。

Spring 的 IoC 容器会管理这种组合,并在程序运行时根据接口把组件连接起来。

1.5 Component Models(组件模型)

组件模型是一个框架或一套标准,用来规定组件如何被定义、实现和组合。
它提供:

  1. Conventions(约定 / 规范):创建组件的规则,比如如何定义接口。
  2. Interaction Mechanisms(交互机制):组件模型会规定组件之间如何通信。
  3. Lifecycle Management(生命周期管理):组件模型会管理组件从创建、使用到销毁的过程。

例子:

  • Enterprise JavaBeans(EJB)
    这是 Java 企业级开发中的一种组件模型。
    它规定了企业级 Java 组件应该怎么写、怎么部署、怎么被容器管理。
  • Microsoft’s COM
    这是微软提出的一种组件模型。
    它允许不同软件组件之间互相调用,甚至可以跨语言使用。
    比如以前 Windows 上很多软件组件、Office 插件等,都和 COM 技术有关。
  • Spring
    正如前文所说,@Component、@Service、@Repository、@Controller 定义组件,然后由 Spring IoC 容器负责管理组件的创建、依赖注入和生命周期。
    所以 Spring 也可以看成一种支持组件化开发的框架。

组件模型可以保证系统中的一致性和互操作性。
如果没有组件模型,每个人写组件的方式可能都不一样,这样系统会很乱,组件之间也不好连接。

1.5.1 Spring 里的 component models(组件模型)

就像刚刚才提到的,Spring 用普通 Java 类,再加上一些注解,把这些类变成可被 Spring 管理的组件。

Spring 组件模型依赖普通 Java 对象,也就是 POJO(Plain Old Java Object),然后通过注解增强它们。
例如:

public class UserService {
}

这个本来只是一个普通类。
但是加上注解以后:

@Service
public class UserService {
}

Spring 就会识别它,并把它当作一个组件来管理。

@Autowired 用来指定依赖关系。
一个组件需要用到另一个组件时,可以用 @Autowired 让 Spring 自动注入。
例如:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
}

UserService 需要使用 UserRepository,Spring 会自动把 UserRepository 对象传进来。
这就是前面讲过的依赖注入(Dependency Injection,DI)。

Spring Boot 的自动配置会根据项目中引入的依赖,自动帮你配置相关组件。

1.5.2 组件与组件模型的对比

组件模型为组件定义了“游戏规则”。
组件就是遵守这些规则的具体实例,这样它们才能在系统中被有效管理和组合。

1.6 Middleware(中间件)

中间件是位于操作系统和应用程序之间的软件,它提供一些服务,让组件之间的交互和系统集成变得更简单。
它处理:

  1. Communication(通信)
    中间件可以让组件之间互相通信,即使它们不在同一台机器上。
    例如:RPC:远程过程调用,像调用本地方法一样调用远程服务;
    Messaging:消息通信,比如消息队列。
  2. Data Management(数据管理)
    中间件可以抽象数据库访问或缓存操作。
  3. Infrastructure Services(基础设施服务)
    中间件还可以提供一些基础设施服务,比如安全、事务管理和负载均衡。

在 CBSE 中,中间件像“胶水”一样,把组件粘合在一起,帮助它们组合、运行和协作。
Spring 中的例子:

  • Spring Data 可以作为中间件,在组件和数据库之间提供统一的数据访问层。
  • Spring Cloud 为分布式 Spring Boot 应用提供中间件功能,比如服务发现、负载均衡、熔断等。
  • 例如,一个 Spring Boot 应用可以使用 Spring Cloud,把一台服务器上的 UserService 组件和另一台服务器上的 UserRepository 组件连接起来,同时隐藏网络通信的复杂性。

1.7 Components(组件)、Component Models(组件模型)、Middleware(中间件)三者如何一起工作

组件模型通常会依赖某种特定的中间件环境。
以 Spring 为例,Spring 的组件模型和它的中间件紧密结合,比如 Spring Cloud。这样组件就可以很顺畅地使用分布式系统功能。

组件、组件模型和中间件一起形成了一个分层系统:

  1. 组件提供具体的定制化功能。
  2. 组件模型标准化组件的创建和组合。
  3. 中间件支持组件的运行和交互。

2. Spring Boot 中的组件:基础知识

Spring Boot 是 Spring Framework 的扩展,它通过减少配置复杂度、提供合理的默认设置,让开发者更容易快速开发可以投入生产使用的应用程序。
简单来说,Spring Boot 是更方便、更自动化的 Spring。
主要特点包括:

  1. Auto-configuration(自动配置):
    Spring Boot 会根据项目中已有的依赖,自动配置相关组件。
  2. Embedded Servers(嵌入式服务器):
    Spring Boot 内置支持 Tomcat、Jetty 等 Web 服务器,所以应用程序不需要额外安装外部服务器也可以运行。
  3. Opinionated Defaults(有主见的默认配置 / 推荐默认配置):
    Spring Boot 为常见任务提供了预先配置好的默认设置,开发者需要时也可以覆盖这些设置。

2.1 用于 CBSE 的 Spring Boot

Spring Boot 的架构本身就是基于组件的,所以它非常适合实现 CBSE 的原则。

  1. 组件由 Spring 的 IoC 容器管理,从而保证模块化和可复用性。
  2. 依赖注入可以促进组件之间的低耦合。
  3. 自动配置和 starter 依赖可以简化组件的集成和组合,使它们形成一个完整协调的应用程序。

2.2 Spring Boot 中的组件

在 Spring 中,只要一个类被 Spring IoC 容器管理,它就可以称为一个组件。这些类通常会用注解(Annotations)标记,告诉 Spring 它们的角色,并让 Spring 自动发现它们。
我们前面提到过这些注解,现在区分一下它们:

  1. @Component:普通的 Spring 组件。被 Spring 创建和管理的对象。
  2. @Service:封装业务逻辑。比如用户登录、订单处理、支付判断等业务功能,通常写在 Service 里面。
  3. @Repository:数据访问组件。它通常负责和数据库打交道。
  4. @Controller / @RestController:处理 HTTP 请求的组件。也就是说,它负责接收用户从网页或接口发来的请求。

Component Registration(组件注册):
Spring Boot 使用 component scanning,也就是组件扫描,自动发现并注册组件。默认情况下,Spring 会扫描主程序类所在的包,以及它下面的所有子包。
因此我们不需要手动一个个告诉 Spring:请管理 UserService、UserRepository、UserController。
Spring Boot 会自动扫描带有注解的类,然后把它们注册到 IoC 容器里。

下面给出实力:

import org.springframework.stereotype.Service;

@Service
public class GreetingService {
    public String greet() {
        return "Hello, World!";
    }
}

这里 @Service 注解把 GreetingService 标记成了一个组件。
Spring Boot 在组件扫描时会发现它,并把它注册成 IoC 容器中的一个 bean。

2.3 IoC Container(控制反转容器)

IoC 容器是 Spring 的组件管理系统,它体现了 IoC 原则。
在 IoC 中,框架负责创建和管理对象,而不是由应用程序代码手动完成。
IoC Container 的作用如下:

  1. 创建组件对象,这些被 Spring 管理的对象叫做 beans。
  2. 通过注入依赖和设置属性来配置组件。
  3. 管理组件从创建到销毁的整个生命周期。
  4. 把 bean 存放在 application context 中,application context 就是程序运行时的环境。

2.4 Dependency Injection(DI,依赖注入)

依赖注入是一种设计模式。组件需要的依赖对象,不是由组件自己创建,而是由框架提供。
这可以提高模块化、可测试性和可维护性。
依赖注入的类型:

  1. Constructor Injection(构造函数注入):构造函数注入,推荐用于必须依赖的对象。
  2. Setter Injection(Setter 方法注入):Setter 注入,适合可选依赖,或者运行时可能变化的依赖。
  3. Field Injection(字段注入):字段注入,直接把依赖注入到属性上,但不太推荐,因为不利于测试。

2.4.1 Constructor Injection(构造函数注入)

@Service
public class OrderService {

    private final PaymentService paymentService;

    @Autowired
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

构造函数注入就是通过类的构造函数,把一个组件需要的依赖对象传进去。这里 OrderService 依赖 PaymentService,Spring 会自动把 PaymentService 注入到 OrderService 中,从而避免手动创建对象,并降低组件之间的耦合。

2.4.2 Setter Injection(Setter 方法注入)

@Service
public class NotificationService {

    private EmailService emailService;

    @Autowired
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
}

Setter Injection 是通过 setter 方法把依赖对象注入到组件中。这里 Spring 会自动调用 setEmailService(),把 EmailService 注入到 NotificationService 里,使通知服务可以使用邮件服务。

2.4.3 Field Injection(字段注入)

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;
}

Field Injection 是直接在字段上使用 @Autowired,让 Spring 自动把依赖对象注入进去。它写法简单,但不如构造函数注入清晰和易测试,所以一般不推荐作为首选。

2.4.4 Dependency Injection(DI,依赖注入)的优点

  1. Decoupling(解耦 / 降低耦合):组件不需要关心它依赖的对象是怎么被创建出来的。
  2. Testability(可测试性):在测试时,可以用假的依赖对象来替代真实依赖。
  3. Flexibility(灵活性):依赖对象可以被替换或重新配置,而不需要修改组件本身的代码。

2.5 Component Discovery and Customization(组件发现与自定义)

Spring Boot 启动时会自动扫描代码,发现 @Service,然后把 UserService 注册到 IoC 容器中。
默认情况下,Spring 会扫描主启动类所在的包,以及它下面的所有子包。
如果不想让 Spring 只扫描默认包,可以用@ComponentScan:

@SpringBootApplication
@ComponentScan(basePackages = {"com.example.services", "com.example.repositories"})
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Spring Boot 启动时,不是随便扫描所有地方,而是指定扫描这两个包:

com.example.services
com.example.repositories

也就是说,Spring 会去这两个包里面找:

@Component
@Service
@Repository
@Controller

这些被注解标记的类,然后注册成 bean。

可以在配置类中使用 @Bean 显式地定义 bean。
这种方式适合第三方类,或者当需要更多控制时使用。
如下所示。

@Configuration
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}

2.6 构建一个简单的 REST API

我们现在以一个简单的 REST API为例,REST API 指的是一种让前端、客户端或其他系统通过网络和后端交换数据的接口方式。

public class Product {
    private Long id;
    private String name;
    private double price;

    public Product(Long id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Long getId() { 
        return id; 
    }

    public void setId(Long id) { 
        this.id = id; 
    }

    public String getName() { 
        return name; 
    }

    public void setName(String name) { 
        this.name = name; 
    }

    public double getPrice() { 
        return price; 
    }

    public void setPrice(double price) { 
        this.price = price; 
    }
}

这里首先定义一个 Product 类,用来表示商品数据。它包含商品编号、名称和价格,并通过构造函数、getter 和 setter 方法来创建、读取和修改商品信息。

@Service
public class ProductService {

    public List<Product> getAllProducts() {
        // Hardcoded for simplicity
        return Arrays.asList(
            new Product(1L, "Laptop", 999.99),
            new Product(2L, "Smartphone", 499.99)
        );
    }
}

然后定义一个 ProductService 服务组件,它通过 getAllProducts() 方法返回一个商品列表。这里的数据是直接写死的,目的是为了简单演示 REST API 中 Service 层如何向 Controller 提供数据。

@RestController
@RequestMapping("/api/products")
public class ProductController {

    private final ProductService productService;

    @Autowired
    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping
    public List<Product> getProducts() {
        return productService.getAllProducts();
    }
}

接着是控制器,它通过 /api/products 接收 GET 请求,然后调用 ProductService 获取商品列表,并把结果作为 JSON 数据返回给用户。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }
}

最后是启动类。@SpringBootApplication 告诉 Spring Boot 从这里开始自动配置和扫描组件,SpringApplication.run() 则真正启动应用程序,使前面写好的 REST API 可以运行并被访问。

3. 组件接口、组件组合和生命周期(Component Interfaces, Composition, and Lifecycle)

3.1 接口

接口是 CBSE 的核心基础之一,因为接口规定了组件必须遵守的合同。
这个合同会说明一个组件提供什么服务,以及它需要什么依赖。
通过接口来定义组件之间的交互,而不是直接依赖具体实现类,接口可以带来以下好处。

  1. Polymorphism(多态):
    同一个接口可以有不同实现,而且这些实现可以很自然地替换。
  2. Loose Coupling(低耦合):
    组件依赖抽象接口,而不是依赖具体实现类,从而减少对具体类的强依赖。
  3. Testability(可测试性):
    接口可以让单元测试时更容易模拟依赖对象。

3.1.1 示例

我们现在用一个示例来展示刚刚提到的这些优点。

public interface PaymentProcessor {
    void processPayment(double amount);
}
@Service
public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}
@Service
public class PayPalProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing PayPal payment of $" + amount);
    }
}

这里 PaymentProcessor 接口定义了支付处理的统一规则,CreditCardProcessor 和 PayPalProcessor 是两个不同的具体实现。因为它们实现了同一个接口,所以可以互相替换使用,从而实现低耦合、多态和组件可替换性。

  1. Flexibility(灵活性):可以通过注入不同的实现类,在不同支付方式之间切换。
  2. Extensibility(可扩展性):可以添加新的支付方式,比如 CryptoProcessor,而不需要修改已有代码。
  3. Testability(可测试性):在测试时,可以模拟 PaymentProcessor 接口,从而隔离被依赖的组件。

3.2 Composition Techniques(组件组合技术)

在 CBSE 中,组合指的是通过接口把多个组件连接起来,形成一个有功能的完整系统。
Spring Boot 主要通过依赖注入(Dependency Injection,DI)来实现组件组合。依赖注入就是由框架自动提供某个组件需要的依赖对象。
Spring 支持多种依赖注入方式,前文已经进行了详细的叙述,每种方式适合不同场景。

优先使用构造函数注入,尤其是必须依赖的对象。
少用 Setter 注入,只在依赖是可选的,或者运行时可能需要重新配置时使用。
为了提高可测试性和可维护性,优先选择构造函数注入或 Setter 注入。
字段注入虽然简单,但依赖关系不够明显;测试时不方便手动传入 mock 对象;类的依赖隐藏在字段里,不如构造函数清楚。

3.3 Component Lifecycle Management(组件生命周期管理)

Spring Boot 的 IoC 容器会管理组件 bean 的整个生命周期,从创建到销毁。
理解组件生命周期,可以让开发者在合适的阶段执行初始化任务和清理任务。

3.3.1 Spring Boot 中组件 bean 生命周期的阶段

一个 Spring 管理的组件,不是一下子就直接用,而是会经历:

  1. Creation(创建阶段):
    IoC 容器会创建 bean 对象。
    Spring 会根据选择的依赖注入方式,把依赖对象注入进去。
  2. Initialization(初始化阶段):
    依赖注入完成后,Spring 会执行初始化逻辑。
    @PostConstruct 可以标记一个方法,让它在 bean 完全创建并注入依赖后自动执行。
  3. Usage(使用阶段):
    bean 已经完成初始化,可以被其他组件或服务使用。
  4. Destruction(销毁阶段):
    当应用程序关闭之前,Spring 会执行销毁逻辑。
    @PreDestroy 可以标记一个方法,让它在 bean 被销毁之前自动执行。

下面的例子展示了生命周期。

@Service
public class CacheService {

    private Map<String, Object> cache = new HashMap<>();

    @PostConstruct
    public void init() {
        System.out.println("CacheService initialized. Loading cache...");
        // Simulate loading initial cache data
        cache.put("defaultKey", "defaultValue");
    }

    public void put(String key, Object value) {
        cache.put(key, value);
    }

    public Object get(String key) {
        return cache.get(key);
    }

    @PreDestroy
    public void cleanup() {
        System.out.println("CacheService shutting down. Clearing cache...");
        cache.clear();
    }
}

CacheService 是一个 Spring 管理的缓存组件。Spring 创建它后会自动执行 @PostConstruct 标记的 init() 方法来初始化缓存;程序关闭前会自动执行 @PreDestroy 标记的 cleanup() 方法来清理缓存。

3.3.2 为什么组件生命周期管理很重要

Spring 不只是创建组件,还要在合适的时候让组件 初始化资源、释放资源,保证系统稳定运行。

  1. Resource Initialization(资源初始化):
    在组件开始使用前,先准备好需要的资源。
  2. Resource Cleanup(资源清理):
    组件不用时,要释放资源,防止资源泄漏。
  3. Controlled Behavior(受控行为 / 稳定控制):
    确保组件能够平稳地启动和平稳地停止,从而保持应用程序稳定。

3.4 组件设计的最佳实践、

下面六条指导怎么更好地实践组件设计。

  1. 为抽象定义接口。
    组件之间最好依赖接口,而不是直接依赖具体实现类。
  2. 优先使用构造函数注入。
    优先使用构造函数注入。
  3. 单一职责原则。
    一个组件最好只负责一类功能,不要什么都做。
  4. 利用生命周期钩子。
    比如前面的 CacheService:
    启动时加载缓存
    关闭时清空缓存
  5. 避免循环依赖。
    不要让两个组件互相依赖。
  6. 给接口写文档。
    比如使用 JavaDoc,说明接口的方法是干什么的、参数是什么意思、返回值是什么。

3.4.1 示例

OrderService 订单服务依赖两个组件:
PaymentProcessor:支付处理器
NotificationService:通知服务
也就是说,当用户下单时,系统需要做两件事:
处理付款
发送通知
所以 OrderService 不能单独完成所有功能,它需要和其他组件组合起来。

public interface PaymentProcessor {
    void processPayment(double amount);
}

定义一个支付处理接口 PaymentProcessor。

@Service
public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}
@Service
public class PayPalProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing PayPal payment of $" + amount);
    }
}

CreditCardProcessor 和 PayPalProcessor。它们都实现了 PaymentProcessor 接口,因此可以互相替换使用,体现了接口抽象、低耦合和组件可替换的思想。

@Service
public class NotificationService {
    public void sendNotification(String message) {
        System.out.println("Sending notification: " + message);
    }
}

NotificationService 是一个通知服务组件,用来发送订单相关通知。它会被 Spring 管理,并且可以被 OrderService 注入和调用,从而和支付组件一起完成“下单 → 支付 → 通知”的业务流程。

@Service
public class OrderService {

    private final PaymentProcessor paymentProcessor;
    private final NotificationService notificationService;

    @Autowired
    public OrderService(PaymentProcessor paymentProcessor,
                        NotificationService notificationService) {
        this.paymentProcessor = paymentProcessor;
        this.notificationService = notificationService;
    }

    public void placeOrder(double amount, String customerEmail) {
        paymentProcessor.processPayment(amount);
        notificationService.sendNotification(
            "Order placed successfully for " + customerEmail
        );
    }
}

OrderService 通过构造函数注入,把 PaymentProcessor 支付组件和 NotificationService 通知组件组合起来。当调用 placeOrder() 时,系统会先处理支付,再发送下单成功通知。这体现了接口抽象、依赖注入和组件组合。

@Service
@Primary
public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}

当一个接口有多个实现类时,可以用 @Primary 指定默认优先使用的实现类。这里 CreditCardProcessor 被标记为 @Primary,所以 Spring 会默认把它注入到需要 PaymentProcessor 的组件中。

@Service
public class OrderService {
    private final PaymentProcessor paymentProcessor;
    private final NotificationService notificationService;
    @Autowired
    public OrderService(
        @Qualifier("creditCardProcessor") PaymentProcessor paymentProcessor,
        NotificationService notificationService
    ) {
        this.paymentProcessor = paymentProcessor;
        this.notificationService = notificationService;
    }
}

当 PaymentProcessor 有多个实现类时,可以用 @Qualifier(“creditCardProcessor”) 明确告诉 Spring 注入信用卡支付实现,避免 Spring 不知道该选择哪个实现类。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐