CPT304 SoftwareEngineeringII 软件工程 2 Pt.10 基于组件的软件工程 (Component-based Software Engineering)(上)
文章目录
- 1. CBSE(Component-based Software Engineering)的简介
- 2. Spring Boot 中的组件:基础知识
- 3. 组件接口、组件组合和生命周期(Component Interfaces, Composition, and Lifecycle)
1. CBSE(Component-based Software Engineering)的简介
这一部分其实继续接着软件复用。CBSE 也是软件复用的一种。
CBSE 是一种软件工程实践,它主要基于软件组件来开发软件。
我们将重点关注 CBSE 的三个核心原则:组件、接口和组合(Components, Interfaces, and Composition)。
除了这些核心原则,要真正理解 CBSE,还需要进一步了解组件、组件模型、中间件,以及它们之间是如何相互关联的。
1.1 CBSE(基于组件的软件工程)的优点和挑战
优点:
- 可复用性可以减少开发时间和成本。
- 模块化可以提高可维护性和可扩展性。
- 独立组件更容易测试和调试。
挑战:
- 依赖管理和版本冲突。
- 组件之间通信会带来额外开销。
- 设计既可复用、又足够具体能实际使用的组件很困难。
1.2 Components(组件)
在 CBSE 中,组件是一种模块化的、可复用的、可以独立部署的软件单元,它封装了特定的功能和数据。
比如在电商系统里:
| 组件 | 功能 |
|---|---|
| Login Component | 登录功能 |
| Payment Component | 支付功能 |
| Order Component | 订单功能 |
| Search Component | 搜索功能 |
每个组件负责一部分功能,不是整个系统都写在一起。
组件通过清晰定义好的接口,向系统的其他部分提供服务,同时隐藏自己的内部实现细节。
这种模块化可以提高软件的可复用性和可维护性。
一个好的软件组件,通常要具备下面几个特点。
- Self-contained(自包含 / 独立完整):
每个组件都封装了自己的逻辑和数据。 - Reusable(可复用):
组件被设计成可以在不同系统或不同场景中重复使用。 - Replaceable(可替换):
只要另一个组件提供相同的接口,就可以替换原来的组件。 - 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(组件模型)
组件模型是一个框架或一套标准,用来规定组件如何被定义、实现和组合。
它提供:
- Conventions(约定 / 规范):创建组件的规则,比如如何定义接口。
- Interaction Mechanisms(交互机制):组件模型会规定组件之间如何通信。
- 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(中间件)
中间件是位于操作系统和应用程序之间的软件,它提供一些服务,让组件之间的交互和系统集成变得更简单。
它处理:
- Communication(通信)
中间件可以让组件之间互相通信,即使它们不在同一台机器上。
例如:RPC:远程过程调用,像调用本地方法一样调用远程服务;
Messaging:消息通信,比如消息队列。 - Data Management(数据管理)
中间件可以抽象数据库访问或缓存操作。 - 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。这样组件就可以很顺畅地使用分布式系统功能。
组件、组件模型和中间件一起形成了一个分层系统:
- 组件提供具体的定制化功能。
- 组件模型标准化组件的创建和组合。
- 中间件支持组件的运行和交互。
2. Spring Boot 中的组件:基础知识
Spring Boot 是 Spring Framework 的扩展,它通过减少配置复杂度、提供合理的默认设置,让开发者更容易快速开发可以投入生产使用的应用程序。
简单来说,Spring Boot 是更方便、更自动化的 Spring。
主要特点包括:
- Auto-configuration(自动配置):
Spring Boot 会根据项目中已有的依赖,自动配置相关组件。 - Embedded Servers(嵌入式服务器):
Spring Boot 内置支持 Tomcat、Jetty 等 Web 服务器,所以应用程序不需要额外安装外部服务器也可以运行。 - Opinionated Defaults(有主见的默认配置 / 推荐默认配置):
Spring Boot 为常见任务提供了预先配置好的默认设置,开发者需要时也可以覆盖这些设置。
2.1 用于 CBSE 的 Spring Boot
Spring Boot 的架构本身就是基于组件的,所以它非常适合实现 CBSE 的原则。
- 组件由 Spring 的 IoC 容器管理,从而保证模块化和可复用性。
- 依赖注入可以促进组件之间的低耦合。
- 自动配置和 starter 依赖可以简化组件的集成和组合,使它们形成一个完整协调的应用程序。
2.2 Spring Boot 中的组件
在 Spring 中,只要一个类被 Spring IoC 容器管理,它就可以称为一个组件。这些类通常会用注解(Annotations)标记,告诉 Spring 它们的角色,并让 Spring 自动发现它们。
我们前面提到过这些注解,现在区分一下它们:
- @Component:普通的 Spring 组件。被 Spring 创建和管理的对象。
- @Service:封装业务逻辑。比如用户登录、订单处理、支付判断等业务功能,通常写在 Service 里面。
- @Repository:数据访问组件。它通常负责和数据库打交道。
- @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 的作用如下:
- 创建组件对象,这些被 Spring 管理的对象叫做 beans。
- 通过注入依赖和设置属性来配置组件。
- 管理组件从创建到销毁的整个生命周期。
- 把 bean 存放在 application context 中,application context 就是程序运行时的环境。
2.4 Dependency Injection(DI,依赖注入)
依赖注入是一种设计模式。组件需要的依赖对象,不是由组件自己创建,而是由框架提供。
这可以提高模块化、可测试性和可维护性。
依赖注入的类型:
- Constructor Injection(构造函数注入):构造函数注入,推荐用于必须依赖的对象。
- Setter Injection(Setter 方法注入):Setter 注入,适合可选依赖,或者运行时可能变化的依赖。
- 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,依赖注入)的优点
- Decoupling(解耦 / 降低耦合):组件不需要关心它依赖的对象是怎么被创建出来的。
- Testability(可测试性):在测试时,可以用假的依赖对象来替代真实依赖。
- 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 的核心基础之一,因为接口规定了组件必须遵守的合同。
这个合同会说明一个组件提供什么服务,以及它需要什么依赖。
通过接口来定义组件之间的交互,而不是直接依赖具体实现类,接口可以带来以下好处。
- Polymorphism(多态):
同一个接口可以有不同实现,而且这些实现可以很自然地替换。 - Loose Coupling(低耦合):
组件依赖抽象接口,而不是依赖具体实现类,从而减少对具体类的强依赖。 - 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 是两个不同的具体实现。因为它们实现了同一个接口,所以可以互相替换使用,从而实现低耦合、多态和组件可替换性。
- Flexibility(灵活性):可以通过注入不同的实现类,在不同支付方式之间切换。
- Extensibility(可扩展性):可以添加新的支付方式,比如 CryptoProcessor,而不需要修改已有代码。
- 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 管理的组件,不是一下子就直接用,而是会经历:
- Creation(创建阶段):
IoC 容器会创建 bean 对象。
Spring 会根据选择的依赖注入方式,把依赖对象注入进去。 - Initialization(初始化阶段):
依赖注入完成后,Spring 会执行初始化逻辑。
@PostConstruct 可以标记一个方法,让它在 bean 完全创建并注入依赖后自动执行。 - Usage(使用阶段):
bean 已经完成初始化,可以被其他组件或服务使用。 - 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 不只是创建组件,还要在合适的时候让组件 初始化资源、释放资源,保证系统稳定运行。
- Resource Initialization(资源初始化):
在组件开始使用前,先准备好需要的资源。 - Resource Cleanup(资源清理):
组件不用时,要释放资源,防止资源泄漏。 - Controlled Behavior(受控行为 / 稳定控制):
确保组件能够平稳地启动和平稳地停止,从而保持应用程序稳定。
3.4 组件设计的最佳实践、
下面六条指导怎么更好地实践组件设计。
- 为抽象定义接口。
组件之间最好依赖接口,而不是直接依赖具体实现类。 - 优先使用构造函数注入。
优先使用构造函数注入。 - 单一职责原则。
一个组件最好只负责一类功能,不要什么都做。 - 利用生命周期钩子。
比如前面的 CacheService:
启动时加载缓存
关闭时清空缓存 - 避免循环依赖。
不要让两个组件互相依赖。 - 给接口写文档。
比如使用 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 不知道该选择哪个实现类。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)