Spring中的设计模式

目录

工厂模式

组合模式

适配器模式

代理模式

单例模式

观察者模式

模板方法模式

责任链模式


  • Spring有着非常优雅的设计,很多地方都遵循SOLID原则,里面的设计模式更是数不胜数
  • 大概有以下几种:
  • 工厂模式

    • 所谓的工厂模式,核心是屏蔽内部的实现,直接由client使用即可
    • Spring的IOC就是一个非常好的工厂模式的例子
    • Spring IOC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的
    • IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁
    • Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象
    • 两者对比:
      • BeanFactory:延迟注入(使用到某个 bean 的时候才会注入),相比于ApplicationContext来说会占用更少的内存,程序启动速度更快
      • ApplicationContext:容器启动的时候,不管你用没用到,一次性创建所有 bean
      • BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用ApplicationContext会更多
  • 组合模式

    • 组合模式在Spring MVC中用的非常多,其中的参数解析,响应值处理等模块就是使用了组合模式
    • 拿参数解析模块举例:
    • 类图如下:

    • 可以发现,整体的参数解析模块中,由一个接口 HandlerMethodArgumentResolver 负责
    • 其中父节点会实现该接口,同时对所有的具体的子接口进行聚合
    • 其实这个里面不止用了组合模式,接口还提供了 #supportsParamerter 方法,去判断是否执行该resolver,这也是策略模式的一种
  • 适配器模式

    • 适配器模式简而言之就是上游为了适应下游,而要做一些适配,承担适配工作的模块,就叫做适配器
    • 常见的场景是甲方因为话语权很高,提供了一套交互模型,而所有对接甲方模型的乙方,就需要通过适配器模式来适配甲方的模型和自己已有的系统
    • 在Spring MVC中,HandlerAdapter 就是典型的适配器模式
    • 参考其注释我们可以发现:

    • 对于DispatcherServlet来说,HandlerAdapter是核心的业务逻辑处理流程,DispatcherServlet只负责调用 HandlerAdapter#handle 方法即可
    • 至于当前Http的请求该如何处理,则交给HandlerAdapter的实现方负责
    • 换句话说,HandlerAdapter只是定义了和DispatcherServlet交互的标准,帮助不同的实现适配了DispatcherServlet而已
    • 譬如,用于Controller注解解析和url映射的逻辑就是通过 RequestMappingHandlerAdapter 实现的

    • Spring AOP中的适配器模式
      • 我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter
      • Advice 常用的类型有:BeforeAdvice(目标方法调用前,前置通知)、AfterAdvice(目标方法调用后,后置通知)、AfterReturningAdvice(目标方法执行结束后,return之前)等等
      • 每个类型Advice(通知)都有对应的拦截器:MethodBeforeAdviceInterceptor、AfterReturningAdviceAdapter、AfterReturningAdviceInterceptor
      • Spring预定义的通知要通过对应的适配器,适配成 MethodInterceptor 接口(方法拦截器)类型的对象(如:MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice)
    • Spring MVC中的适配器模式
      • 在Spring MVC中,DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler
      • 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理
      • HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类
      • 为什么要在 Spring MVC 中使用适配器模式?
      • Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理
      • 如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要自行来判断,像下面这段代码一样:

      • 假如我们再增加一个 Controller类型就要在上面代码中再加入一行判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭
  • 代理模式

    • 代理模式和适配器模式的核心区别就在于,适配器模式的目的是为了适配不同的场景,而代理模式的目的则是enhance,即增强被代理的类(如增加日志打印功能等)
    • Spring的AOP就是代理模式的典型代表,Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
    • AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性
    • 如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:

    • 当然你也可以使用 AspectJ,Spring AOP 已经集成了AspectJ
    • Spring AOP 属于运行时增强,而 AspectJ 是编译时增强
    • Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)
    • AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单
    • 如果我们的切面比较少,那么两者性能差异不大
    • 但是,当切面太多的话,最好选择 AspectJ,它比 Spring AOP 快很多
  • 单例模式

    • 单例模式是Spring一个非常核心的功能,Spring中的bean默认都是单例的,这样可以尽最大程度保证对象的复用和线程安全
    • Spring Bean也不止是单例的,还有其他作用域,如下:
      • prototype:每次获取都会创建一个新的 bean 实例;也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例
      • request(仅 Web 应用可用):每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效
      • session(仅 Web 应用可用):每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效
      • global-session(仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效
      • websocket(仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean

    • Spring 通过 ConcurrentHashMap 实现单例注册表的特殊方式实现单例模式
    • Spring 实现单例的核心代码如下:

  • 观察者模式

    • 观察者模式是一种对象行为型模式
    • 它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应
    • 当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener
    • Spring 事件驱动模型就是观察者模式很经典的一个应用
    • Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码
    • 比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题
    • Spring 事件驱动模型中的三种角色
      • 事件角色
        • ApplicationEvent(org.springframework.context包下)充当事件的角色,这是一个抽象类,它继承了java.util.EventObject并实现了 java.io.Serializable 接口
        • Spring 中默认存在以下事件,他们都是对 ApplicationEvent 的实现(继承自ApplicationEvent):

          • ContextStartedEvent:ApplicationContext 启动后触发的事件
          • ContextStoppedEvent:ApplicationContext 停止后触发的事件
          • ContextRefreshedEvent:ApplicationContext 初始化或刷新完成后触发的事件
          • ContextClosedEvent:ApplicationContext 关闭后触发的事件
      • 事件监听者角色
        • ApplicationListener 充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEvent() 方法来处理ApplicationEvent
        • ApplicationListener 接口类源码如下,可以看出接口定义看出接口中的事件只要实现了 ApplicationEvent 就可以了
        • 所以,在 Spring 中我们只要实现 ApplicationListener 接口实现 onApplicationEvent() 方法即可完成监听事件

      • 事件发布者角色
        • ApplicationEventPublisher 充当了事件的发布者,它也是一个接口

        • ApplicationEventPublisher 接口的 publishEvent() 这个方法在 AbstractApplicationContext 类中被实现,阅读这个方法的实现,你会发现实际上事件真正是通过 ApplicationEventMulticaster 来广播出去的;具体内容过多,就不在这里分析了
      • Spring 的事件流程总结
        • 1-定义一个事件:实现一个继承自 ApplicationEvent,并且写好相应的构造函数
        • 2-定义一个事件监听者:实现 ApplicationListener 接口,重写 onApplicationEvent() 方法
        • 3-使用事件发布者发布消息:可以通过 ApplicationEventPublisher 的 publishEvent() 方法发布消息
        • Example:

        • 当调用 DemoPublisher 的 publish() 方法的时候,比如 demoPublisher.publish("你好"),控制台就会打印出:接收到的信息是:你好
  • 模板方法模式

    • 用来解决代码重复的问题
    • 模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中
    • 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式

    • Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式
    • 一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用 Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性
    • 如果使用过Spring的事务管理,相信一定对 TransactionTemplate 这个类不陌生,而且顾名思义,这个也是用到了模板方法
    • 它把事务操作按照3个固定步骤来写:
    • 1. 执行业务逻辑
    • 2. 如果异常则回滚事务
    • 3. 否则提交事务如下代码所示:

  • 责任链模式

    • 对于Spring MVC来说,他会通过一系列的拦截器来处理请求执行前,执行后,以及结束的response,核心的类是 handlerExecutionChain,它封装了 HandlerAdapter 和一系列的过滤器
    • 对于执行前的处理来说,DispatherServlet会先通过 handlerExecutionChain 获取所有的 HandlerInterceptor,然后再执行处理逻辑,如下代码所示:

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

十三、Django之添加用户(原始方法实现)

修改urls.py path("user/add/", views.user_add),添加user_add.html {% extends layout.html %} {% block content %}<div class"container"><div class"panel panel-default"><div class"panel-heading"><h3 c…

解决ASP.NET Core的中间件无法读取Response.Body的问题

概要 本文主要介绍如何在ASP.NET Core的中间件中&#xff0c;读取Response.Body的方法&#xff0c;以便于我们实现更多的定制化开发。本文介绍的方法适用于.Net 3.1 和 .Net 6。 代码和实现 现象解释 首先我们尝试在自定义中间件中直接读取Response.Body&#xff0c;代码如…

【计算机网络黑皮书】传输层

【事先声明】 这是对于中科大的计算机网络的网课的学习笔记&#xff0c;感谢郑烇老师的无偿分享 书籍是《计算机网络&#xff08;自顶向下方法 第6版&#xff09;》 需要的可以私信我&#xff0c;无偿分享&#xff0c;课程简介下也有 课程链接 目录 传输服务与协议网络层与传输…

redis,mongoDB,mysql,Elasticsearch区别

Redis&#xff1a; Redis是一种高性能键值存储数据库&#xff0c;基于内存操作&#xff0c;支持数据持久化&#xff0c;支持数据类型丰富灵活&#xff0c;如字符串、哈希、列表、集合、有序集合等。Redis还提供了订阅/发布、事务、Lua脚本、主从同步等功能&#xff0c;适用于访…

数学分析:含参变量的积分

同样很多收敛性的证明不是重点&#xff0c;但里面的知识还是需要适当掌握&#xff0c;知道中间的大致思考和解决路径即可。 本质还是极限的可交换性&#xff0c;求导可以换到积分里面去操作。 这里要注意变量的区别&#xff0c;首先积分的被积变量是x&#xff0c;但是函数的变量…

对于L1正则化和L2正则化的理解

在DL中&#xff0c;L1和L2正则化经常被使用到&#xff0c;因为大于1L的正则化都是凸优化的问题&#xff0c;是个简单问题&#xff0c;可以被解决。 首先说正则的意义&#xff1a; 一切可以缓解过拟合的方法&#xff0c;都可以被叫做正则化 我最开始理解正则化的时候就是看lh…

基于共生生物优化的BP神经网络(分类应用) - 附代码

基于共生生物优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于共生生物优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.共生生物优化BP神经网络3.1 BP神经网络参数设置3.2 共生生物算法应用 4.测试结果…

一个好用的k8s代理工具——KtConnect

介绍 KtConnect实现了开发者本地运行的服务与Kubernetes集群中的服务之间的双向互通。 核心功能 本地直接访问Kubernetes集群内网 通过KtConnect可以直接连接Kubernetes集群内部网络&#xff0c;在不修改代码的情况下完成本地联调测试 本地解析Kubernetes服务内网域名 直…

SpringCloud学习笔记-注册微服务到Eureka注册中心

目录 1.在该Module的pom文件中引入eureka依赖2.在该module的src/main/resources/application.yml配置文件3.启动对应的微服务4.查看微服务是否启动成功 假如我有一个微服务名字叫user-service,我需要把它注册到Eureka注册中心,则具体步骤如下: 1.在该Module的pom文件中引入eure…

Bootstrap中固定某一个元素不随滚动条滚动

可以利用类sticky-top实现固定某个元素在顶部的效果&#xff0c;示例代码如下&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>固定某一个元素不随滚动条滚动</title><meta name"viewport&quo…

子组件跳转父组件

描述&#xff1a;父组件Form.vue 点击关联&#xff0c;弹出子组件importForm.vue 选中一条数据之后&#xff0c;点击确定按钮&#xff0c;关闭子组件importForm.vue&#xff0c;将子组件的内容显示在父组件Form.vue中 选中第一条数据&#xff0c;点击确定 父组件对应的工作内容…

【新书推荐】当 Python 遇到 ChatGPT —— 自动化办公落地

文章目录 当 Python 遇到 ChatGPT&#xff1a;一种强大的组合1. 文本生成2. 自动翻译3. 对话生成4. 情感分析 新书推荐《Python自动化办公应用大全&#xff08;ChatGPT版&#xff09;&#xff1a;从零开始教编程小白一键搞定烦琐工作&#xff08;上下册&#xff09;》前言内容简…

pillow篇---pillow连续打开同一张图片会导致打开失败问题

如果你需要在多次操作同一张图像时避免出现缓存问题&#xff0c;你可以使用 Image.open() 方法的 seek() 方法将文件指针移动到图像数据的开头&#xff0c;以便重新读取图像数据。示例如下&#xff1a; from PIL import Image# 打开图像文件 image Image.open(example.jpg)# …

mysql面试题27:数据库中间件了解过吗?什么是sharding jdbc、mycat,并且讲讲怎么使用?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:数据库中间件了解过吗,比如sharding jdbc、mycat? 我知道的数据库中间件有以下这些: MySQL Proxy:MySQL Proxy是一个开源的数据库中间件,它位…

Python学习笔记

1、缩进相同的一组语句构成一个代码块&#xff0c;称之为代码组 2、if 、while、def和class这样的复合语句&#xff0c;首行以关键字开始&#xff0c;以冒号(:)结束&#xff0c;该行之后的一行或者多行代码构成代码组 3、在python中无论输入的内容是数字还是字符&#xff0c;…

轻松实现视频、音频、文案批量合并,享受批量剪辑的便捷

在日常生活中&#xff0c;我们经常会需要将多个视频、音频和文案进行合并剪辑&#xff0c;以制作出符合我们需求的短视频。然而&#xff0c;这个过程通常需要花费大量的时间和精力。幸运的是&#xff0c;现在有一款名为“固乔智剪软件”的工具可以帮助我们轻松完成这个任务。 首…

CS5801电路设计|HDMI转EDP转接板方案|HDMI2.0转EDP数据采集卡方案

CS5801国产(北京集睿致远&#xff09;研发HDMI转DP/EDP(4K60)转换器方案芯片,其设计HDMI转EDP转接板方案电路&#xff1a; CS5801输入端可以是1080P、4K30、4K60HZ这三种规格,输出的接口可以是DP信号接口,或者是EDP信号接口,输入端HDMI由4路信号组成&#xff0c;支持1.62Gbps、…

asp.net电影院选座系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net电影院选座系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语言开发 asp.net电影院选座系统1 二、功能介…

flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge

flutter开发实战-inappwebview实现flutter与Javascript的交互JSBridge 在使用webview中&#xff0c;需要实现flutter与Javascript交互&#xff0c;在使用webview_flutter插件的时候&#xff0c;整理了一下webview与Javascript的交互JSBridge&#xff0c;具体可以查看 https:/…

jmeter添加断言(详细图解)

先创建一个线程组&#xff0c;再创建一个http请求。 为了方便观察&#xff0c;我们添加两个监听器&#xff0c;察看结果树和断言结果。 添加断言&#xff1a;响应断言&#xff0c;响应断言也是比较常用的一个断言 设置响应断言&#xff1a;正常情况下响应代码是200。选择响应代…