一,工厂模式

Spring使用工厂模式可以通过 BeanFactoryApplicationContext 创建 bean 对象。

两者对比:

BeanFactory :

延迟注入——使用到某个 bean 的时候才会注入。
相比于ApplicationContext来说,因为延迟注入,所以BeanFactory占用更少的内存,程序启动速度更快。

ApplicationContext

容器启动的时候,不管你用没用到,一次性创建所有 bean 。
BeanFactory 仅提供了最基本的依赖注入支持,具体属性的依赖注入工作要在Bean的实例化步骤完成之后,在初始化的依赖注入环节进行。
ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有其他的更多功能,所以一般开发人员使用ApplicationContext会更多。

二,单例模式

在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存组件、日志对象、打印机、显卡等设备驱动程序的对象。
事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些数据错误甚至是系统问题。

2.1,使用单例模式的好处:

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,频繁的创建和销毁,是一笔比较大的系统开销
  • 由于 创建对象的操作次数的减少,还可以减轻 GC 压力,缩短 GC停顿时间。

2.2,拓展,Spring中bean的几种作用域:

除了 singleton 作用域,Spring 中 bean 还有下面几种作用域:

  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP请求内有效。
  • session : 每一次HTTP会话都会产生一个新的 bean,该bean仅在当前 HTTP会话内有效。
  • global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。

2.3,Spring 实现单例的方式:

Spring 中 bean 的默认作用域就是 singleton(单例)的。

xml :

  1. <bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>

注解:

@Scope(value = “singleton”)

2.4,Spring实现单例模式的源码:

Spring 通过 ConcurrentHashMap 实现单例注册表的特殊方式实现单例模式—生成单例Bean。

Spring 实现单例的核心代码如下:

  1. // 通过 ConcurrentHashMap(线程安全) 实现单例注册表,存储单例对象
  2. private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
  3. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  4. Assert.notNull(beanName, "'beanName' must not be null");
  5. synchronized (this.singletonObjects) {
  6. // 检查缓存中是否存在实例
  7. Object singletonObject = this.singletonObjects.get(beanName);
  8. if (singletonObject == null) {
  9. //...省略了很多代码
  10. try {
  11. singletonObject = singletonFactory.getObject();
  12. }
  13. //...省略了很多代码
  14. // 如果实例对象在缓存中不存在,我们将其注册到单例注册表中。
  15. addSingleton(beanName, singletonObject);
  16. }
  17. return (singletonObject != NULL_OBJECT ? singletonObject : null);
  18. }
  19. }
  20. //将对象添加到单例注册表
  21. protected void addSingleton(String beanName, Object singletonObject) {
  22. synchronized (this.singletonObjects) {
  23. this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
  24. }
  25. }
  26. }

三,代理模式

代理模式在Spring中的应用主要是 AOP 中

AOP,一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),

切面将那些与业务无关,却被业务模块共同调用的逻辑提取、并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象。

而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理。

当然你也可以使用 AspectJ ,Spring AOP 已经集成了AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。

Spring AOP 属于运行时增强,即动态代理;而 AspectJ 是编译时增强,即静态代理。

如果我们的切面比较少,那么AOP和AspectJ 两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。

四,策略模式

4.1,什么是策略模式:


1,把一个类中经常改变或者将来可能改变的部分提取出来,封装为一个接口;

2,然后在使用类中(环境类),将这个接口类型的实例作为一个属性进行声明;

3,这样使用类的实例在真正运行时,需要哪个具体的策略实现类,就传入哪个,赋给步骤2中声明好的属性。

这样,就可以做到 随意调用 这个接口的 不同的策略实现类了。

比如说:定义一系列的算法,每一个算法封装一个实现类,实现统一的接口,使它们可相互替换,使得算法可独立于使用它的客户而变化。这,就是策略模式。

4.2,适用情况以及优缺点:

适用情况:一个系统需要动态地在几种算法中选择一种。
优点:可以动态的改变对象的行为
缺点:
1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类
2、策略模式将造成产生很多策略类

4.3,策略模式核心:3个部分

1)抽象策略接口 :

定义所有策略实现类的公共接口;

2)具体策略类 :

实现刚才的抽象策略接口,填充具体的某种算法或策略。

3)使用类/环境类 :

1,声明一个 抽象策略接口 类型的属性;
2,通过构造方法或set方法,将一个具体策略类对象作为实参进行传入,赋值给步骤1中的属性。
3,使用步骤1中的属性,来调用某具体策略类中定义的具体函数。

4.4,Spring中的策略模式:

策略模式,简单来说就是封装好一组策略算法,然后可以根据不同的条件选择不同的策略算法解决问题。

比如说:在Spring中,加载资源文件的方式,使用了不同的类的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource。

但这些类他们都有共同的接口—Resource(抽象策略接口)
即,针对不同的资源,Spring创建了Resource接口的多个实现类,以此实现不同的访问方式。我们看一张类图:
image.png

简单介绍一下Resource的实现类:

  • UrlResource:访问网络资源的实现类。
  • ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类。
  • ByteArrayResource:访问字节数组资源的实现类。
  • PathResource:访问文件路径资源的实现类。
  • ClassPathResource:访问类加载路径里资源的实现类。

这就是策略模式的思想,通过外部条件使用不同的算法解决问题。

五,装饰者模式

1. 装饰者模式的简介

装饰者模式可以动态地给对象添加一些额外的属性或函数,简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,那么设计一个装饰类套在原有代码外面

就增加功能来说,装饰者相比子类更加灵活。

为什么说是动态的将责任附加到对象身上,因为装饰者模式有了装饰角色,就可以根据需要动态的装饰不同的具体构件角色和具体装饰角色,这个具体构件和装饰角色是可以动态的切换的。

2. 装饰者模式的特点:

装饰器角色和被装饰器角色都实现同一个接口, 主要目的是使得装饰器和被装饰器都实现同一个接口, 是为了扩展之后依旧保留 OOP 关系(同宗同源)。

3. 装饰者模式的应用场景:

IO 流包装、 数据源包装;

其实在 JDK 中就有很多地方用到了装饰者模式,比如说InputStream,InputStream 下边有 FileInputStream (读取文件)、BufferedInputStream等子类,都在不修改InputStream 代码的情况下扩展了各自的功能。

在spring中的体现:Spring 中用到的包装器模式在类名上有两种表现: 一种是类名中含有 Wrapper, 另一种是类名中含有Decorator。 基本上都是动态地给一个对象添加一些额外的职责。

4. 装饰者模式的4个角色

在装饰模式中的角色有:

1)抽象构件角色:

这是一个抽象接口,主要是用来规范准备添加附加责任的对象。

2)具体构件角色:

定义一个准备附加责任的类。

3)抽象装饰角色:

这是一个抽象接口,该接口包含了要添加的附加责任的抽象方法
该接口继承了抽象构件角色,用来在具体装饰角色中保持原有的功能;

4)具体装饰角色:

负责添加具体的附加责任。

4.1,实现了抽象装饰角色;
4.2,以抽象构件角色为类型,注入具体构件角色
4.3,通过具体构建角色对象执行原有的功能函数;
4.4,重写附加责任的方法,添加具体的附加责任;

5,Spring中的装饰者模式:

在spring中的体现:Spring 中用到的包装器模式在类名上有两种表现:一种是类名中含有 Wrapper, 另一种是类名中含有Decorator。

都是为了动态地给一个对象添加一些额外的职责。

1)Spring配置 DataSource:

Spring 中配置 DataSource数据源的时候,这些dataSource可能是各种不同类型的,比如不同的数据库:Oracle、SQL Server、MySQL等,
也可能是不同的数据源:比如apache 提供的org.apache.commons.dbcp.BasicDataSource、spring提供的org.springframework.jndi.JndiObjectFactoryBean等。

这时,能否在尽可能少修改原有类代码下的情况下,做到动态切换不同的数据源?此时就可以用到装饰者模式。

Spring根据每次请求的不同,将dataSource属性设置成不同的数据源,以到达切换数据源的目的。

2)MyBatis执行器:

MyBatis中关于Cache和CachingExecutor接口的实现类也使用了装饰者设计模式。

Executor是MyBatis执行器,是MyBatis 调度的核心负责SQL语句的生成和查询缓存
CachingExecutor是一个Executor的装饰者,在Executor原有的功能基础上,增加了缓存的功能。此时可以看做是对Executor类的一个增强,即使用了装饰器模式。

3)Spring事务缓存

在 Spring 中,TransactionAwareCacheDecorator 类相当于装饰器模式中的抽象装饰角色,主要用来处理事务缓存,TransactionAwareCacheDecorator 就是对 Cache 的一个包装,即在Cache原有的功能基础上,增加了事务缓存的功能。


六,适配器模式

适配器模式(Adapter Pattern) 将一个接口转换成真正需要的另一个接口,适配器模式使那些接口不兼容的类可以一起工作

1)spring AOP中的适配器模式

我们知道 AOP 的实现是基于代理模式,但是其实 AOP 的advice通知 还使用到了适配器模式,与之相关的适配器接口是AdvisorAdapter 。

我们知道,通知Advice 常用的类型有:BeforeAdvice(前置通知)、AfterAdvice(后置通知)等等。

每个类型Advice(通知)都有对应的方法拦截器:

  • BeforeAdvice——MethodBeforeAdviceInterceptor;
  • AfterAdvice——AfterReturningAdviceInterceptor;

Spring的Advice通知,要通过 适配器,去适配成相应的方法拦截器( MethodInterceptor)。

比如:

  • 前置通知BeforeAdvice—> 前置通知适配器MethodBeforeAdviceAdapter—> 前置通知方法拦截器MethodBeforeAdviceInterceptor

  • 后置通知AfterAdvice—> 后置通知适配器AfterReturningAdviceAdapter—> 后置通知方法拦截器AfterReturningAdviceInterceptor

2)SpringMVC中的适配器模式

在Spring MVC中,DispatcherServlet 根据请求信息,调用 HandlerMapping处理器映射器,去解析请求该对应的 Handler处理器。

解析到对应的 Handler处理器(也就是我们平常说的 xxxController 控制器)之后,开始由HandlerAdapter 处理器适配器 处理。

HandlerAdapter 处理器适配器只是一个接口,真正的是具体的适配器实现类xxxHandlerAdapter,去对相应的 Handler处理器 进行适配

适配成功之后,然后去调用执行Handler处理器里边的业务方法。

3)为什么要在 Spring MVC 中使用适配器模式?

Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。
如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要自行来判断,就像下面这段代码一样:

  1. if(mappedHandler.getHandler() instanceof MultiActionController){
  2. ((MultiActionController)mappedHandler.getHandler()).xxx
  3. }else if(mappedHandler.getHandler() instanceof XXX){
  4. ...
  5. }else if(...){
  6. ...
  7. }

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

七,责任链模式

1)什么是责任链模式:

责任链模式是一种设计模式。在责任链模式里,很多处理器对象由每一个处理器对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个处理器决定处理此请求。

发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任

责任链模式实现了请求者和处理者的解耦。

2)责任链模式的两个角色:

责任链模式涉及到的角色如下所示:

● 抽象处理者角色 :

定义出一个处理请求的接口。接口定义一个方法,用来设定和返回对下一个处理器对象的引用

● 具体处理者角色 :

具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家


3)SpringMVC中的责任链模式的体现:

在 SpringMVC 的 DispatcherServlet 类的doDispatch方法中,构建了 一个 处理器拦截器HandlerInterceptor的执行链

1)HandlerExecutionChain介绍:

在该doDispatch方法中,使用到了HandlerExecutionChain这个类

HandlerExecutionChain这个类维护了一个 HandlerInterceptor处理器拦截器 的集合,我们称之为拦截器执行链chain

HandlerExecutionChain这个类 本身不会执行 处理器拦截器HandlerInterceptor 的逻辑,只是将请求分配给 拦截器执行链chain 里边的 处理器拦截器HandlerInterceptor

2)具体体现在doDispatch()方法中:

详情请见:SpringMVC中的责任链模式

4)Spring中的AOP的责任链模式:

详情见:Spring的AOP中的责任链模式

SpringAOP就是利用动态代理和责任链模式实现的,

当一个切入点(目标方法)有多个通知要织入时,这些需要织入的advice通知就形成了一个责任链。

5)tomcat容器中的Servlet责任链模式;

当一个request请求过来的时候,需要对这个request做一系列的加工,使用责任链模式可以使每个加工组件化,减少耦合。

也可以在一个request过来的时候,需要找到合适的加工方式。当一个加工方式不适合这个request的时候,传递到下一个加工方法,该加工方式再尝试对request加工。

在tomcat中容器之间的调用,使用的就是责任链的设计模式:

当一个请求过来的时候,首先是engine容器接受请求,然后engine容器会把请求传到host容器,host容器又会传到context容器,context容器传到wrapper容器,最后wrapper容器使用适配请求的servlet来处理请求。

tomcat更多学习请参见:

八,模板方法模式

Spring 中 jdbcTemplate、hibernateTemplate 、RedisTemplate 等以 Template 结尾的模板类,它们是就使用到了模板模式。

九,观察者模式

观察者模式它表示的是一个对象与多个对象之间具有依赖关系,当一个对象发生改变的时候,依赖这个对象的其他对象也会做出相应改变。

Spring 事件驱动模型就是观察者模式很经典的一个应用。

Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。


Spring 事件驱动模型中的三种角色

1,事件角色-ApplicationEvent

(org.springframework.context包下的)ApplicationEvent 充当事件的角色,这是一个抽象类,它继承了EventObject并实现了Serializable接口。

Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent 的实现(继承自ApplicationContextEvent):

  • ContextStartedEvent:ApplicationContext 启动后触发的事件;
  • ContextStoppedEvent:ApplicationContext 停止后触发的事件;
  • ContextRefreshedEvent:ApplicationContext 初始化或刷新完成后触发的事件;
  • ContextClosedEvent:ApplicationContext 关闭后触发的事件。

image.png
Spring中用到的设计模式 - 图3

2,事件监听者角色-ApplicationListener

ApplicationListener 充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEvent()方法来处理ApplicationEvent

ApplicationListener接口类源码如下,可以看出接口定义看出接口中的事件只要实现了 ApplicationEvent就可以了。

所以,在 Spring中我们只要实现 ApplicationListener 接口、重写 onApplicationEvent()方法,即可完成监听事件。

  1. package org.springframework.context;
  2. import java.util.EventListener;
  3. @FunctionalInterface
  4. public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
  5. void onApplicationEvent(E var1);
  6. }

3,事件发布者角色-ApplicationEventPublisher

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

  1. @FunctionalInterface
  2. public interface ApplicationEventPublisher {
  3. default void publishEvent(ApplicationEvent event) {
  4. this.publishEvent((Object)event);
  5. }
  6. void publishEvent(Object var1);
  7. }

ApplicationEventPublisher 接口的publishEvent()这个方法在AbstractApplicationContext类中被实现,
阅读这个方法的实现,你会发现实际上事件真正是通过ApplicationEventMulticaster来广播出去的。

4,Spring 的事件流程总结

  1. 定义一个事件: 实现一个继承自 ApplicationEvent,并且写相应的构造函数;
  2. 定义一个事件监听者:实现 ApplicationListener 接口,重写 onApplicationEvent() 方法;
  3. 使用事件发布者发布消息: 可以通过 ApplicationEventPublisher 的 publishEvent() 方法发布消息。