Spring 中的设计模式详解

news/2024/10/15 10:07:57/

“JDK 中用到了哪些设计模式? Spring 中用到了哪些设计模式? ”这两个问题,在面试中比较常见。

我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律,而且大部分都年代久远。所以,花了几天时间自己总结了一下。

由于我的个人能力有限,文中如有任何错误各位都可以指出。另外,文章篇幅有限,对于设计模式以及一些源码的解读我只是一笔带过,这篇文章的主要目的是回顾一下 Spring 中的设计模式

控制反转(IoC)和依赖注入(DI)

IoC(Inversion of Control,控制反转) 是 Spring 中一个非常非常重要的概念,它不是什么技术,而是一种解耦的设计思想。IoC 的主要目的是借助于“第三方”(Spring 中的 IoC 容器) 实现具有依赖关系的对象之间的解耦(IOC 容器管理对象,你只管使用即可),从而降低代码之间的耦合度。

IoC 是一个原则,而不是一个模式,以下模式(但不限于)实现了 IoC 原则。

Spring IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 IoC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。

在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。

Spring IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 IoC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。

在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。

java">import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;public class App {public static void main(String[] args) {ApplicationContext context = new FileSystemXmlApplicationContext("C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");obj.getMsg();}
}

单例设计模式

在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。

使用单例模式的好处 :

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
  • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

Spring 中 bean 的默认作用域就是 singleton(单例)的。 除了 singleton 作用域,Spring 中 bean 还有下面几种作用域:

  • prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。
  • request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
  • session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
  • application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),,该 bean 仅在当前应用启动时间内有效。
  • websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。

Spring 通过 ConcurrentHashMap 实现单例注册表的特殊方式实现单例模式。

Spring 实现单例的核心代码如下:

java">// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(beanName, "'beanName' must not be null");synchronized (this.singletonObjects) {// 检查缓存中是否存在实例Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {//...省略了很多代码try {singletonObject = singletonFactory.getObject();}//...省略了很多代码// 如果实例对象在不存在,我们注册到单例注册表中。addSingleton(beanName, singletonObject);}return (singletonObject != NULL_OBJECT ? singletonObject : null);}}//将对象添加到单例注册表protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));}}
}

单例 Bean 存在线程安全问题吗?

大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。

常见的有两种解决办法:

  1. 在 Bean 中尽量避免定义可变的成员变量。
  2. 在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。

代理设计模式

代理模式在Aop中的应用

AOP(Aspect-Oriented Programming,面向切面编程) 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy 去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

当然,你也可以使用 AspectJ ,Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。

使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。

spring-aop-和-aspectj-aop-有什么区别">Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,

如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。


http://www.ppmy.cn/news/1539380.html

相关文章

RNN心脏病预测

本文为为&#x1f517;365天深度学习训练营内部文章 原作者&#xff1a;K同学啊 一 前期准备 1.数据导入 import pandas as pd from keras.optimizers import Adam from matplotlib import pyplot as plt from sklearn.model_selection import train_test_split from sklearn.p…

Maven 父子模块的 pom.xml 文件编写

今天在写课内的实验作业的时候&#xff0c;三个内容要使用的依赖是一样的&#xff0c;于是想使用父子模块来玩玩。 父模块 pom.xml 书写 打包方式 <packaging>pom</packaging> 聚合子模块 <!-- 聚合子模块 --> <modules><module>../one</…

互联网协议(IP)中最常用的端口

80 端口和 443 端口是互联网协议&#xff08;IP&#xff09;中最常用的两个端口&#xff0c;分别用于 HTTP 和 HTTPS 通信。以下是它们的作用、区别以及相关背景信息&#xff1a; 80 端口和 443 端口的作用 80 端口&#xff1a; 用于 HTTP&#xff08;HyperText Transfer Prot…

AOT漫谈专题(第三篇): 如何获取C#程序的CPU利用率

一&#xff1a;背景 1. 讲故事 上篇聊到了如何对AOT程序进行轻量级的APM监控&#xff0c;有朋友问我如何获取AOT程序的CPU利用率&#xff0c;本来我觉得这是一个挺简单的问题&#xff0c;但一研究不是这么一回事&#xff0c;这篇我们简单的聊一聊。 二&#xff1a;如何获取C…

LLM | Tokenization 从原理与代码了解GPT的分词器

声明&#xff1a;以上内容全是学习Andrej Karpathy油管教学视频的总结。 --------------------------------------------------------------------------------------------------------------------------------- 大家好。在今天我们学习llm中的Tokenization&#xff0c;即分…

注册安全分析报告:北外网校

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

【机器学习】朴素贝叶斯算法|商品评论情感分析案例介绍及代码实现

文章目录 朴素贝叶斯算法朴素贝叶斯算法介绍概率数学基础复习朴素贝叶斯算法-利用概率值进行分类的一种机器学习算法贝叶斯公式朴素贝叶斯算法拉普拉斯平滑系数 朴素贝叶斯API案例分析流程数据集代码实现运行结果 朴素贝叶斯算法 朴素贝叶斯算法介绍 概率数学基础复习 条件概…

Allegro PCB中过孔的整体替换

Cadence Allegro PCB中过孔的整体替换 在PCB设计过程中&#xff0c;之前是使用的小的过孔&#xff0c;后面需要替换成大的过孔&#xff0c;一个一个去替换过孔非常麻烦的&#xff0c;这里&#xff0c;讲解一下如何去整体的替换过孔&#xff0c;具体的操作方法如下所示&#xf…