Spring 框架中用到了哪些设计模式?
- 工厂设计模式 :
Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象
。针对使用工厂方法模式实例化对象的方式,Spring的IoC容器提供了对应的集成支持。我们所要做的只是将工厂类所返回的具体的接口实现类注入给主体对象
。工厂模式前置一+工厂模式前置二- Spring使用
工厂模式
可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象
。- BeanFactory :
延迟注入(使用到某个 bean 的时候才会注入)
,相比于ApplicationContext 来说会占用更少的内存,程序启动速度更快 - ApplicationContext :
容器启动的时候,不管你用没用到,一次性创建所有 bean
。BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用 ApplicationContext会更多
- ApplicationContext的三个实现类:
- ClassPathXmlApplication:把上下文文件当成类路径资源。
- FileSystemXmlApplication:从文件系统中的 XML 文件载入上下文定义信息。
- XmlWebApplicationContext:从Web系统中的XML文件载入上下文定义信息
- ApplicationContext的三个实现类:
- BeanFactory :
- 之前我们一直在强调“面向接口编程”,虽然对象可以通过声明接口来避免对特定接口实现类的过度耦合,但总归
需要一种方式将声明依赖接口的对象与接口实现类关联起来
。否则,只依赖一个不做任何事情的接口是没有任何用处的(也就是说我们至少得依赖一个含有业务逻辑的接口)。- (这个相当于啥呢,就是我不想在改变下层子实现类的时候由于依赖关系影响上层接口,不得不使得上层接口发生大变化,如果这个类是我自己写的,我应用Spring实现按需分配)
如果该类是由我们设计并开发的,我们可以通过依赖注入让容器帮助我们解除接口与实现类之间的耦合性
。 - (这个相当于啥呢,就是我不想在改变下层子实现类的时候由于依赖关系影响上层接口,不得不使得上层接口发生大变化,如果这个类是我自己写的,我应用Spring实现按需分配。如果这个类不是我自己写的,那么咱们有一句名言,
没有加一层中间层解决不了的。所以我让工厂类去实例化具体的接口,然后我主体的业务逻辑对象只需要依赖于工厂,实现类有变化的话我只是改变工厂类而不需要对上层的接口做改动
)但是,有时,我们需要依赖第三方库
,需要实例化并使用第三方库中的相关类,这时,接口与实现类的耦合性需要其他方式来避免
。通常的做法是通过使用工厂方法(Factory Method)模式,提供一个工厂类来实例化具体的接口实现类
,这样,主体对象只需要依赖工厂类,具体使用的实现类有变更的话,只是变更工厂类,而主体对象不需要做任何变动。
- (这个相当于啥呢,就是我不想在改变下层子实现类的时候由于依赖关系影响上层接口,不得不使得上层接口发生大变化,如果这个类是我自己写的,我应用Spring实现按需分配)
- 静态工厂方法的使用:
- 静态工厂或实例工厂也就是factory-bean:设计模式里,工厂方法模式分静态工厂和实例工厂
- 静态工厂
- 实例工厂
- 静态工厂
- 假设某个第三方库发布了BarInterface,为了向使用BarInterface接口的客户端对象屏蔽以后可能对BarInterface实现类的变动,同时还提供了一个静态的工厂方法实现类StaticBarInterface-Factory,代码如下:
public class StaticBarInterfaceFactory{ public static BarInterface getInstance(){ return new Bar InterfaceImpl(); } }
//为了将该静态工厂方法类返回的实现注入Foo,我们使用以下方式进行配置(通过setter方法注入方式,为Foo注入BarInterface的实例) : <bean id="foo" class="...Foo"> <property name= "barInterface"> <ref bean="bar"/> </property> </bean>//class指定静态方法工厂类,factory-method指定工厂方法名称,然后,容器调用该静态方法工厂类的指定工厂方法(getInstance) ,并返回方法调用后的结果,即BarInterfaceImpl的实例。 <bean id= "bar" class=" ... staticBarInterfaceFactory" factory-method="getInstance"/> //也就是说,为foo注入的bar实际上是BarInterfaceImp1的实例,即方法调用后的结果,而不是静态工厂方法类(staticBarInterfaceFactory)。我们可以实现自己的静态工厂方法类返回任意类型的对象实例,但工厂方法类的类型与工厂方法返回的类型没有必然的相同关系,咱们只是让工厂成为一个中间层,但最后注入并被使用的还是咱们原来的实现类。
- 静态工厂或实例工厂也就是factory-bean:设计模式里,工厂方法模式分静态工厂和实例工厂
- Spring使用
- 代理设计模式 : Spring AOP 【AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。】功能的实现就是通过代理。Spring主要是使用两种:
- 使用JDK动态代理
- CGLIB代理
单例设计模式
: Spring 中的 Bean 默认都是单例的- Spring 实现单例的方式:
- xml:
<bean id="userService" class="top.xxx.UserService" scope="singleton"/>
- 注解:
@Scope(value = "singleton")
- xml:
- 标记为singleton的bean是
由容器来保证这种类型的bean在同一个容器中只存在一个共享实例
;- singleton类型的bean定义,在一个容器中只存在一个共享实例,所有对该类型bean的依赖都引用这一单一实例。 这就好像每个幼儿园都会有一个滑梯一样,这个幼儿园的小朋友共同使用这一个滑梯。而对于该幼儿园容器来说,滑梯实际上就是一个singleton的bean。
- 另外,singleton类型bean定义,从容器启动,到它第一次被请求而实例化开始,只要容器不销毁或者退出,该类型bean的单一实例就会一直存活。
而Singleton模式则是保证在同一个classloader中只存在一个这种类型的实例
。Spring 通过 ConcurrentHashMap 实现单例注册表的特殊方式实现单例模式
。Spring 实现单例的核心代码如下:// 通过 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)); } } }
- 在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
- 使用单例模式的好处:
- 对于频繁使用的对象,
可以省略创建对象所花费的时间
,这对于那些重量级对象而言,是非常可观的一笔系统开销; 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间
。
- 对于频繁使用的对象,
- 使用单例模式的好处:
- Spring 实现单例的方式:
- 模板方法模式 : Spring 中jdbcTemplate、hibernateTemplate 等
以 Template 结尾的对数据库操作的类
,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
- 模板方法模式是一种行为设计模式,
它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中
。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式
。它的核心思想就是:定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现
,这样不同的子类就可以定义出不同的步骤。- 比如假设我们要去给超市进货,那么我们可以坐高铁或者飞机,或者火车,那么定义一个乘坐xx交通工具的抽象类,可以有以下模板:买票->安检->
乘坐xx交通工具
->到达北京。然后让子类继承该抽象类,实现对应的模板方法,相当于就可以延迟咱们的目标或者实现到子类中。 - AQS中也定义了一些模板方法如下:
就是AQS提供tryAcquire,tryAcquireShared等模板方法,给子类实现自定义的同步器
。
- 比如假设我们要去给超市进货,那么我们可以坐高铁或者飞机,或者火车,那么定义一个乘坐xx交通工具的抽象类,可以有以下模板:买票->安检->
- 假设我们有这么一个业务场景:内部系统不同商户调用我们系统接口,去跟外部第三方系统交互(http方式)
- 让咱们实现代码的话就这样:
// 商户A处理句柄 CompanyAHandler implements RequestHandler { Resp hander(req){ //查询商户信息 queryMerchantInfo(); //加签 signature(); //http请求(A商户假设走的是代理) httpRequestbyProxy() //验签 verify(); } } // 商户B处理句柄 CompanyBHandler implements RequestHandler { Resp hander(Rreq){ //查询商户信息 queryMerchantInfo(); //加签 signature(); // http请求(B商户不走代理,直连) httpRequestbyDirect(); // 验签 verify(); } }
- 问题来了:假设新加一个C商户接入,你需要再实现一套这样的代码。并且不符合单一职责原理和对修改关闭对扩展开放显然,这样代码就重复了,
一些通用的方法,却在每一个子类都重新写了这一方法
。此时就可以使用模板方法模式【定义一个操作中的算法的骨架流程,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。它的核心思想就是:定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现,这样不同的子类就可以定义出不同的步骤。模板方法使用时需要先搞一个抽象类,定义骨架流程(抽象方法放一起),然后确定的共同方法步骤,放到抽象类(去除抽象方法标记),最后不确定的步骤,给子类去差异化实现
】。- 定义一个抽象类,包含请求流程的几个方法,方法首先都定义为抽象方法:
/** * 抽象类定义骨架流程(查询商户信息,加签,http请求,验签) */ abstract class AbstractMerchantService { //查询商户信息 abstract queryMerchantInfo(); //加签 abstract signature(); //http 请求 abstract httpRequest(); // 验签 abstract verifySinature(); }
- 确定的共同方法步骤,放到抽象类
abstract class AbstractMerchantService { //模板方法流程 Resp handlerTempPlate(req){ //查询商户信息 queryMerchantInfo(); //加签 signature(); //http 请求 httpRequest(); // 验签 verifySinature(); } // Http是否走代理(提供给子类实现) abstract boolean isRequestByProxy(); }
- 不确定的步骤,给子类去差异化实现【
因为是否走代理流程是不确定的,所以给子类去实现
】//商户A的请求实现: CompanyAServiceImpl extends AbstractMerchantService{ Resp hander(req){ return handlerTempPlate(req); } //走http代理的 boolean isRequestByProxy(){ return true; } }
//商户B的请求实现: CompanyBServiceImpl extends AbstractMerchantService{ Resp hander(req){ return handlerTempPlate(req); } //公司B是不走代理的 boolean isRequestByProxy(){ return false; } }
- 定义一个抽象类,包含请求流程的几个方法,方法首先都定义为抽象方法:
- 让咱们实现代码的话就这样:
- 模板方法模式是一种行为设计模式,
- 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。
这种模式让我们可以根据客户的需求能够动态切换不同的数据源
- Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式
- 【装饰者模式】:
装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面
。- 在 JDK 中就有很多地方用到了装饰者模式,比如 InputStream家族,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream 代码的情况下扩展了它的功能。
- 在 JDK 中就有很多地方用到了装饰者模式,比如 InputStream家族,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream 代码的情况下扩展了它的功能。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。观察者模式是一种对象行为型模式。
观察者模式表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应
。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码
。比如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。- Spring的事件流程总结:
- 步骤一:定义一个事件,让这个事件实现ApplicationEvent,并且写好相应的构造函数
- 步骤二:定义一个事件监听者,实现ApplicationListener接口,重写onApplicationEvent()方法
- 步骤三:使用事件发布者发布消息,可以通过ApplicationEventPublisher的publishWvent()方法发布消息。
- Spring 事件驱动模型中的三种角色:
- 事件角色:
- org.springframework.context包下的ApplicationEvent充当事件的角色,这是一个抽象类,它继承了java.util.EventObject并实现了 java.io.Serializable接口。
- Spring 中默认存在以下事件,他们都是
对 ApplicationContextEvent 的实现
(继承自ApplicationContextEvent):
- ContextStartedEvent:ApplicationContext 启动后触发的事件;
- ContextStoppedEvent:ApplicationContext 停止后触发的事件;
- ContextRefreshedEvent:ApplicationContext 初始化或刷新完成后触发的事件;
- ContextClosedEvent:ApplicationContext 关闭后触发的事件
- Spring 中默认存在以下事件,他们都是
- org.springframework.context包下的ApplicationEvent充当事件的角色,这是一个抽象类,它继承了java.util.EventObject并实现了 java.io.Serializable接口。
- 事件监听者角色
- ApplicationListener 这个接口充当了事件监听者角色,
ApplicationListener 接口里面只定义了一个 onApplicationEvent()方法来处理ApplicationEvent
。【从ApplicationListener接口类源码可以看出接口定义看出接口中的事件只要实现了 ApplicationEvent就可以完成监听事件,在 Spring中我们只要实现 ApplicationListener 接口实现 onApplicationEvent() 方法即可完成监听事件
】package org.springframework.context; import java.util.EventListener; @FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { void onApplicationEvent(E var1); }
- ApplicationListener 这个接口充当了事件监听者角色,
- 事件发布者角色
- ApplicationEventPublisher接口充当了事件的发布者,
ApplicationEventPublisher 接口的publishEvent()这个方法在AbstractApplicationContext类中被实现,实际上事件真正是通过ApplicationEventMulticaster来广播出去的
。@FunctionalInterface public interface ApplicationEventPublisher { default void publishEvent(ApplicationEvent event) { this.publishEvent((Object)event); } void publishEvent(Object var1); }
- ApplicationEventPublisher接口充当了事件的发布者,
- 事件角色:
- Spring的事件流程总结:
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller【
Spring预定义的通知或者叫增强,要通过对应的适配器,适配成 MethodInterceptor接口(方法拦截器)类型的对象(如:MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice)
】。【适配器模式(Adapter Pattern) 将一个接口转换成客户希望的另一个接口
,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。】适配器模式做的就是,有一个接口需要实现,但是我们现成的对象都不满足,需要加一层适配器来进行适配
。适配器模式总体来说分三种:默认适配器模式、对象适配器模式、类适配器模式- Spring AOP中的适配器模式:
Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter
。- Spring AOP 的增强或叫通知,就是Advice 常用的类型有:【
每个类型Advice(通知)都有对应的拦截器
:】- BeforeAdvice(目标方法调用前,前置通知)
- 有拦截器MethodBeforeAdviceInterceptor。【
Spring预定义的通知要通过对应的适配器,适配成 MethodInterceptor接口(方法拦截器)类型的对象(如:MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice)
。】
- 有拦截器MethodBeforeAdviceInterceptor。【
- AfterAdvice(目标方法调用后,后置通知)
- 有拦截器AfterReturningAdviceAdapter
- AfterReturningAdvice(目标方法执行结束后,return之前)
- 有拦截器AfterReturningAdviceInterceptor
- BeforeAdvice(目标方法调用前,前置通知)
- Spring AOP 的增强或叫通知,就是Advice 常用的类型有:【
- Spring MVC中的适配器模式:
在Spring MVC中,DispatcherServlet 根据请求信息调用 HandlerMapping,解析出请求对应的 Handler或者叫控制器或者叫Controller。解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter 适配器处理
。HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类
。- 为什么要在 Spring MVC 中使用适配器模式?
Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,那不得好几万个if else嘛,如下逻辑:
。假如我们再增加一个 Controller类型就要在上面代码中再加入一行 判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭
。if(mappedHandler.getHandler() instanceof MultiActionController){ ((MultiActionController)mappedHandler.getHandler()).xxx }else if(mappedHandler.getHandler() instanceof XXX){ ... }else if(...){ ... }
- IoC(Inversion of Control,控制翻转) 是
Spring 中一种解耦的设计思想
。它的主要目的是借助于“第三方”(Spring 中的 IOC 容器) 实现具有依赖关系的对象之间的解耦(IOC容易管理对象,你只管使用即可),从而降低代码之间的耦合度。IOC 是一个原则,而不是一个模式,以下模式(但不限于)实现了IoC原则。【Spring IOC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。
】
- 在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
"对象a 依赖了对象 b,当对象 a 需要使用 对象 b的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象a 和对象 b 之前就失去了直接的联系。这个时候,当对象 a 需要使用 对象 b的时候, 我们可以指定 IOC 容器去创建一个对象b注入到对象 a 中"。 对象 a 获得依赖对象 b 的过程,由主动行为变为了被动行为,控制权翻转,这就是控制反转名字的由来
。- DI(Dependecy Inject,依赖注入)是实现控制反转的一种设计模式,依赖注入就是将实例变量传入到一个对象中去。
未完待续…
巨人的肩膀:
设计模式之禅
Head First 设计模式
spring揭秘
JavaGuide
程序员田螺的设计模式文章