Spring源码中是如何使用设计模式六大原则的

2024-06-21 19:52

本文主要是介绍Spring源码中是如何使用设计模式六大原则的,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

设计模式的六大原则,通常指的是SOLID原则,它们是面向对象设计中用于提高代码可维护性、灵活性和可扩展性的五个指导原则,学习六大原则,可以让你的代码变得高级而优雅,今天的内容 V 哥结合 Spring源码中如何运用六大原则来具体讲解,希望能给你带来帮助:

  1. 单一职责原则(Single Responsibility Principle, SRP):一个类应该只有一个引起它变化的原因。
  2. 开闭原则(Open-Closed Principle, OCP):软件实体应该对扩展开放,对修改关闭。
  3. 里氏替换原则(Liskov Substitution Principle, LSP):子类对象必须能够替换掉它们的父类对象,而不影响程序的行为。
  4. 接口隔离原则(Interface Segregation Principle, ISP):客户端不应该依赖于它不使用的接口。
  5. 依赖倒置原则(Dependency Inversion Principle, DIP):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
  6. 迪米特法则(Law of Demeter):一个对象应该对其他对象有尽可能少的了解。

在Spring框架中,这些原则得到了广泛应用,下面 V 哥一一详细解释 Spring框架源码中是如何运用的。

1. 单一职责原则

在Spring框架中,单一职责原则(SRP)的应用非常广泛,它体现在框架的许多组件和类的设计中。以下是通过Spring框架的BeanPostProcessor接口的一个例子来说明如何运用单一职责原则。

单一职责原则的定义

根据单一职责原则,一个类应该只有一个引起它变化的原因,即一个类应该只负责一项职责。

Spring中的BeanPostProcessor接口

BeanPostProcessor接口是一个用于在Bean的初始化过程中进行自定义处理的钩子(hook)。它提供了两个方法:postProcessBeforeInitialization和postProcessAfterInitialization。这两个方法分别在Bean初始化之前和之后调用,允许开发者插入自定义逻辑。

示例:自定义日志记录BeanPostProcessor

假设我们想要创建一个自定义的BeanPostProcessor来记录所有Bean的初始化日志。根据单一职责原则,这个BeanPostProcessor的职责就是记录日志,不应该包含其他业务逻辑。

public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 在Bean初始化之前记录日志System.out.println("Before initializing bean: " + beanName);return bean; // 返回原始Bean以继续初始化过程}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {// 在Bean初始化之后记录日志System.out.println("After initializing bean: " + beanName);return bean; // 返回原始Bean}
}

注册BeanPostProcessor

接下来,我们需要将这个BeanPostProcessor注册到Spring的上下文中,以便它可以被调用。

@Configuration
public class AppConfig {@Beanpublic MyBeanPostProcessor myBeanPostProcessor() {return new MyBeanPostProcessor();}
}

分析

在这个例子中,MyBeanPostProcessor类遵循了单一职责原则,因为它只负责一件事情:记录日志。这个类没有涉及到其他业务逻辑,如数据访问、业务计算等。这种设计使得MyBeanPostProcessor类非常专注于它的职责,易于理解和维护。

此外,Spring框架本身也遵循了单一职责原则。例如,ApplicationContext负责整个应用上下文的生命周期管理,BeanFactory负责Bean的创建和管理,而BeanPostProcessor则专注于Bean生命周期中的扩展点。每个接口和类都有明确的职责,这有助于保持代码的清晰和模块化。

通过这种方式,Spring框架的源码实现了高度的模块化和可维护性,使得开发者可以轻松地扩展和定制框架的行为。

2. 开闭原则

开闭原则(Open-Closed Principle, OCP)是说软件实体应当对扩展开放,对修改封闭。这意味着设计时应当使软件模块易于扩展,但是不需要修改现有代码就能添加新功能。

Spring框架在多个方面运用了开闭原则,以下是一些示例:

1. 扩展点 - 接口和抽象类

Spring框架定义了许多接口和抽象类作为扩展点,允许开发者实现这些接口或继承这些抽象类来扩展框架功能,而不需要修改框架本身的代码。

示例: ApplicationContext 接口及其实现类允许开发者通过实现不同的上下文来扩展Spring的IoC容器功能。

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource {// 定义了获取Bean等方法
}

开发者可以创建自定义的ApplicationContext实现来添加特定功能。

2. 策略模式 - 多个实现类

Spring框架使用策略模式允许在运行时根据不同的条件选择不同的行为。

示例: BeanPostProcessor 接口有多个实现类,每个类实现不同的处理逻辑。

public interface BeanPostProcessor {Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

开发者可以添加自己的BeanPostProcessor实现来扩展Bean的初始化过程。

3. 装饰者模式 - 增强功能

Spring AOP使用装饰者模式来增强方法的执行,允许开发者在不修改原有方法实现的情况下添加额外功能。

示例: 使用@Aspect注解的类可以定义切面,通过这些切面可以在不修改业务逻辑代码的情况下添加日志、事务管理等功能。

@Aspect
@Component
public class LoggingAspect {@Before("execution(* com.example.service.*.*(..))")public void beforeServiceMethod(JoinPoint joinPoint) {// 记录日志}
}

4. 观察者模式 - 事件发布和监听

Spring框架使用观察者模式来实现事件的发布和监听机制,允许在不修改事件源代码的情况下添加新的事件监听器。

示例: ApplicationEventPublisher 接口允许发布事件,而ApplicationListener 接口允许监听事件。

public interface ApplicationEventPublisher {void publishEvent(ApplicationEvent event);
}public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E event);
}

开发者可以实现ApplicationListener接口来响应应用程序中的事件。

5. 依赖注入 - 灵活配置

Spring的依赖注入允许在不修改类代码的情况下,通过配置来改变类的行为。

示例: 通过XML配置或注解来注入不同实现的Bean。

<!-- XML配置示例 -->
<bean id="myService" class="com.example.service.MyServiceImpl"/>

或者使用注解:

@Service
public class MyService {// ...
}

在上述示例中,MyServiceImpl类可以被任何实现了MyService接口的类所替换,只需更改配置,而不需要修改使用MyService的代码。

分析

Spring框架通过提供清晰的接口和抽象类作为扩展点,允许开发者扩展其功能而不必修改现有代码,这正是开闭原则的体现。此外,Spring的AOP、事件机制和依赖注入等特性,都是遵循开闭原则设计的,它们提供了强大的扩展能力,同时保持了代码的封闭性,使得Spring能够适应不断变化的需求。

3. 里氏替换原则

里氏替换原则(Liskov Substitution Principle, LSP)指出子类型必须能够替换掉它们的父类型,而不影响程序的行为。在Spring框架中,里氏替换原则主要通过接口和抽象类的多态性来实现。以下是Spring框架中运用里氏替换原则的一些示例:

示例 1:使用JdbcTemplate的自定义扩展

Spring的JdbcTemplate是一个用于简化数据库操作的类。它提供了一个抽象的模板方法,可以被任何继承自它的子类所扩展。

public class MyCustomJdbcTemplate extends JdbcTemplate {@Overridepublic <T> T queryForObject(String sql, Object[] args, RowMapper<T> rowMapper) {// 自定义查询逻辑return super.queryForObject(sql, args, rowMapper);}
}

在这个示例中,MyCustomJdbcTemplate继承自JdbcTemplate并重写了queryForObject方法。根据里氏替换原则,MyCustomJdbcTemplate可以在任何使用JdbcTemplate的地方使用,而不需要修改使用它的代码。

示例 2:Spring的事件监听器

Spring的事件发布机制允许开发者发布和监听应用程序事件。ApplicationListener接口定义了事件监听器的行为。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E event);
}

开发者可以实现ApplicationListener接口来创建自定义的事件监听器:

@Component
public class MyCustomEventListener implements ApplicationListener<ApplicationEvent> {@Overridepublic void onApplicationEvent(ApplicationEvent event) {// 事件处理逻辑}
}

根据里氏替换原则,MyCustomEventListener可以替换任何实现了ApplicationListener接口的类,因为它遵循了接口定义的契约。

示例 3:Spring的拦截器

Spring的拦截器HandlerInterceptor用于在请求处理之前和之后执行自定义逻辑。

public interface HandlerInterceptor {boolean preHandle(HttpRequest request, HttpResponse response, Object handler) throws Exception;void postHandle(HttpRequest request, HttpResponse response, Object handler) throws Exception;void afterCompletion(HttpRequest request, HttpResponse response, Object handler, Exception ex) throws Exception;
}

开发者可以创建自定义的拦截器实现:

public class MyCustomInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 预处理逻辑return true;}// 实现其他方法...
}

MyCustomInterceptor遵循了HandlerInterceptor接口的契约,因此可以在任何需要拦截器的地方使用,满足里氏替换原则。

分析

在Spring框架中,里氏替换原则的运用主要体现在以下几个方面:

  • 多态性:通过接口和抽象类的多态性,Spring允许开发者实现自定义行为而不影响其他代码。
  • 扩展性:开发者可以实现或继承Spring的类和接口来扩展功能,同时保持与现有代码的兼容性。
  • 契约:接口和抽象类定义了清晰的契约,实现这些契约的子类必须保证不改变原有行为。

通过这种方式,Spring框架的设计允许高度的灵活性和可扩展性,同时保持代码的稳定性和可维护性。

4. 接口隔离原则

接口隔离原则(Interface Segregation Principle, ISP)指出客户端不应该依赖它不使用的方法。一个类对一个接口的依赖应该被限制为它真正需要的方法。在Spring框架中,接口隔离原则体现在多个方面,以下是一些示例:

示例 1:Spring的事件处理接口

在Spring框架中,事件处理相关的接口被设计得非常具体,以满足不同的使用场景。

public interface ApplicationEvent {// 事件相关的方法
}public interface ApplicationListener<E extends ApplicationEvent> {void onApplicationEvent(E event);
}public interface ContextRefreshedEvent extends ApplicationEvent {// 上下文刷新事件相关的方法
}

在这个例子中,ApplicationListener接口仅包含处理事件的方法,而不是一个包含多种类型事件处理方法的大接口。这样,实现ApplicationListener的类只需要关注它们感兴趣的事件类型,遵循接口隔离原则。

示例 2:Spring的数据访问异常层次结构

Spring为不同类型的数据访问操作提供了不同的异常类,而不是使用一个通用的异常类。

public class DataAccessException extends RuntimeException {// 数据访问异常基类
}public class DataAccessResourceFailureException extends DataAccessException {// 资源访问失败异常
}public class DataIntegrityViolationException extends DataAccessException {// 数据完整性违反异常
}

通过这种方式,调用者只需要捕获它们关心的异常类型,而不是一个大而全的异常类。

示例 3:Spring的事务管理接口

Spring提供了多个事务管理接口,每个接口关注事务管理的不同方面。

public interface PlatformTransactionManager {TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;void commit(TransactionStatus status);void rollback(TransactionStatus status);
}public interface TransactionDefinition {// 定义事务属性的方法
}public interface TransactionStatus {// 表示事务状态的方法
}

在这个例子中,PlatformTransactionManager接口提供了事务管理的方法,而TransactionDefinition和TransactionStatus接口分别定义了事务的属性和状态。这样,使用事务的代码只需要与它们真正需要的接口交互。

示例 4:Spring的Web MVC

Spring的Web MVC框架提供了多个接口来处理不同类型的请求处理逻辑。

public interface Controller {// 控制器的基础方法
}public interface LastModified {long getLastModified();
}public interface SessionStatus {void setComplete();
}public interface ModelAndView {// 处理模型和视图的方法
}

开发者可以根据需要实现Controller接口,并根据特定需求选择性实现LastModified或SessionStatus接口。

分析

Spring框架通过以下方式运用接口隔离原则:

  • 细粒度接口:Spring提供了细粒度的接口,每个接口只包含特定的方法集合,以满足特定的客户端需求。
  • 定制化实现:开发者可以根据需要实现特定的接口,而不需要实现一个包含许多不需要方法的大接口。
  • 灵活性和可维护性:接口隔离原则提高了系统的灵活性和可维护性,因为每个接口都有明确的责任,修改一个接口不会影响其他接口。

通过这种方式,Spring框架的设计允许开发者编写松耦合、高内聚的代码,使得系统更加模块化和易于扩展。

5. 依赖倒置原则

依赖倒置原则(Dependency Inversion Principle, DIP)是SOLID原则中的"D",它指出高层模块不应该依赖低层模块,两者都应该依赖于抽象(接口或抽象类)。Spring框架在多个方面运用了依赖倒置原则,以下是一些示例和分析:

示例 1:依赖注入(DI)

Spring框架的核心特性之一是依赖注入,它允许将组件的依赖关系注入到组件中,而不是让组件自己查找或创建依赖。这种方式使得组件依赖于抽象(接口或抽象类),而不是具体的实现。

public class UserService {private UserRepository userRepository;@Autowiredpublic UserService(UserRepository userRepository) {this.userRepository = userRepository;}// 使用userRepository执行业务逻辑
}

在这个例子中,UserService依赖于UserRepository接口,而不是任何具体的实现。这样,如果需要替换UserRepository的具体实现,只需要更改Spring的配置,而不需要修改UserService的代码。

示例 2:JdbcTemplate

JdbcTemplate是一个数据访问抽象,它允许开发者编写数据库操作代码而不需要依赖于特定的数据库操作实现。

public class JdbcTemplate extends DataSourceUtils {// ...public <T> List<T> query(String sql, RowMapper<T> rowMapper) {// 使用DataSource执行查询操作}
}

开发者可以使用JdbcTemplate来编写数据库操作,而不需要依赖于具体的DataSource实现。

示例 3:Spring的策略模式

Spring框架使用策略模式来允许开发者在运行时选择不同的行为。例如,BeanPostProcessor接口允许开发者实现自定义的Bean生命周期处理器。

public interface BeanPostProcessor {Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

开发者可以实现BeanPostProcessor接口来定义自己的处理逻辑,Spring容器将调用这些接口方法而不是具体的实现。

示例 4:Spring的Web MVC

在Spring的Web MVC框架中,Controller接口定义了请求处理的方法,开发者可以实现这个接口来创建自定义的控制器。

public interface Controller {ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

通过实现Controller接口,开发者可以编写处理HTTP请求的逻辑,而不需要依赖于具体的请求处理实现。

分析

Spring框架通过以下方式运用依赖倒置原则:

  • 抽象化依赖:Spring鼓励开发者对接口编程,而不是对具体实现编程。这减少了模块间的耦合性。
  • 灵活性和可扩展性:通过依赖抽象,Spring框架提高了应用程序的灵活性和可扩展性。当需要替换组件的具体实现时,不需要修改依赖于它们的代码。
  • 控制反转(IoC):Spring的IoC容器管理了组件的创建和依赖关系的注入,这体现了依赖倒置原则,因为组件的依赖不是由组件自己控制的,而是由容器控制的。

通过运用依赖倒置原则,Spring框架提供了一个灵活、可扩展的编程模型,使得开发者能够轻松地替换组件实现,而不影响其他部分的代码。

6. 迪米特法则

迪米特法则(Least Knowledge Principle, LKP),也称为最少知识原则,主张一个对象应该对其他对象有尽可能少的了解,只与直接的朋友通信。在Spring框架中,迪米特法则主要通过控制组件之间的耦合度来实现,以下是一些示例和分析:

示例 1:Spring的依赖注入(DI)

Spring的依赖注入机制遵循迪米特法则,因为它允许组件声明它们的依赖关系,但是不负责获取依赖对象的实现。Spring容器负责将依赖注入到组件中。

@Component
public class UserServiceImpl implements UserService {private UserRepository userRepository; // 声明依赖,但不直接获取实现@Autowiredpublic void setUserRepository(UserRepository userRepository) {this.userRepository = userRepository;}// 使用userRepository执行业务逻辑
}

在这个例子中,UserServiceImpl只与UserRepository有直接的交互,它不关心UserRepository的具体实现细节,这符合迪米特法则。

示例 2:Spring的AOP

Spring的AOP模块允许开发者定义切面(advice)和切入点(pointcut),在不修改业务逻辑代码的情况下增加额外的行为。

@Aspect
@Component
public class LoggingAspect {@Before("execution(* com.example.service.*.*(..))")public void logBeforeServiceMethod(JoinPoint joinPoint) {// 日志记录逻辑}
}

在这个例子中,LoggingAspect只与JoinPoint交互,它不需要了解目标对象的内部结构或其它切面的实现。

示例 3:Spring的MVC

在Spring的MVC框架中,控制器(Controller)只与HttpServletRequest和HttpServletResponse交互,不直接与用户会话或其它Web组件交互。

@Controller
public class UserController {@GetMapping("/users")public String listUsers(Model model, HttpServletRequest request) {List<User> users = userService.listUsers();model.addAttribute("users", users);return "userList";}
}

控制器只与Model和HttpServletRequest交互,不直接与用户会话或其它Web组件交互,这减少了组件之间的耦合度。

示例 4:Spring的事件发布机制

Spring的事件发布机制允许组件发布事件而不需要知道谁是监听者,同样,事件监听者也不需要知道事件的来源。

@Component
public class UserEventPublisher {@Autowiredprivate ApplicationEventPublisher eventPublisher;public void publishUserEvent(User user) {UserCreatedEvent event = new UserCreatedEvent(this, user);eventPublisher.publishEvent(event);}
}@Component
public class UserEventListener implements ApplicationListener<UserCreatedEvent> {@Overridepublic void onApplicationEvent(UserCreatedEvent event) {// 处理用户创建事件}
}

UserEventPublisher只与ApplicationEventPublisher交互,而UserEventListener只与UserCreatedEvent交互,它们之间没有直接的耦合。

分析

Spring框架通过以下方式运用迪米特法则:

  • 减少直接交互:组件之间的交互通过抽象和间接的方式进行,减少了直接的依赖关系。
  • 使用中间件:Spring使用各种中间件(如IoC容器、AOP框架、MVC框架等)来减少组件之间的直接耦合。
  • 声明式编程:Spring鼓励使用声明式编程,如使用注解或配置文件来声明依赖关系,而不是在代码中硬编码。

通过运用迪米特法则,Spring框架提高了组件的内聚性和降低了组件之间的耦合度,使得系统更加模块化和易于维护。

最后

V 哥建议,通过 Spring 源码来学习设计模式的六大原则具体应用,不仅能够掌握基本的概念,还能掌握实际应用场景中是如何使用的,正所谓知其然知其所以然,这样学习 JAVA 才带劲。如果文章内容对你有帮助,客官,点个赞再走呗,你的支持是 V 哥原创写作的最大动力,关注威哥爱编程,JAVA 之路咱们一起搀扶前行,结伴同行,才能走得更远。

这篇关于Spring源码中是如何使用设计模式六大原则的的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1082182

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程