阅读 506

【肥朝】从一道真实的面试题,聊聊事件机制

前言

上周一同事面试遇到一个比较有趣的面试题,大意是,我们在项目中,像分布式消息队列,比如RocketMQKafka等都用得很多了,他们的优点比如解耦大家都很熟了,那么,有没有在项目中,用到一些单机的队列,或者事件通知机制?

坦白说,绝大部分同学平时开发,都是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做了请求头的拦截校验处理。

那么对于这个功能,肥朝认为有常见的几个解决办法。

常见的问题解决思路分析

1.依赖api

要么feichao-boot-starter-swagger依赖feichao-boot-starter-web,然后往FeiChaoMVCPropertiesfilter里面set进相关的swagger请求URL,从而达到忽略校验的目的。但是这样就会出现一个问题,就是feichao-boot-starter-swagger就会依赖feichao-boot-starter-web。我们都知道,starter就相当于一个组件,组件和组件之间就不应该有依赖关系,再说,我们写代码,提倡的不就是低耦合。其实生活也是一样的,舔狗不就是因为过度依赖对方,一直舔,最后一无所有?

当然你可能会说,那我把这个FeiChaoMVCProperties放在一个公共的common.jar不就行了?当然这个也可以,但是你想FeiChaoMVCProperties的字段以及后续维护,肯定是和该starter-web相关的,如果放在common.jar,和我们面向对象的想法,总还是有些违背。

2.配置

那么还有一种方案,就是FeiChaoMVCProperties还是放在starter-web,但是FeiChaoMVCProperties的内容,是可以通过配置文件配置的方式来设置值,这样,把swagger的相关URL,放在配置文件不就行了。看似没问题,但是你想啊,我现在用丝袜哥(swagger)你就配置一下url,那万一我后面又多增加一个肥朝哥,难道你配置文件又要配置一下url?

如何解决遇到的问题?

那么这个问题如何解决呢?我们可以利用Spring的事件机制,当然有些同学可能用到的是GuavaEventBus。但是为了尽量减少依赖,我们就直接用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的方式来记录操作日志,也可以在执行完操作后,发出一个消息,然后在监听消息做相关的日志记录。

当然,相信日常工作内容丰富多彩极具挑战性的你,也肯定还有一些其他的使用场景,欢迎留言告诉肥朝。



关注下面的标签,发现更多相似文章
评论