这个问题我做面试官的时候也经常喜欢问,考察一下候选人的代码能力和设计思维,在我校招的年代,一般来说面试官只是问一问说出几种常见的设计模式八股文就算过关了。就像当时我面试最怕别人问我说一下你项目中用到了哪些设计模式,一问一个不吱声,根本说不出来。。。。因为我都是背的概念,哪知道代码里面怎么实现的。
Java内卷到现在的已经并不是背背八股文就可以的时代了,不光要问你概念是啥,还要问你代码里怎么用的,为什么这么用,解决了什么问题。这时候如果你只回答出Spring中的那两个设计模式,beanFactory工厂设计模式 、aop代理模式。这俩显然大家都能答出来,那为什么面试官选择你而不选择其他人呢。
今天给大家介绍一种设计模式,学会之后让你平滑的吹到自己的经历上。
模版设计模式与钩子函数
什么是模版设计模式和钩子函数,允许子类对父类的一个或多个步骤进行重写 。用父类实现通用的逻辑,由子类实现不同的交互逻辑。举个例子,看过Spring源码的同学或者熟读背诵Spring八股文的肯定都对Refresh方法不陌生
scss
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}
这个方法是SpringBean生命周期里最重要的一环,包含了加载bean的各种前置器、后置器以及完成beanDefinition的加载。这块的八股没背熟的可以再背一背。
我们这里只是借助refersh来抛砖引玉一下,refresh方法是在Spring启动时调用,单单分析这个方法,refersh方法只是定义了一个bean加载的流程。具体的实现方法由子类去实现。也就是相当于这里定义了一个模板方法+钩子函数。
代码应用
应用在我们的代码设计中如何实现呢,假设要你设计一套数据流转系统,功能是负责异构数据源之间的稳定同步,比如每天定时从爬虫组的MySQL1库中同步到数仓组的MySQL2库中做清洗计算。
那么任务执行如何去定义?首先一个任务我们首先可以想到的是两种状态,任务开始、任务结束,那么先定义出两个方法
- begin();
- end();
这样我们可以通过调用begin来开始任务,调用end来结束任务,任务不是空手得来的吧,在begin之前是不是要做一些前置操作,获取任务详情之类的。所以增加一个beforeExec();那么结束之后是不是要对任务做一下结果判断,再加一个afterExec()。
- begin();
- end();
- beforeExec();
- afterExec();
那么任务进行中耗费了太多资源了,想取消终端掉是不是也要有个方法呢。再加一个cancel()。
- begin();
- end();
- beforeExec();
- afterExec();
- cancel();
这样一个任务的生命周期钩子函数就定义好了,熟悉vue的可能不陌生,vue里的生命周期函数跟这个差不多意思。定义好了钩子函数再定义好模板方法。
所谓模版方法就是在一定义好一套流程,我在父类的一个方法中把任务触发的流程指定好,具体怎么执行让子类去具体实现。
scss
public abstract class JobAction {
public void execute() {
// 获取任务详情
Job job = beforeExec();
while (// 监控任务状态) {
try {
// 开始任务
begin(job);
} catch(Exception e) {
// 任务触发后钩子
afterExec(job);
}
if (// 取消任务) {
cancel(job);
}
}
// 任务结束
end();
}
}
具体的实现逻辑由子类去具体实现。完整的项目代码在Gitee:
这是作者搬了近四年砖头积累的下来的经验开源的项目,基于flink的异构数据源流转服务,过一遍代码绝对不会让你失望。拯救简历上只有商城、外卖的JavaBoy!