Spring里用到了哪些设计模式
前言
前几天,一位读者面阿里被问到一个问题:Spring框架用到了哪些设计模式?,答的不是很好,于是打算写篇文章讲讲这个!
- 文章首发在公众号(月伴飞鱼),之后同步到个人网站:xiaoflyfish.cn/
- 面经:社招一年半面经分享(含阿里美团头条京东滴滴)
微信搜索:月伴飞鱼,交个朋友,进面试交流群
- 公众号后台回复666,可以获得免费电子书籍
觉得不错,希望点赞,在看,转发支持一下,谢谢
代理模式
所谓代理,是指它与被代理对象实现了相同的接口,客户端必须通过代理才能与被代理的目标类进行交互,而代理一般在交互的过程中(交互前后),进行某些特定的处理,比如在调用这个方法前做前置处理,调用这个方法后做后置处理。
代理又分为静态代理和动态代理两种方式,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中拿ObjectObject 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 {@Overridepublic void onApplicationEvent(DemoEvent demoEvent) {String message = demoEvent.getMessage();System.out.println(message);}
}// Publisher发送者
@Component
public class DemoPublisher {@Autowiredprivate 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 {@Overridepublic 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 {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected 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()函数就可以了
作者:程序员段飞
链接:https://juejin.cn/post/7066266639355871268
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Spring里用到了哪些设计模式相关推荐
- spring中用到的9种设计模式
spring中用到了9种设计模式,学习spring的源码以及设计模式,可以提高开发人员软件设计以及开发的水平,写出更加优雅的代码. 文章目录 简单工厂(非23种设计模式中的一种) 工厂方法 单例模式 ...
- Spring运用到的几种设计模式
一.什么是Spring? Spring是一个轻量级的IOC和AOP容器框架 是为JAVA应用程序提供基础性服务的一套框架,目的是用于简化应用程序的开发,它使得开发者只需要关心业务需求. 在spring ...
- 在普通Java类里使用spring里注入的service、dao等
版权声明:本文为博主武伟峰原创文章,转载请注明地址http://blog.csdn.net/tianyaleixiaowu. 在spring管理的web项目里,譬如Struts和spring的项目,配 ...
- 谈谈Spring中都用到了那些设计模式
控制反转(IoC)和依赖注入(DI) IoC(Inversion of Control,控制翻转) 是Spring 中一个非常非常重要的概念,它不是什么技术,而是一种解耦的设计思想.它的主要目的是借助 ...
- Spring主要用到两种设计模式
Spring主要用到两种设计模式 1.工厂模式 Spring容器就是实例化和管理全部Bean的工厂. 工厂模式可以将Java对象的调用者从被调用者的实现逻辑中分离出来. 调用者只关心被调用者必须满足的 ...
- Spring中都用到了哪些设计模式?
Spring 框架中用到了哪些设计模式: 工厂设计模式 : Spring使用工厂模式通过 BeanFactory.ApplicationContext 创建 bean 对象. 代理设计模式 : Spr ...
- Spring中都用到了哪些设计模式
原文地址:https://juejin.cn/post/6844903849849962509 面试官:"谈谈Spring中都用到了那些设计模式?". 我自己总结的Java学习的系 ...
- spring框架中用到了哪些设计模式
spring框架中用到了哪些设计模式 1.工厂设计模式 pring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象. 两者对比: BeanF ...
- spring里结合POI生成EXCEL
spring里支持从数据动态生成PDF/XSL,PDF的很多例子了,而Spring可以结合Apache的POI包,来生成EXCEL的. 首先到http://jakarta.apache.org/poi ...
- Spring 中经典的 9 种设计模式,打死也要记住啊!
点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达今日推荐:分享一套基于SpringBoot和Vue的企业级中后台开源项目,这个项目有点哇塞!个人原创100W +访问量博客:点 ...
最新文章
- CSS基础学习-7.CSS元素分类
- 从小白到精通python要多久-零基础如何学Python?小白学Python需要多久?
- ABAP Development Tool IDE里编写的CDS view源代码是如何传递到ABAP后台并解析的
- Linux shell 内部命令与外部命令有什么区别以及怎么辨别
- mongo数据库和mysql数据库的区别_Mongodb与mysql数据库的区别
- 记录一些容易忘记的属性 -- UITabBarController
- 【HDU - 4509】湫湫系列故事——减肥记II(合并区间模板 or 离散化标记 or 线段树)
- python时间序列滞后命令,时间序列-相关性和滞后时间
- Available Packages更换国内源后,仍显示为nothing to show
- JavaScript算法 之 选择排序
- 网站安全之为Web项目添加验证码功能(一)
- Officescan 常用的端口
- commandname
- 苹果4至苹果X解锁id最新工具及教程
- 视频格式转换(avi、wmv、flv、mkv、rmvb、rm、3gp转MP4、MP3)边学边开发
- LSTM实现股票预测
- 如何加声调口诀_拼音标声调的口诀歌
- proftpd 服务器配置
- JavaWeb09_Cookie Session
- 配置CLion clang-format保存时自动格式化