上周一同事面试遇到一个比较有趣的面试题,大意是,我们在项目中,像分布式消息队列,比如 RocketMQ
、 Kafka
等都用得很多了,他们的优点比如 解耦
大家都很熟了,那么,有没有在项目中,用到一些单机的队列,或者事件通知机制?
坦白说,绝大部分同学平时开发,都是CRUD为主,除了分布式队列外,能和队列沾上边的,还是比较少的。可能比较好想到的是,线程池中有 队列
这么一个概念。但是这种生拉硬扯的队列“远方亲戚”可能也不是面试官想听的,肥朝就根据自己的经验,说一下单机的队列,或者事件通知机制的场景。
先简单交代背景,每个公司肯定有一些前后端交互的规范,比如常见的 请求头参数
、 响应json
格式等等,这个很好理解。所以我们会在 spring-boot-starter-web
的基础上,进行二次开发,做一些增强,这里命名为 feichao-boot-starter-web
。(不要问我feichao是什么)
特别声明,我们之所以做二次开发,并不仅仅是为了上述的功能,增强的全部的功能不是本文重点,这里暂不介绍。
但是我们知道,肯定有一些不需要校验请求头的url,比如我们也对Swagger做了一些增强 feichao-boot-starter-swagger
。因此,就需要在 feichao-boot-starter-web
做一些忽略校验的URL。
public class FeiChaoMVCProperties { /** * 忽略校验请求头的url */ private List<String> filter = new ArrayList<>(); } 复制代码
稍微熟悉swagger的原理都知道,比如我们打开swagger的html界面的时候,那肯定要访问你的web项目来进行获取swagger的相关注解参数信息,那么问题来了,这个请求,就明显是不需要校验请求头的URL。也就是说swagger的html界面发起的请求,是不需要做任何请求头的校验处理的。但是我们在 feichao-boot-starter-web
做了请求头的拦截校验处理。
那么对于这个功能,肥朝认为有常见的几个解决办法。
要么 feichao-boot-starter-swagger
依赖 feichao-boot-starter-web
,然后往 FeiChaoMVCProperties
的 filter
里面set进相关的swagger请求URL,从而达到忽略校验的目的。但是这样就会出现一个问题,就是 feichao-boot-starter-swagger
就会依赖 feichao-boot-starter-web
。我们都知道, starter
就相当于一个组件,组件和组件之间就不应该有依赖关系,再说,我们写代码,提倡的不就是低耦合。其实生活也是一样的,舔狗不就是因为过度依赖对方,一直舔,最后一无所有?
当然你可能会说,那我把这个 FeiChaoMVCProperties
放在一个公共的 common.jar
不就行了?当然这个也可以,但是你想 FeiChaoMVCProperties
的字段以及后续维护,肯定是和该 starter-web
相关的,如果放在 common.jar
,和我们面向对象的想法,总还是有些违背。
那么还有一种方案,就是 FeiChaoMVCProperties
还是放在 starter-web
,但是 FeiChaoMVCProperties
的内容,是可以通过配置文件配置的方式来设置值,这样,把swagger的相关URL,放在配置文件不就行了。看似没问题,但是你想啊,我现在用丝袜哥(swagger)你就配置一下url,那万一我后面又多增加一个肥朝哥,难道你配置文件又要配置一下url?
那么这个问题如何解决呢?我们可以利用Spring的事件机制,当然有些同学可能用到的是 Guava
的 EventBus
。但是为了尽量减少依赖,我们就直接用Spring的。
我们在 feichao-boot-starter-web
的模块就可以进行监听
@Component public class FilterUrlListener implements ApplicationListener<UrlFilterEvent>{ @Autowired private SpringMVCProperties springMVCProperties; @Override public void onApplicationEvent(UrlFilterEvent event) { List<String> urls = event.getSource(); springMVCProperties.getFilter().addAll(urls); } } 复制代码
然后 feichao-boot-starter-swagger
进行发送
@Component @Slf4j public class SwaggerFilterUrlEvent implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { applicationContext.publishEvent(new UrlFilterEvent(SwaggerConstant.SWAGGER_URLS)); } } 复制代码
这样,两个组件之间就彻底解耦开来。后续除了丝袜哥(swagger)外,即使增加一个肥朝哥的组件,都不需要改动 feichao-boot-starter-web
的代码。只需要在肥朝哥的组件中,发送一个消息即可。
因为肥朝平常的日常工作主要是基础架构相关的,任何一个耦合的设计,一旦改动起来,影响还是比较大的。当然除了上述场景,还有一些其他场景,比如目前公司做的是海外业务,就会涉及到国际化,那么就会有多个服务会用到 语言
这个东西。怎么把语言这个字段,在多个业务之间传递,就会是个问题。如果你直接在业务的DTO,比如订单DTO加个语言的字段,那么就明显很不友好了。如何做到业务方随时随地都能获取语言,但是语言这个字段又不和具体业务DTO耦合?那么这个肥朝下期再讲,防止一些假粉老是白嫖,每次看完了就取关!!!
关注肥朝公众号的老粉丝想必都知道,肥朝向来不解决某一个问题,对于这个问题的思考,我们到这里就截止了吗?当然不是,比如,我们文中提到的过滤URL。这个过滤URL,我们希望做成类似SpringMVC,或者Tomcat这样,能配置 /*
、 /feichao/*
等形式,那么怎么实现这个需求呢?
其实这个问题,常见的错误做法是,花费大量的时间写一个这样的匹配URL逻辑,甚至恶补一下正则表达式之类的。但是这些做法都有如下缺点:
1.如果你写的这个规则比较蹩脚,业务方的同事需要了解你这个配置的规则,需要一定的学习成本
2.自己写的,bug多多不说,正则这个出问题了,不好debug
3.花费的时间巨大,得不偿失
那么,根据肥朝的愚见,比较合适的做法是怎么样呢?其实这个你直接参考Tomcat或者SpringMVC中的这个功能,看一下他们是怎么实现的,然后把他们的代码拷贝过来,这样,业务方的同学,需要配相关URL的时候,他就根本不需要重新学习,就按照之前SpringMVC的配置习惯来即可,另外,经过SpringMVC、Tomcat等知名项目考验,稳定性没问题,最重要的是,你如果有阅读源码的优秀习惯,找到这段功能代码,基本是半个小时之内,比你自己写个类似逻辑,简直好太多,所以,不要再问我,看源码有没有用了。
当然这个功能,Spirng提供了一个好的工具类做类似的事,你直接使用即可, AntPathMatcher
。什么,你不知道这个工具类,早喊你关注肥朝的公众号你就是不听!
当然对于事件机制,还有很多的使用场景,比如,肥朝要求每个项目配置文件必须设置 appName
,那么我就需要在Spring容器启动的时候,做相应的校验,如果没设置,就不允许项目启动。当然还有今天群里朋友讨论的,一些CRUD记录操作日志的场景,除了 注解+AOP
的方式来记录操作日志,也可以在执行完操作后,发出一个消息,然后在监听消息做相关的日志记录。
当然,相信日常工作内容 丰富多彩
且 极具挑战性
的你,也肯定还有一些其他的使用场景,欢迎留言告诉肥朝。