Spring中⽤到了哪些设计模式?
谈谈Spring中都⽤到了哪些设计模式?
JDK 中⽤到了那些设计模式?Spring 中⽤到了那些设计模式?这两个问题,在⾯试中⽐较常见。我在⽹上搜索了⼀下关于 Spring 中设计模式的讲解⼏乎都是千篇⼀律,⽽且⼤部分都年代久远。所以,花了⼏天时间⾃⼰总结了⼀下,由于我的个⼈能⼒有限,⽂中如有任何错误各位都可以指出。另外,⽂章篇幅有限,对于设计模式以及⼀些源码的解读我只是⼀笔带过,这篇⽂章的主要⽬的是回顾⼀下 Spring 中的常见的设计模式。
Design Patterns(设计模式) 表⽰⾯向对象软件开发中最好的计算机编程实践。 Spring 框架中⼴泛使⽤了不同类型的设计模式,下⾯我们来看看到底有哪些设计模式?
控制反转(IOC)和依赖注⼊(DI)
IoC(Inversion of Control,控制翻转) 是Spring 中⼀个⾮常⾮常重要的概念,它不是什么技术,⽽是⼀种解耦的设计思想。它的主要⽬的是借助于“第三⽅”(即Spring 中的 IOC 容器) 实现具有依赖关系的对象之间的解耦(IOC容易管理对象,你只管使⽤即可),从⽽降低代码之间的耦合度。IOC 是⼀个原则,⽽不是⼀个模式,以下模式(但不限于)实现了IoC原则。
ioc-patterns
Spring IOC容器就像是⼀个⼯⼚⼀样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即可,完全不⽤考虑对象是如何被创建出来的。IOC 容器负责创建对象,将对象连接在⼀起,配置这些对象,并从创建中处理这些对象的整个⽣命周期,直到它们被完全销毁。
在实际项⽬中⼀个 Service 类如果有⼏百甚⾄上千个类作为它的底层,我们需要实例化这个 Service,你可能要每次都要搞清这个 Service
所有底层类的构造函数,这可能会把⼈逼疯。如果利⽤ IOC 的话,你只需要配置好,然后在需要的地⽅引⽤就⾏了,这⼤⼤增加了项⽬的可维护性且降低了开发难度。关于Spring IOC 的理解,推荐看这⼀下知乎的⼀个回答:
,⾮常不错。
控制翻转怎么理解呢? 举个例⼦:"对象a 依赖了对象 b,当对象 a 需要使⽤对象 b的时候必须⾃⼰去创建。但是当系统引⼊了 IOC 容器后,对象a 和对象 b 之前就失去了直接的联系。这个时候,当对象 a 需要使⽤对象 b的时候,我们可以指定 IOC 容器去创建⼀个对象b注⼊到对象 a 中"。对象 a 获得依赖对象 b 的过程,由主动⾏为变为了被动⾏为,控制权反转了,这就是控制反转名字的由来。
DI(Dependency Inject,依赖注⼊),是实现控制反转的⼀种设计模式,依赖注⼊就是将实例变量传⼊到⼀个对象中去。
⼯⼚设计模式
Spring使⽤⼯⼚模式可以通过BeanFactory或ApplicationContext创建 bean 对象。
两者对⽐:
BeanFactory:延迟注⼊(使⽤到某个 bean 的时候才会注⼊),相⽐于BeanFactory来说会占⽤更少的内存,程序启动速度更快。
ApplicationContext:容器启动的时候,不管你⽤没⽤到,⼀次性创建所有 bean 。BeanFactory仅提供了最基本的依赖注⼊⽀
持,ApplicationContext扩展了BeanFactory ,除了有BeanFactory的功能之外还有额外更多功能,所以⼀般开发⼈员使⽤ApplicationContext会更多。
ApplicationContext的三个实现类:
1. ClassPathXmlApplication:把上下⽂⽂件当成类路径资源。
2. FileSystemXmlApplication:从⽂件系统中的 XML ⽂件载⼊上下⽂定义信息。
3. XmlWebApplicationContext:从Web系统中的XML⽂件载⼊上下⽂定义信息。
Example:
import t.ApplicationContext;
import t.support.FileSystemXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext(
"C:/work/IOC Containers/springframework.applicationcontext/src/main/l");
HelloApplicationContext obj = (HelloApplicationContext) Bean("helloApplicationContext");
}
}
单例设计模式
在我们的系统中,有⼀些对象其实我们只需要⼀个,⽐如说:线程池、缓存、对话框、注册表、⽇志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这⼀类对象只能有⼀个实例,如果制造出多个实例就可能会导致⼀些问题的产⽣,⽐如:程序的⾏为异常、资源使⽤过量、或者不⼀致性的结果。
使⽤单例模式的好处:
对于频繁使⽤的对象,可以省略创建对象所花费的时间,这对于那些重量级对象⽽⾔,是⾮常可观的⼀笔系统开销;
由于new操作的次数减少,因⽽对系统内存的使⽤频率也会降低,这将减轻GC压⼒,缩短GC停顿时间。
Spring中bean的默认作⽤域就是singleton(单例)的,除了singleton作⽤域,Spring中bean还有下⾯⼏种作⽤域:prototype : 每次请求都会创建⼀个新的 bean 实例。
request : 每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP request内有效。
session : 每⼀次HTTP请求都会产⽣⼀个新的 bean,该bean仅在当前 HTTP session 内有效。
global-session:全局session作⽤域,仅仅在基于portlet的web应⽤中才有意义,Spring5已经没有了。Portlet是能够⽣成语义代码(例如:HTML)⽚段的⼩型Java Web插件。它们基于portlet容器,可以像servlet⼀样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
Spring实现单例的⽅式:
xml:<bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>
注解:@Scope(value = "singleton")
Spring通过ConcurrentHashMap实现单例注册表的特殊⽅式实现单例模式。Spring实现单例的核⼼代码如下:
// 通过 ConcurrentHashMap(线程安全)实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// 检查缓存中是否存在实例
Object singletonObject = (beanName);
if (singletonObject == null) {
//...省略了很多代码
try {
singletonObject = Object();
}
//...省略了很多代码
/
/ 如果实例对象在不存在,我们注册到单例注册表中。
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));
}
spring ioc注解}
}
代理设计模式
AOP(Aspect-Oriented Programming:⾯向切⾯编程)能够将那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任(例如事务处理、⽇志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象实现了某个接⼝,那么Spring AOP会使⽤JDK Proxy,去创建代理对象,⽽对于没有实现接⼝的对象,Spring AOP会使⽤Cglib,这时候Spring AOP会使⽤Cglib⽣成⼀个被代理对象的⼦类来作为代理,如下图所⽰:
当然你也可以使⽤AspectJ,Spring AOP以及集成了AspectJ,AspectJ应该算得上是Java⽣态系统中最完整的AOP框架了。
使⽤AOP之后我们可以把⼀些通⽤的功能抽象出来,在在需要⽤到的地⽅直接使⽤即可,这样⼤⼤简化了代码量。我们需要增加新功能时也⽅便,这样也提⾼了系统扩展性。⽇志功能、事务管理等等场景都⽤到了 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 快很多。
模板⽅法
模板⽅法模式是⼀种⾏为设计模式,它定义⼀个操作中的算法的⾻架,⽽将⼀些步骤延迟到⼦类中。模板⽅法使得⼦类可以不改变⼀个算法的结构即可重定义该算法的某些特定步骤的实现⽅式。
public abstract class Template {
//这是我们的模板⽅法
public final void TemplateMethod(){
PrimitiveOperation1();
PrimitiveOperation2();
PrimitiveOperation3();
}
protected void  PrimitiveOperation1(){
//当前类实现
}
//被⼦类实现的⽅法
protected abstract void PrimitiveOperation2();
protected abstract void PrimitiveOperation3();
}
public class TemplateImpl extends Template {
@Override
public void PrimitiveOperation2() {
//当前类实现
}
@Override
public void PrimitiveOperation3() {
//当前类实现
}
}
Spring 中jdbcTemplate、hibernateTemplate等以 Template 结尾的对数据库操作的类,它们就使⽤到
了模板模式。⼀般情况下,我们都是使⽤继承的⽅式来实现模板模式,但是 Spring 并没有使⽤这种⽅式,⽽是使⽤Callback 模式与模板⽅法模式配合,既达到了代码复⽤的效果,同时增加了灵活性。
观察者模式
观察者模式是⼀种对象⾏为型模式。它表⽰的是⼀种对象与对象之间具有依赖关系,当⼀个对象发⽣改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。Spring 事件驱动模型⾮常有⽤,在很多场景都可以解耦我们的代码。⽐如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利⽤观察者模式来解决这个问题。
观察者模式是⼀种对象⾏为型模式。它表⽰的是⼀种对象与对象之间具有依赖关系,当⼀个对象发⽣改变的时候,这个对象所依赖的对象也会做出反应。Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。Spring 事件驱动模型⾮常有⽤,在很多场景都可以解耦我们的代码。⽐如我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利⽤观察者模式来解决这个问题。
Spring 事件驱动模型中的三种⾓⾊
事件⾓⾊
ApplicationEvent (t包下)充当事件的⾓⾊,这是⼀个抽象类,它继承了jav
a.util.EventObject并实现了java.io.Serializable接⼝。Spring 中默认存在以下事件,他们都是对ApplicationContextEvent的实现(继承⾃ApplicationContextEvent):
ContextStartedEvent:ApplicationContext启动后触发的事件;
ContextStoppedEvent:ApplicationContext停⽌后触发的事件;
ContextRefreshedEvent:ApplicationContext初始化或刷新完成后触发的事件;
ContextClosedEvent:ApplicationContext关闭后触发的事件。
事件监听者⾓⾊
ApplicationListener充当了事件监听者⾓⾊,它是⼀个接⼝,⾥⾯只定义了⼀个onApplicationEvent()⽅法来处
理ApplicationEvent。ApplicationListener接⼝类源码如下,可以看出接⼝定义看出接⼝中的事件只要实现了ApplicationEvent就可以了。所以,在
Spring中我们只要实现ApplicationListener接⼝实现onApplicationEvent()⽅法即可完成监听事件
package t;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
事件发布者⾓⾊
ApplicationEventPublisher充当了事件的发布者,它也是⼀个接⼝。
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
void publishEvent(Object var1);
}
ApplicationEventPublisher接⼝的publishEvent()这个⽅法在AbstractApplicationContext类中被实现,阅读这个⽅法的实现,你会发现实际上事件真正是通过ApplicationEventMulticaster来⼴播出去的。具体内容过多,就不在这⾥分析了,后⾯可能会单独写⼀篇⽂章提到。
Spring 的事件流程总结
1. 定义⼀个事件: 实现⼀个继承⾃ApplicationEvent,并且写相应的构造函数;
2. 定义⼀个事件监听者:实现ApplicationListener接⼝,重写onApplicationEvent()⽅法;
3. 使⽤事件发布者发布消息: 可以通过ApplicationEventPublisher的publishEvent()⽅法发布消息。
Example:
// 定义⼀个事件,继承⾃ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String message;
public DemoEvent(Object source,String message){
super(source);
}
public String getMessage() {
return message;
}
/
/ 定义⼀个事件监听者,实现ApplicationListener接⼝,重写 onApplicationEvent() ⽅法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
//使⽤onApplicationEvent接收消息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = Message();
System.out.println("接收到的信息是:"+msg);
}
}
// 发布事件,可以通过ApplicationEventPublisher  的 publishEvent() ⽅法发布消息。
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String message){
//发布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}
当调⽤DemoPublisher的publish()⽅法的时候,⽐如demoPublisher.publish("你好"),控制台就会打印出:接收到的信息是:你好。
适配器模式
适配器模式(Adapter Pattern) 将⼀个接⼝转换成客户希望的另⼀个接⼝,适配器模式使接⼝不兼容的那些类可以⼀起⼯作,其别名为包装器(Wrapper)。
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,需要的⾃⾏来判断,像下⾯这段代码⼀样:
Handler() instanceof MultiActionController){
((Handler()).xxx
}else Handler() instanceof XXX){
...
}else if(...){
...
}
假如我们再增加⼀个Controller类型就要在上⾯代码中再加⼊⼀⾏判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则– 对扩展开放,对修改关闭。
装饰者模式
装饰者模式可以动态地给对象添加⼀些额外的属性或⾏为。相⽐于使⽤继承,装饰者模式更加灵活。简单点⼉说就是当我们需要修改原有的功能,但我们⼜不愿直接去修改原有的代码时,设计⼀个Decorator套在原有代码外⾯。其实在 JDK 中就有很多地⽅⽤到了装饰者模式,⽐如InputStream家族,InputStream类下有FileInputStream (读取⽂件)、BufferedInputStream (增加缓存,使读取⽂件速度⼤⼤提升)等⼦类都在不修
改InputStream代码的情况下扩展了它的功能。
装饰者模式⽰意图
Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的
需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要⽤到装饰者模式(这⼀点我⾃⼰还没太理解具体原理)。Spring 中⽤到的包装器模式在类名上含有Wrapper或
者Decorator。这些类基本上都是动态地给⼀个对象添加⼀些额外的职责
总结
Spring 框架中⽤到了哪些设计模式:
⼯⼚设计模式 : Spring使⽤⼯⼚模式通过BeanFactory、ApplicationContext创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板⽅法模式 : Spring 中jdbcTemplate、hibernateTemplate等以 Template 结尾的对数据库操作的类,它们就使⽤到了模板模式。
包装器设计模式 : 我们的项⽬需要连接多个数据库,⽽且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的⼀个应⽤。
适配器模式 :Spring AOP 的增强或通知(Advice)使⽤到了适配器模式、spring MVC 中也是⽤到了适配器模式适配Controller。
……

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。