阿里二面:Spring用到了哪些设计模式?

news2024/9/21 4:30:43

代理模式

所谓代理,是指它与被代理对象实现了相同的接口,客户端必须通过代理才能与被代理的目标类进行交互,而代理一般在交互的过程中(交互前后),进行某些特定的处理,比如在调用这个方法前做前置处理,调用这个方法后做后置处理。

代理又分为静态代理和动态代理两种方式,Spring的AOP采用的是动态代理的方式

Spring通过动态代理对类进行方法级别的切面增强,动态生成目标对象的代理类,并在代理类的方法中设置拦截器,通过执行拦截器中的逻辑增强了代理方法的功能,从而实现AOP。

策略模式

我们前面讲到,Spring AOP是通过动态代理来实现的。

具体到代码实现,Spring支持两种动态代理实现方式,一种是JDK提供的动态代理实现方式,另一种是Cglib提供的动态代理实现方式。

Spring会在运行时动态地选择不同的动态代理实现方式。这个应用场景实际上就是策略模式的典型应用场景。

我们只需要定义一个策略接口,让不同的策略类都实现这一个策略接口。对应到Spring源码,AopProxy是策略接口,JdkDynamicAopProxy、CglibAopProxy是两个实现了AopProxy接口的策略类。

其中,AopProxy接口的定义如下所示:

在策略模式中,策略的创建一般通过工厂方法来实现。对应到Spring源码,AopProxyFactory是一个工厂类接口,DefaultAopProxyFactory是一个默认的工厂类,用来创建AopProxy对象。

源码如下所示:

策略模式的典型应用场景,一般是通过环境变量、状态值、计算结果等动态地决定使用哪个策略。

对应到Spring源码中,我们可以参看刚刚给出的DefaultAopProxyFactory类中的createAopProxy()函数的代码实现。

其中,第10行代码是动态选择哪种策略的判断条件。

装饰器模式

我们知道,缓存一般都是配合数据库来使用的。如果写缓存成功,但数据库事务回滚了,那缓存中就会有脏数据。

为了解决这个问题,我们需要将缓存的写操作和数据库的写操作,放到同一个事务中,要么都成功,要么都失败。

实现这样一个功能,Spring使用到了装饰器模式。

TransactionAwareCacheDecorator增加了对事务的支持,在事务提交、回滚的时候分别对Cache的数据进行处理。

TransactionAwareCacheDecorator实现Cache接口,并且将所有的操作都委托给targetCache来实现,对其中的写操作添加了事务功能。这是典型的装饰器模式的应用场景和代码实现。

单例模式

单例模式是指一个类在整个系统运行过程中,只允许产生一个实例

在Spring中,Bean可以被定义为两种模式:Prototype(多例)和Singleton(单例),Spring Bean默认是单例模式。

那Spring是如何实现单例模式的呢?

答案是通过单例注册表的方式,具体来说就是使用了HashMap。简化代码如下:

public class DefaultSingletonBeanRegistry {
    
    //使用了线程安全容器ConcurrentHashMap,保存各种单实例对象
    private final Map singletonObjects = new ConcurrentHashMap;

    protected Object getSingleton(String beanName) {
    //先到HashMap中拿Object
    Object singletonObject = singletonObjects.get(beanName);
    
    //如果没拿到通过反射创建一个对象实例,并添加到HashMap中
    if (singletonObject == null) {
      singletonObjects.put(beanName,
                           Class.forName(beanName).newInstance());
   }
   
   //返回对象实例
   return singletonObjects.get(beanName);
  }
}

上面的代码逻辑比较清晰,先到HashMap去拿单实例对象,没拿到就创建一个添加到HashMap。

简单工厂模式

有这样一个场景:

当A对象需要调用B对象的方法时,我们需要在A中new一个B的实例,它的缺点是一旦需求发生变化,比如需要使用C类来代替B时,就要改写A类的方法。

假如应用中有100个类以的方式耦合了B,那改起来就费劲了。

使用简单工厂模式:

简单工厂模式又叫静态工厂方法,其实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。

其中Spring中的BeanFactory就是简单工厂模式的体现,BeanFactory是Spring IOC容器中的一个核心接口,它的定义如下:

我们可以通过它的具体实现类(比如ClassPathXmlApplicationContext)来获取Bean:

BeanFactory bf = new ClassPathXmlApplicationContext("spring.xml");
FlyFish flyFishBean = (FlyFish) bf.getBean("flyfishBean");

从上面代码可以看到,使用者不需要自己来new对象,而是通过工厂类的方法getBean来获取对象实例,这是典型的简单工厂模式,只不过Spring是用反射机制来创建Bean的。

工厂方法模式

在简单工厂中,由工厂类进行所有的逻辑判断、实例创建;如果不想在工厂类中进行判断,可以为不同的产品提供不同的工厂,不同的工厂生产不同的产品,每一个工厂都只对应一个相应的对象,这就是工厂方法模式。

Spring中的FactoryBean就是这种思想的体现,FactoryBean可以理解为工厂Bean,先来看看它的定义:

\

我们定义一个类FlyFishFactoryBean来实现FactoryBean接口,主要是在getObject方法里new一个FlyFish对象。这样我们通过getBean(id) 获得的是该工厂所产生的FlyFish的实例,而不是FlyFishFactoryBean本身的实例,像下面这样:

BeanFactory bf = new ClassPathXmlApplicationContext("spring.xml");
FlyFish flyFishBean = (FlyFish) bf.getBean("flyfishBean");

观察者模式

Spring中实现的观察者模式包含三部分:Event事件(相当于消息)、Listener监听者(相当于观察者)、Publisher发送者(相当于被观察者)

我们通过一个例子来看下Spring提供的观察者模式是怎么使用的

// Event事件
public class DemoEvent extends ApplicationEvent {
  private String message;

  public DemoEvent(Object source, String message) {
    super(source);
  }

  public String getMessage() {
    return this.message;
  }
}

// Listener监听者
@Component
public class DemoListener implements ApplicationListener {
  @Override
  public void onApplicationEvent(DemoEvent demoEvent) {
    String message = demoEvent.getMessage();
    System.out.println(message);
  }
}

// Publisher发送者
@Component
public class DemoPublisher {
  @Autowired
  private ApplicationContext applicationContext;

  public void publishEvent(DemoEvent demoEvent) {
    this.applicationContext.publishEvent(demoEvent);
  }
}

从代码中,我们可以看出,主要包含三部分工作:

  • 定义一个继承ApplicationEvent的事件(DemoEvent);

  • 定义一个实现了ApplicationListener的监听器(DemoListener);

  • 定义一个发送者(DemoPublisher),发送者调用ApplicationContext来发送事件消息。

在Spring的实现中,观察者注册到了哪里呢?又是如何注册的呢?

Spring把观察者注册到了ApplicationContext对象中。

实际上,具体到源码来说,ApplicationContext只是一个接口,具体的代码实现包含在它的实现类AbstractApplicationContext中。我把跟观察者模式相关的代码,如下。你只需要关注它是如何发送事件和注册监听者就好。

从上面的代码中,我们发现,真正的消息发送,实际上是通过ApplicationEventMulticaster这个类来完成的。

下面这个类的源码我只摘抄了最关键的一部分,也就是multicastEvent()这个消息发送函数,它通过线程池,支持异步非阻塞、同步阻塞这两种类型的观察者模式。

借助Spring提供的观察者模式的骨架代码,如果我们要在Spring下实现某个事件的发送和监听,只需要做很少的工作,定义事件、定义监听器、往ApplicationContext中发送事件就可以了,剩下的工作都由Spring框架来完成。

实际上,这也体现了Spring框架的扩展性,也就是在不需要修改任何代码的情况下,扩展新的事件和监听。

模板模式

我们经常在面试中被问到的一个问题:

请你说下Spring Bean的创建过程包含哪些主要的步骤。

这其中就涉及模板模式。它也体现了Spring的扩展性。利用模板模式,Spring能让用户定制Bean的创建过程。

下面是Spring Bean的整个生命周期,一张图,清晰明了:

更多详细内容,可以看看之前的文章:Spring奇技淫巧之扩展点的应用

如果你仔细看过源码会发现,实际上,这里的模板模式的实现,并不是标准的抽象类的实现方式,而是有点类似Callback回调的实现方式,也就是将要执行的函数封装成对象(比如,初始化方法封装成InitializingBean对象),传递给模板(BeanFactory)来执行。

观察者模式和模板模式,这两种模式能够帮助我们创建扩展点,让框架的使用者在不修改源码的情况下,基于扩展点定制化框架功能。

适配器模式

在Spring MVC中,定义一个Controller最常用的方式是,通过@Controller注解来标记某个类是Controller类,通过@RequesMapping注解来标记函数对应的URL

不过,我们还可以通过让类实现Controller接口或者Servlet接口,来定义一个Controller。

针对这三种定义方式,我写了三段示例代码,如下所示:

// 方法一:通过@Controller、@RequestMapping来定义
@Controller
public class DemoController {
    @RequestMapping("/FlyFish")
    public ModelAndView getEmployeeName() {
        ModelAndView model = new ModelAndView("FlyFish");        
        model.addObject("message", "FlyFish");       
        return model; 
    }  
}

// 方法二:实现Controller接口 + xml配置文件:配置DemoController与URL的对应关系
public class DemoController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        ModelAndView model = new ModelAndView("FlyFish");
        model.addObject("message", "FlyFish");
        return model;
    }
}

// 方法三:实现Servlet接口 + xml配置文件:配置DemoController类与URL的对应关系
public class DemoServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doPost(req, resp);
  }
  
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello World.");
  }
}

在应用启动的时候,Spring容器会加载这些Controller类,并且解析出URL对应的处理函数,封装成Handler对象,存储到HandlerMapping对象中。当有请求到来的时候,DispatcherServlet从HanderMapping中,查找请求URL对应的Handler,然后调用执行Handler对应的函数代码,最后将执行结果返回给客户端。

但是,不同方式定义的Controller,其函数的定义(函数名、入参、返回值等)是不统一的。

DispatcherServlet调用的是service()方法,DispatcherServlet需要根据不同类型的Controller,调用不同的函数。

Spring利用适配器模式,我们将不同方式定义的Controller类中的函数,适配为统一的函数定义。

我们再具体看下Spring的代码实现。

Spring定义了统一的接口HandlerAdapter,并且对每种Controller定义了对应的适配器类。

这些适配器类包括:AnnotationMethodHandlerAdapter、SimpleControllerHandlerAdapter、SimpleServletHandlerAdapter等。

在DispatcherServlet类中,我们就不需要区分对待不同的Controller对象了,统一调用HandlerAdapter的handle()函数就可以了

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

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

相关文章

计算机网络|第二章:应用层

分层体系结构回顾 在本章中,我们学习有关网络应用的原理和实现方面的知识。 我们从定义关键的应用层概念开始,其中包括应用程序所需要的网络服务、客户和服务器、进程和运输层接口。详细考察几种网络应用程序,包括Web、电子邮件、DNS、对等文…

工业一体机在CNC机台起到什么作用?

随着工业自动化的不断发展,CNC机床已经成为现代制造业的重要设备之一。而工业一体机在CNC机台则是CNC机床的一种新型形态,其具有全封闭式设计、高精度、高效率等特点,广泛应用于各种制造行业。 全封闭工业一体机 一、工业一体机的功能&#x…

部门来了个软件测试工程师,听说是00后,上来一顿操作给我看呆了...

公司新来了个同事,听说大学是学的广告专业,因为喜欢IT行业就找了个培训班,后来在一家小公司实习半年,现在跳槽来我们公司。来了之后把现有项目的性能优化了一遍,服务器缩减一半,性能反而提升4倍!给公司省了…

ArcGIS中加载在线地图系列:风格12

前言: ArcGIS作为最强大的地理数据处理、编辑、制图和地图服务发布、管理等的GIS平台,在不同的专业都可以看到它的身影。对于那些利用GIS信息进行编辑,设计的专业人士来说,其桌面端ArcMap备受欢迎。尤其是在可视化制图方面需求颇…

C++入门(C++)

目录 命名空间 1、命名空间的定义 2、命名空间的使用 1、加名空间名称和作用域限定符 2、使用using namespace 命名空间引入 3、使用using将命名空间中某个成员引入 C的输入与输出 缺省参数 1、缺省参数的概念 2、缺省参数分类 1、全缺省参数 2、半缺省参数 函数重载 1、函数重…

【数据库】索引和事务

目录 1.索引 1.1关于索引 索引是什么? 为什么要有索引? 索引的作用? 索引的优点和缺点? 1.2索引类型及创建 索引的分类 创建索引 1.3索引的数据结构 1.4索引覆盖 2.事务 2.1关于事务 概念 事务的使用 2.2事务的特…

智慧旅游丨“服务+获客”新模式

全面放开的第一个五一小长假即将来临,旅游企业、城市、景点等又将进入流量及经济“升级密码”阶段。目前,传统旅行社在客户服务、运营模式中仍存在诸多痛点: 全面放开后“假期出游潮”日益高涨,佳信针对这些“痛点”问题&#xff…

问题排查记录-ffmpeg链接libavfilter和libavcodec:未定义的引用

目录 一、问题背景 二、问题现象 2.1 ffmpeg测试例程 2.2 编译脚本 2.3 错误提示 三、问题排查 3.1 关于提示找不到“stdio" "iostream"头文件的问题 3.1.1查看工具链头文件检索位置 3.1.2 根据工具链路径查找头文件 3.1.3 在编译脚本中指定头文件路径…

QT QPainter绘图之视口和窗口简介

1、视口和窗口的定义与原理 绘图设备的物理坐标是基本的坐标系,通过 QPainter 的平移、旋转等变换可以得到更容易操作的逻辑坐标。 为了实现更方便的坐标,QPainter 还提供了视口 (Viewport)和窗口 (Window)坐标系,通过QPainter 内部的坐标变…

【多线程】线程安全问题

1. 一段线程不安全的代码 我们先来看一段代码&#xff1a; public class ThreadDemo {public static int count 0;public static void main(String[] args) {for (int i 0; i < 10_0000; i) {count;}System.out.println("count " count);} } // 打印结果&…

Side Window Filtering 边窗滤波

原理分析 通常用常规图像算法做检测类的算法需要将图像特征增强&#xff0c;其中就需要滤波&#xff0c;把噪点去掉&#xff0c;如果直接用滤波&#xff0c;像高斯滤波&#xff0c;中值滤波&#xff0c;均值滤波等等&#xff0c;不仅会把噪点过滤掉&#xff0c;也会把图像的一些…

An error occurred during installation: No such plugin: cloudbees-folder

An error occurred during installation: No such plugin: cloudbees-folder Index of /packages/jenkins/plugins/cloudbees-folder 下载文件【cloudbees-folder.hpi】

Machine Learning-Ex6(吴恩达课后习题)Support Vector Machines

目录 1. Support Vector Machines 1.1 Example Dataset 1 1.2 SVM with Gaussian Kernels 1.2.1 Gaussian Kernel 1.2.2 Example Dataset 2 1.2.3 Example Dataset 3 2. Spam Classification 2.1 Preprocessing Emails 2.1.1 Vocabulary List 2.2 Extracting Feature…

ffmpeg学习发现av_err2str使用报错问题

最近在学习ffmpeg,照着书上敲代码,发现有个av_err2str错误.书上环境是linux系统的,我使用的windows系统编译器使用的是VS2015.可能是微软的编译器和GCC编译器不太一样这个宏函数用不了. 会报这个错误. 网上找资料超级少,找到一个类似的按照上面修改ffmpeg代码.上面的错误没有了…

Java——装箱和拆箱

一.装箱和拆箱的概念 基本数据(Primitive)类型的自动装箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0开始提供的功能。Java语言规范中说道&#xff1a;在许多情况下包装与解包装是由编译器自行完成的&#xff08;在这种情况下包装称为装箱&#xff0c;解包装称为拆箱&#xff09…

360文件恢复怎么做?3种文件恢复方法分享!

案例&#xff1a;360文件恢复怎么做&#xff1f; 【为了防止病毒入侵和更好的保护电脑&#xff0c;我在电脑上安装了360杀毒软件&#xff0c;但是我昨天在进行垃圾扫描时&#xff0c;软件把我一个很重要的文件删除了&#xff0c;有没有朋友遇到过这种情况呀&#xff1f;我应该…

高数三重积分+离散二元关系+线代矩阵解线性方程

&#x1f442; 梦寻古镇 - 羽翼深蓝Wings - 单曲 - 网易云音乐 &#x1f442; 老男孩 - 1个球 - 单曲 - 网易云音乐 目录 &#x1f33c;前言 &#x1f33c;高数 &#x1f418;B站 -- 三重积分 &#x1f418;课本 -- 7种曲面 公式 &#x1f418;PPT -- 知识点 例题 &a…

【Android FrameWork(五)】- ServiceManager

文章目录 前言源码分析1.service_mananger流程2.Binder通信流程&#xff08;ServiceManager.addService&#xff09;3.Binder的cmd流程图 拓展知识总结1.service_manager2.Binder 前言 接上一篇文章 源码分析 1.service_mananger流程 2.Binder通信流程&#xff08;ServiceMan…

VScode好用的设置(鼠标滚动缩进字体大小等等)

首先我们打开VScode软件&#xff0c;找到左下角的设置 点击设置&#xff0c;找到setting.json&#xff0c;然后点进去 把下面的复制进去&#xff0c;如果想看&#xff0c;可以鼠标悬浮在上面点击看详情 { "workbench.startupEditor": "none", "files.…

电商平台架构演变

大家好&#xff0c;我是易安&#xff01; 今天&#xff0c;我以国内这些年来电商平台的架构的角度&#xff0c;来具体说明下&#xff0c;电商架构是如何一步步演进的。 从2003年淘宝上线开始&#xff0c;国内电商平台经历了高速的发展&#xff0c;在这个过程中&#xff0c;系统…