Spring Framework 6 中的设计模式

news2025/1/12 13:35:22

文章目录

  • 1、简介
  • 2、单例模式(Singleton Pattern)
    • 2.1 单例 Beans(Singleton Beans)
    • 2.2 自动注入单例(Autowired Singletons)
  • 3、工厂方法模式(Factory Method Pattern)
    • 3.1 应用上下文(Application Context)
    • 3.2 外部配置(External Configuration)
  • 4、代理模式(Proxy Pattern)
    • 4.1 事务
    • 4.2 CGLib 代理(CGLib Proxies)
  • 5、模版方法模式(Template Method Pattern)
    • 5.1 模版和回调(Templates & Callbacks)
    • 5.2 JDBC 模版(JdbcTemplates)
  • 6、结论

更多 Java / AI / 大数据 好文章

在实际开发工作中,我们每天都在自己的工作中依赖了别人的代码。包括了你正在用的编程语言、你正在构建的框架,或者一些很前沿的开源产品。

它们都做得很好,用起来真的很爽,但你自己有没有想过自己也要去实现它?哈哈,可能大概率是没想过的,是吧?

如果你没有尝试过自己去实现类似的功能,或者是深挖过这些优秀三方框架的话,对技术人来说,其实是隐含相当大一个风险的。

如果运气很不好,当你在生产中碰到了分崩离析的事情,出现了生产事故,又不得不去调试您不熟悉的第三方库的实现时,对你来说至少可以说是相当棘手的,毫无头绪的,搞不好真的就是想原地提离职……

推荐一个 Lightrun,它是一种新型的调试器。

它是专门针对现实生活中的生产环境。使用 Lightrun,您可以向下钻取到正在运行的应用程序,包括第三方依赖项,以及实时日志、快照和指标。

这不是重点,重点是我们借助 Spring 来聊聊这个如此受欢迎的框架的设计模式,以此打开你研究三方框架的道路。

1、简介

设计模式是软件开发的重要组成部分。这些解决方案不仅可以解决反复出现的问题,还可以通过识别常见模式来帮助开发人员了解框架的设计。

接下来呢,我们将介绍 Spring 框架中使用的四种最常见的设计模式:

  1. Singleton pattern 单例模式
  2. Factory Method pattern 工厂方法模式
  3. Proxy pattern 代理模式
  4. Template pattern 模板模式

我们还将研究 Spring 如何使用这些模式来减轻开发人员的负担并帮助用户快速执行繁琐的任务。

2、单例模式(Singleton Pattern)

单一实例模式是一种机制,可确保每个应用程序仅存在对象的一个实例。在管理共享资源或提供横切服务(如日志记录)时,此模式非常有用。

2.1 单例 Beans(Singleton Beans)

通常,单例对于应用程序是全局唯一的,但在 Spring 中,此约束是宽松的。相反,Spring 将单个实例限制为每个 Spring IoC 容器一个对象。在实践中,这意味着 Spring 只会为每个应用程序上下文的每种类型创建一个 bean。

Spring 的方法与单例的严格定义不同,因为一个应用程序可以有多个 Spring 容器。因此,如果我们有多个容器,则同一类的多个对象可以存在于单个应用程序中。

Singleton

默认情况下,Spring 将所有 bean 创建为单例。

2.2 自动注入单例(Autowired Singletons)

例如,我们可以在单个应用程序上下文中创建两个控制器,并将相同类型的 bean 注入到每个控制器中。

首先,我们创建一个 BookRepository 来管理我们的 Book 域对象。

接下来,我们创建 LibraryController,它使用 BookRepository 返回库中的书籍数量:

@RestController
public class LibraryController {
    
    @Autowired
    private BookRepository repository;

    @GetMapping("/count")
    public Long findCount() {
        System.out.println(repository);
        return repository.count();
    }
}

最后,我们创建一个 BookController,它专注于特定于书籍的操作,例如按 ID 查找书籍:

@RestController
public class BookController {
     
    @Autowired
    private BookRepository repository;
 
    @GetMapping("/book/{id}")
    public Book findById(@PathVariable long id) {
        System.out.println(repository);
        return repository.findById(id).get();
    }
}

然后我们启动这个应用程序,并对 /count 和 /book/1 执行 GET 请求访问:

curl -X GET http://localhost:8080/count
curl -X GET http://localhost:8080/book/1

在应用程序输出中,我们看到两个 BookRepository 对象具有相同的对象 ID:

com.baeldung.spring.patterns.singleton.BookRepository@3ea9524f
com.baeldung.spring.patterns.singleton.BookRepository@3ea9524f

LibraryController 和 BookController 中的 BookRepository 对象 ID 是相同的,这证明 Spring 将相同的 bean 注入到两个控制器中。

我们可以通过使用 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 注释将 bean 范围从单例更改为原型来创建 BookRepository bean 的单独实例。

这样做会指示 Spring 为它创建的每个 BookRepository bean 创建单独的对象。因此,如果我们再次检查每个控制器中 BookRepository 的对象 ID,我们会发现它们不再相同。

3、工厂方法模式(Factory Method Pattern)

工厂方法模式需要一个工厂类,其中包含用于创建所需对象的抽象方法。

通常,我们希望根据特定的上下文创建不同的对象。

例如,我们的应用程序可能需要车辆对象。在航海环境中,我们想制造船只,但在航空航天环境中,我们想制造飞机:

Factory pattern

为此,我们可以为每个所需对象创建一个工厂实现,并从具体的工厂方法返回所需的对象。

3.1 应用上下文(Application Context)

Spring 在其依赖注入(DI)框架的根中使用这种技术。

从根本上说,Spring 将 Bean 容器视为生产 Beans 的工厂。

因此,Spring 将 BeanFactory 接口定义为 bean 容器的抽象:

public interface BeanFactory {

    getBean(Class<T> requiredType);
    getBean(Class<T> requiredType, Object... args);
    getBean(String name);

    // ...
}

每个 getBean 方法都被视为工厂方法,它返回与提供给该方法的条件匹配的 Bean,例如 Bean 的类型和名称。

然后,Spring 使用 ApplicationContext 接口扩展了 BeanFactory,该接口引入了额外的应用程序配置。Spring 使用此配置根据某些外部配置(例如 XML 文件或 Java 注释)启动 Bean 容器。

使用 ApplicationContext 类实现(如 AnnotationConfigApplicationContext),我们可以通过从 BeanFactory 接口继承的各种工厂方法创建 bean。

首先,我们创建一个简单的应用程序配置:

@Configuration
@ComponentScan(basePackageClasses = ApplicationConfig.class)
public class ApplicationConfig {
}

接下来,我们创建一个简单的类 Foo,它不接受构造函数参数:

@Component
public class Foo {
}

然后创建另一个接受单个构造函数参数的类 Bar:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Bar {
 
    private String name;
     
    public Bar(String name) {
        this.name = name;
    }
     
    // Getter ...
}

最后,我们通过 ApplicationContext 的 AnnotationConfigApplicationContext 实现创建我们的 bean:

@Test
public void whenGetSimpleBean_thenReturnConstructedBean() {
    
    ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
    
    Foo foo = context.getBean(Foo.class);
    
    assertNotNull(foo);
}

@Test
public void whenGetPrototypeBean_thenReturnConstructedBean() {
    
    String expectedName = "Some name";
    ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
    
    Bar bar = context.getBean(Bar.class, expectedName);
    
    assertNotNull(bar);
    assertThat(bar.getName(), is(expectedName));
}

使用 getBean 工厂方法,我们可以仅使用类类型和(在 Bar 的情况下)构造函数参数来创建配置的 bean。

3.2 外部配置(External Configuration)

此模式是通用的,因为我们可以根据外部配置完全更改应用程序的行为。

如果我们希望更改应用程序中自动连线对象的实现,我们可以调整我们使用的 ApplicationContext 实现。

Factory 1

例如,我们可以将 AnnotationConfigApplicationContext 更改为基于 XML 的配置类,例如 ClassPathXmlApplicationContext:

@Test 
public void givenXmlConfiguration_whenGetPrototypeBean_thenReturnConstructedBean() { 

    String expectedName = "Some name";
    ApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
 
    // Same test as before ...
}

4、代理模式(Proxy Pattern)

代理在我们的数字世界中是一种方便的工具,我们经常在软件(例如网络代理)之外使用它们。在代码中,代理模式是一种技术,它允许一个对象(代理)控制对另一个对象(主体或服务)的访问。

Proxy class diagram

4.1 事务

为了创建代理,我们创建一个对象,该对象实现与我们的主题相同的接口,并包含对主题的引用。

然后,我们可以使用代理代替主题。

在 Spring 里,bean 被代理以控制对底层 bean 的访问。我们在使用事务时看到这种方法:

@Service
public class BookManager {
    
    @Autowired
    private BookRepository repository;

    @Transactional
    public Book create(String author) {
        System.out.println(repository.getClass().getName());
        return repository.create(author);
    }
}

在我们的 BookManager 类中,我们使用 @Transactional 注释注释创建方法。这个注释指示 Spring 原子地执行我们的创建方法。如果没有代理,Spring 将无法控制对 BookRepository bean 的访问并确保其事务一致性。

4.2 CGLib 代理(CGLib Proxies)

相反,Spring 创建了一个代理来包装我们的 BookRepository bean,并检测我们的 bean 以原子方式执行我们的 create 方法。

当我们调用 BookManager#create 方法时,我们可以看到输出:

com.baeldung.patterns.proxy.BookRepository$$EnhancerBySpringCGLIB$$3dc2b55c

通常,我们希望看到一个标准的 BookRepository 对象 ID;相反,我们看到一个 EnhancerBySpringCGLIB 对象 ID。

在底层,Spring 将我们的 BookRepository 对象包装在里面作为 EnhancerBySpringCGLIB 对象。因此,Spring 控制了对 BookRepository 对象的访问(确保事务一致性)。

Proxy

通常,Spring 使用两种类型的代理:

  • CGLib 代理 – 在代理类时使用;
  • JDK 动态代理 – 在代理接口时使用

虽然我们使用事务来公开底层代理,但 Spring 将在必须控制对 bean 的访问的任何场景中使用代理。

5、模版方法模式(Template Method Pattern)

在许多框架中,代码的很大一部分是样板代码。

例如,在数据库上执行查询时,必须完成相同的一系列步骤:

  1. Establish a connection 建立连接
  2. Execute query 执行查询
  3. Perform cleanup 执行清理
  4. Close the connection 关闭连接

这些步骤是模板方法模式的理想方案。

5.1 模版和回调(Templates & Callbacks)

模板方法模式是一种技术,用于定义某些操作所需的步骤,实现样板步骤,并将可自定义的步骤保留为抽象。然后,子类可以实现此抽象类,并为缺少的步骤提供具体的实现。

我们可以在数据库查询的情况下创建一个模板:

public abstract DatabaseQuery {

    public void execute() {
        Connection connection = createConnection();
        executeQuery(connection);
        closeConnection(connection);
    } 

    protected Connection createConnection() {
        // Connect to database...
    }

    protected void closeConnection(Connection connection) {
        // Close connection...
    }

    protected abstract void executeQuery(Connection connection);
}

或者,我们可以通过提供回调方法来提供缺失的步骤。

回调方法是一种方法,它允许主体向客户端发出信号,表明某些所需的操作已完成。

在某些情况下,主体可以使用此回调来执行操作,例如映射结果。

Template

例如,我们可以为执行方法提供一个查询字符串和一个回调方法来处理结果,而不是一个 executeQuery 方法。

首先,我们创建一个回调方法,该方法采用 Result 对象并将其映射到 T 类型的对象:

public interface ResultsMapper<T> {
    public T map(Results results);
}

然后我们更改我们的 DatabaseQuery 类以利用此回调:

public abstract DatabaseQuery {

    public <T> T execute(String query, ResultsMapper<T> mapper) {
        Connection connection = createConnection();
        Results results = executeQuery(connection, query);
        closeConnection(connection);
        return mapper.map(results);
    ]

    protected Results executeQuery(Connection connection, String query) {
        // Perform query...
    }
}

这种回调机制正是 Spring 在 JdbcTemplate 类中使用的方法。

5.2 JDBC 模版(JdbcTemplates)

JdbcTemplate 类提供查询方法,该方法接受查询字符串和 ResultSetExtractor 对象:

public class JdbcTemplate {

    public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
        // Execute query...
    }

    // Other methods...
}

ResultSetExtractor 将 ResultSet 对象(表示查询结果)转换为类型 T 的域对象:

@FunctionalInterface
public interface ResultSetExtractor<T> {
    T extractData(ResultSet rs) throws SQLException, DataAccessException;
}

Spring 通过创建更具体的回调接口进一步减少了样板代码。

例如,RowMapper 接口用于将单行 SQL 数据转换为 T 类型的域对象。

@FunctionalInterface
public interface RowMapper<T> {
    T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

为了使 RowMapper 接口适应预期的 ResultSetExtractor,Spring 创建了 RowMapperResultSetExtractor 类:

public class JdbcTemplate {

    public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
        return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
    }

    // Other methods...
}

我们可以提供如何转换单个行的逻辑,而不是提供转换整个 ResultSet 对象的逻辑,包括对行的迭代:

public class BookRowMapper implements RowMapper<Book> {

    @Override
    public Book mapRow(ResultSet rs, int rowNum) throws SQLException {

        Book book = new Book();
        
        book.setId(rs.getLong("id"));
        book.setTitle(rs.getString("title"));
        book.setAuthor(rs.getString("author"));
        
        return book;
    }
}

使用此转换器,我们可以使用 JdbcTemplate 查询数据库并映射每个结果行:

JdbcTemplate template = // create template...
template.query("SELECT * FROM books", new BookRowMapper());

除了JDBC数据库管理之外,Spring 还使用模板:

  • Java Message Service (JMS)
  • Java Persistence API (JPA)
  • Hibernate(现已弃用)
  • Transactions 事务

6、结论

上面,我们研究了 Spring 框架中应用的四种最常见的设计模式。

我们还探讨了Spring如何利用这些模式来提供丰富的功能,同时减轻开发人员的负担。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/679726.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【课程笔记】分布式计算系统 徐辰

分布式计算系统笔记 课程介绍 课程背景 大数据处理系统——> 分布式计算系统 Hadoop&#xff0c;Spark&#xff0c;Flink大数据涵义过于宽泛 本课程/教材 强调系统设计、原理、编程的集合 课程目的 培养系统思维 应用层&#xff1a;搜索、推荐算法设计层&#xff1a…

work 6.24

1、grep&#xff1a;查找字符串 grep 字符串 文件名 -w:按单词查找 -R:实现递归查找&#xff0c;主要用于路径是目录的情况 -i:不区分大小写 -n:显示行号 grep -w "^ubuntu" /etc/passwd ---->查找以ubuntu单词作为开头位置的所在行 grep -w "ubuntu$"…

使用frp实现内网穿透

本篇博客最早发布于实验室公共博客&#xff0c;但已无人维护&#xff0c;现迁移至个人博客 引言 一打五师兄走之前留了一块树莓派给我&#xff0c;暑假闲来无事拿出来玩玩 如果每次都连接显示屏和键盘使用有点麻烦而且低级 正常笔记本和树莓派都连着实验室的WIFI&#xff0c…

【人工智能概论】 PyTorch中的topk、expand_as、eq方法

【人工智能概论】 PyTorch中的topk、expand_as、eq方法 文章目录 【人工智能概论】 PyTorch中的topk、expand_as、eq方法一. topk方法1.1 简介1.2 参数详解 二. expand_as方法三. eq方法 一. topk方法 1.1 简介 对PyTorch中的tensor类型的数据都存在topk方法&#xff0c;其功…

【Java高级语法】(十四)函数式接口:与Lambda表达式共舞:探索Java函数式接口,赋予程序员更强大的编程能力!~

Java高级语法详解之函数式接口 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 函数式接口定义3.2 函数式接口实战3.3 使用技巧 4️⃣ 内置函数式接口5️⃣ 应用场景&#x1f33e; 总结 1️⃣ 概念 Java函数式接口起源于Java 8版本中的Lambda表达式和函数式编程特性的引入。在之前的…

Vue基础(二)

Vue组件 组件化是Vue.js中的重要思想 它提供了一种抽象&#xff0c;让我们可以开发出一个个独立可复用的小组件来构造我们的应用。 任何的应用都会被抽象成一颗组件树。 注册组件 <body> <div id"app"> <!--3、使用组件--><my-cpn></m…

C语言小游戏——猜数字

猜数字游戏是一种简单而有趣的游戏&#xff0c;玩家需要根据提示猜出一个随机数。 //游戏菜单 void menu() {printf("*********************\n");printf("* 1.play *\n");printf("* 0.exit *\n");printf("********…

一道北大强基题背后的故事(五)——解数学题的数学模型是什么?

早点关注我&#xff0c;精彩不错过&#xff01; 上回说到&#xff0c;数学之美&#xff0c;美在作为一个工具的通用性&#xff0c;艺术追求和思维游戏&#xff0c;相关内容请戳&#xff1a; 一道北大强基题背后的故事&#xff08;四&#xff09;——数学之美&#xff0c;美在哪…

详解sentinel使用

目录 1.概述 2.下载安装 3.应用托管 4.流量控制 4.1.流控规则 4.2.流控模式 4.2.1.直接模式 4.2.2.关联模式 4.2.3.链路模式 4.3.流控效果 4.3.1.预热 4.3.2.排队等待 5.降级 5.1.降级规则 5.2.降级策略 5.2.1.RT 5.2.2.异常比例 5.2.3.异常数 1.概述 senti…

CentOS7设置nginx服务开机自启【开机自启】

方法一&#xff1a;使用Systemd 1.创建服务单元文件 sudo vi /etc/systemd/system/nginx.service2.编辑配置文件 [Unit] DescriptionThe NGINX HTTP and reverse proxy server Aftersyslog.target network.target[Service] Typeforking ExecStartPre/usr/sbin/nginx -t Exec…

[数据结构初阶]双链表

目录 双链表定义 初始化 创建节点 尾插 ​编辑 尾删 头插 头删 打印 查找 pos插入 头插复用 尾插复用 pos删除 头删复用 尾删复用 判空 size 销毁 完整代码 前面我们学习了单链表&#xff0c;但按照带头不带头(哨兵)和循环不循环我们可以有四种单链表&#…

知识蒸馏学习记录

最近在学习降噪处理不良天气的算法过程中&#xff0c;接触到了知识蒸馏&#xff0c;该算法作为一个深度学习通用算法&#xff0c;不仅广泛应用在自然语言处理方面&#xff0c;在计算机视觉等领域也广受追捧。 概要 简单来说&#xff0c;知识蒸馏就是将一个大的教师网络萃取到…

Nginx虚拟机主机

Nginx虚拟机主机 简述 虚拟主机是一种特殊软硬件技术&#xff0c;将网络上每一台计算机分成多个虚拟主机&#xff0c;每个虚拟主机可独立对外提供www服务&#xff0c;实现一台主机对外提供多个web服务&#xff0c;每个虚拟主机之间独立&#xff0c;互不影响。 配置位置 既可以在…

基于深度学习的高精度塑料瓶检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度塑料瓶检测识别系统可用于日常生活中或野外来检测与定位塑料瓶目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的塑料瓶目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检…

自动化面试题5

一、Modbus通信485/232/422的优缺点。 &#xff08;1&#xff09;RS232和RS422是全双工的&#xff0c;RS485是半双工的。 &#xff08;2&#xff09;RS485与RS232仅仅是通讯的物理协议&#xff08;即接口标准&#xff09;有区别&#xff0c;RS485是差分传输方式&#xff0c;R…

Spring6 AOT 提前编译

文章目录 1、AOT概述1.1、JIT与AOT的区别1.2、Graalvm1.3、Native Image 2、Native Image构建过程2.1、GraalVM安装&#xff08;1&#xff09;下载GraalVM&#xff08;2&#xff09;配置环境变量&#xff08;3&#xff09;安装native-image插件 2.2、安装C的编译环境&#xff0…

法规标准-ISO 23374标准解读

ISO 23374是做什么的&#xff1f; ISO 23374全名为智能交通系统-自动代客泊车系统(AVPS) 第一部分&#xff1a;系统框架、自动驾驶要求和通信接口&#xff0c;针对AVPS的系统框架及功能要求、通信接口进行介绍&#xff0c;由于通信接口涉及功能实现&#xff0c;但此处介绍较为…

ubuntu20.04 磁盘故障,然后重装22.04

ubuntu20.04 磁盘故障&#xff0c;然后重装22.04 重装原因开机自启动不需要使用sudo 软件截图 flameshot输入法 fcitx5 重装原因 编译程序报错 /usr/include/x86_64-linux-gnu/bits/signum.h:26:10: fatal error: /usr/include/x86_64-linux-gnu/bits/signum-generic.h: 结构需…

VUE L ∠脚手架 配置代理 ⑩⑧

目录 文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持✨ V u e j s Vuejs Vuejs初识 V u e C L I VueCLI VueCLI C L I CLI CLI V u e Vue Vue配置代理 C L I CLI CLI配置方法一 C L I CLI CLI配置方法二 C L I CLI CLI V u …

经典文献阅读之--VIP-SLAM(紧耦合RGB-D视觉惯性平面SLAM)

0. 简介 现有的视觉SLAM很多的算法让仍然是基于特征提取的方法来完成地图的建立&#xff0c;而RGB-D传感器的算法仍然是主要基于稀疏点的SLAM系统&#xff0c;这就导致在构建稠密点云地图的时候需要保持大量的地图点来建模环境。大量的地图点给我们带来了很高的计算复杂性&…