导航:
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
目录
4、Spring 框架AbstractApplicationContext抽象类
模板模式
1、基本介绍
- 1)模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行
- 2)简单说,模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
- 3)这种类型的设计模式属于行为型模式(用于描述对象之间的通信和责任分配)。
实现方式:抽象类有一个模板方法和其他行为方法,模板方法按流程调用各行为方法(抽象或非抽象);具体子类重写抽象的行为方法。
对原理类图的说明——即模板方法模式的角色和职责
AbstractClass
抽象类中实现了模板方法,定义了算法的骨架,具体子类需要去实现其抽象方法或重写其中方法
ConcreteClass
实现了抽象方法,已完成算法中特定子类的步骤
注意事项和细节
- 1)基本思想:算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 2)实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用
- 3)既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现
- 4)不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,使得系统更加庞大
- 5)一般模板方法都加上final关键字,防止子类重写模板方法
- 6)使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但其个别步骤在实现时可能不同,通常考虑用模板方法模式来处理
2、模板模式解决豆浆制作问题
编写制作豆浆的程序,说明如下:
- 1)制作豆浆的流程选材 ----> 添加配料 ----> 浸泡 ----> 放到豆浆机打碎
- 2)通过添加不同的配料,可以制作出不同口味的豆浆
- 3)选材、浸泡和放到豆浆机打碎这几个步骤是一个模板方法,对于制作每种口味的豆浆都是一样的
- 4)请使用模板方法模式完成
说明:因为模板方法模式比较简单,很容易就想到这个方案,因此就直接使用,不再使用传统的方案来引出模板方法模式
核心代码
/**
* 抽象方法
*/
public abstract class SoyaMilk {
/**
* 模板方法,定义为final禁止覆写
*/
public final void make() {
System.out.println(">>>>>>豆浆制作开始<<<<<<");
useSoyBean();
addIngredients();
soak();
mash();
System.out.println(">>>>>>豆浆制作结束<<<<<<");
}
// 同包可见、对其他包下的子类可见。
protected void useSoyBean() {
System.out.println("Step1. 选用上好的黄豆.");
}
//添加原材料是抽象方法,因为不同豆浆原材料不一样
protected abstract void addIngredients();
protected void soak() {
System.out.println("Step3. 对黄豆和配料进行水洗浸泡.");
}
protected void mash() {
System.out.println("Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.");
}
}
/**
* 花生豆浆
*/
public class PeanutSoyaMilk extends SoyaMilk {
public PeanutSoyaMilk() {
System.out.println("============花生豆浆============");
}
@Override
protected void addIngredients() {
System.out.println("Step2. 加入上好的花生.");
}
}
/**
* 红豆豆浆
*/
public class RedBeanSoyaMilk extends SoyaMilk {
public RedBeanSoyaMilk() {
System.out.println("============红豆豆浆============");
}
@Override
protected void addIngredients() {
System.out.println("Step2. 加入上好的红豆.");
}
}
/**
* 芝麻豆浆
*/
public class SesameSoyaMilk extends SoyaMilk {
public SesameSoyaMilk() {
System.out.println("============芝麻豆浆============");
}
@Override
protected void addIngredients() {
System.out.println("Step2. 加入上好的芝麻.");
}
}
客户端调用模板方法
SoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
SoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
SoyaMilk sesameSoyaMilk = new SesameSoyaMilk();
sesameSoyaMilk.make();
/*
============花生豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step2. 加入上好的花生.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
============红豆豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step2. 加入上好的红豆.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
============芝麻豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step2. 加入上好的芝麻.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
*/
3、钩子方法
在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”
还是用上面做豆浆的例子来讲解,比如,我们还希望制作纯豆浆,不添加任何的配料,请使用钩子方法对前面的模板方法进行改造
抽象类和具体类
//抽象模板类
public abstract class SoyaMilk {
//模板方法
public final void make() {
// ...
//如果钩子方法决定加配料,就加配料;否则不执行加配料操作。
if (customAddIngredients()) {
addIngredients();
}
// ...
}
//钩子方法,决定是否需要添加配料。默认情况是加配料。
boolean customAddIngredients() {
return true;
}
// ...
}
/**
* 纯豆浆
*/
public class PureSoyaMilk extends SoyaMilk {
public PureSoyaMilk() {
System.out.println("============纯豆浆============");
}
@Override
protected void addIngredients() {
// 空实现即可
}
@Override
protected Boolean customAddIngredients() {
return false;
}
}
客户端,测试钩子方法
SoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
/*
============纯豆浆============
>>>>>>豆浆制作开始<<<<<<
Step1. 选用上好的黄豆.
Step3. 对黄豆和配料进行水洗浸泡.
Step4. 将充分浸泡过的黄豆和配料放入豆浆机中,开始打豆浆.
>>>>>>豆浆制作结束<<<<<<
*/
4、Spring 框架AbstractApplicationContext抽象类
AbstractApplicationContext.java中有一个refresh()方法就是模板方法,它用于根据流程调用aop代理创建、bean生命周期初始化、属性注入等启动并初始化Spring应用上下文的方法。
AbstractApplicationContext抽象类是ApplicationContext接口的一种默认实现,提供了一些通用的应用上下文功能,同时也为其他具体的应用上下文实现类提供了一些可扩展的方法。
应用上下文:负责管理各种bean以及它们之间的关系,并对它们进行生命周期的管理。
// 模板方法
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory); // 钩子方法
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh(); // 钩子方法
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
}
}
}
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory(); // 抽象方法
ConfigurableListableBeanFactory beanFactory = getBeanFactory(); // 抽象方法
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
5、JUC包下的AQS抽象队列同步器
AQS是基于模板方法模式进行设计的。锁的实现类需要继承AQS并重写它指定的方法。
AQS的模板方法将“管理同步状态的逻辑”提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。
AQS(AbstractQueuedSynchronizer抽象队列同步器):
AQS是实现锁或者其他同步器(用于协调线程之间对共享资源的访问)的核心框架,通过内部的状态变量和队列来实现线程的同步。ReentrantLock,ThreadPoolExecutor,CountDownLatch等都是基于AQS实现。
- 同步状态:在AQS中,volatile类型的state变量表示锁的状态,通过CAS原子操作这个状态变量来保证线程安全。
- 同步队列:在AQS中,FIFO(先入先出)队列用来管理等待锁的线程,队列每个节点记录等待线程的状态以及等待锁的条件。先入先出确保同步器的公平性,也就是先等待的线程先获得锁。
实现线程同步的原理:线程通过CAS原子性修改state变量,修改成功则获得锁,失败则插入队尾等待。
基于模板方法:AQS是基于模板方法模式进行设计的。锁的实现类需要继承AQS并重写它指定的方法。
AQS的模板方法将“管理同步状态的逻辑”提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。
6、应用场景:AQS
AQS是基于模板方法模式进行设计的。锁的实现类需要继承AQS并重写它指定的方法。
AQS的模板方法将“管理同步状态的逻辑”提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。
AQS(AbstractQueuedSynchronizer抽象队列同步器):
AQS是一个抽象类,在JUC.locks包下,它通过内部的状态变量和同步队列来实现线程的同步(允许多个线程协作共享访问共享资源)。很多锁和同步器都是基于AQS实现的。ReentrantLock,ThreadPoolExecutor,CountDownLatch等都是基于AQS实现。
- ReentrantLock:可重入锁,同一个线程在持有锁的情况下,可以重复地获取该锁,无需等待,只需记录重入次数。能防止死锁,因为不用线程自己等待自己释放锁。是Lock接口的实现类。可以通过构造参数true或false指定公平锁或非公平锁,可以通过newCondition()方法创建多个Condition对象分组唤醒等待线程。
- 公平锁:按加锁顺序获取锁。线程竞争锁时判断AQS队列里有没有等待线程,有就加入队尾。
- 非公平锁(默认):可能某个线程会不断获取锁,牺牲公平的情况下提高了效率。不管AQS队列里有没有等待线程,都会先尝试获取锁;如果抢占不到,再加入队尾。如果线程刚好在上个线程释放时拿到锁,就不用像公平锁那样还要阻塞等待、放队尾、唤醒,这些操作涉及到对内核的切换,对性能有影响。
- Condition对象:用于线程间通信,通过await()和signal(),signalAll()让线程等待或唤醒。通常用lock锁创建Condition对象,即lock.newCondition();
- CountDownLatch:计数器,它允许一个或多个线程等待其他线程完成操作后再执行。countDown()方法让计数器减一,await()方法阻塞当前线程直到计数器减为0。
- ThreadPoolExecutor。
state变量和等待队列:
- state变量:在AQS中,volatile类型的state变量表示锁的状态,通过CAS原子操作这个状态变量来保证线程安全。初始是0,代表没拿到锁,1代表拿到锁。
- 同步队列:在AQS中,FIFO(先入先出)队列用来管理等待锁的线程,队列每个节点记录等待锁的线程的地址、状态、等待锁的条件。先入先出确保同步器的公平性,也就是先等待的线程先获得锁。队列底层是双向链表。
实现线程同步的原理:线程通过CAS原子性修改state变量,修改成功则获得锁,失败则插入队尾等待。
基于模板方法:AQS是基于模板方法模式进行设计的。锁的实现类需要继承AQS并重写它指定的方法。
AQS的模板方法将“管理同步状态的逻辑”提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。
模板方法:抽象类有一个模板方法和其他行为方法,模板方法按流程调用各行为方法(抽象或非抽象);具体子类重写抽象的行为方法。