JAVA中间件(middleware)模式
主要围绕下面几点介绍
-
概念
-
应用场景(对比其他语言)
-
和filter,拦截器,传统的pipeline模式的区别(用forEach代替不了么?)
-
在java中如何实现中间件模式(附源码)
中间件的概念
首先它是一种设计模式,一种功能的封装方式,就是封装在程序中处理复杂业务逻辑的功能。
说概念很难理解,结合应用场景比较好说明
比如在http请求中往往会涉及很多动作, IP筛选, 查询字符串传递, 请求体解析, cookie信息处理, 权限校验, 日志记录 ,会话管理中间件(session), gzip压缩中间件(如compress) ,错误处理等
aspnetcore中处理http请求的也是用的中间件模式,
微软官方文档介绍:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0
nodejs中封装以上http请求细节处理的方法里面用到的就是中间件模式。比如主流的nodejs框架 express ,koa 等
以koa框架为例 middleware的构成是用use方法添加, 下面的代码中添加了一个 日志中间件,一个在response的header中添加'X-Response-Time'(记录请求处理时长)的中间件
const Koa = require('koa'); const app = new Koa(); // logger app.use(async (ctx, next) => { await next();//进入下一个中间件 const rt = ctx.response.get('X-Response-Time'); //记录日志 console.log(`${ctx.method} ${ctx.url} - ${rt}`); }); // x-response-time app.use(async (ctx, next) => { const start = Date.now(); await next();//进入下一个中间件 const ms = Date.now() - start; //记录请求时长 ctx.set('X-Response-Time', `${ms}ms`); }); // response app.use(async ctx => { ctx.body = 'Hello World'; }); app.listen(3000);
理解下意思:
-
程序启动监听了并提供3000端口提供web服务
-
你在浏览器输入http://localhost:3000并回车
-
首先进入第一个logger中间件
-
logger中间件首先调用了next() 进入了下一个 x-response-time中间件
-
记录当前的时间 调用next() 进入了 reponse中间件,返回 helloword
-
回到x-response-time中间件记录时长
-
回到logger中间件记录日志
-
请求结束
仔细注意看:
中间件有一个next()函数,如果不调用next函数,请求就在这个中间件中终止了
这点也是中间件相比filter或者传统的pipeline对比最大的不同点(控制权交给了中间件本身)
filter的话 一旦开始走过滤器逻辑,要么全部走完
//java伪代码
for (Filter filter : myFilters){
filter.excute(xx)
}
//要么是在组织调用的地方加判断
for (Filter filter : myFilters){
bool continueRun = filter.excute(xx)
if(!continueRun) break;
}
每个filter都是独立执行的,在流程上无法过多干预,没有中间件模式灵活:
用极少的操作就能得到一个插件,用最简单的方法就能将新的过滤器和处理程序扩展到现有的系统上。
中间件模式运行示意图:
有点类似于【娃娃里面的娃娃】
在java中如何实现中间件模式
中间件模式中,最基础的组成部分就是 中间件管理器,我们可以用它来组织和执行中间件的函数,如图所示:
要实现中间件模式
-
可以通过use()函数来注册新的中间件
-
每个中间件的调用参数都是一致的
-
每个中间件按照注册的顺序被依次调用
-
每个中间件都有权停止进一步处理
下面我们动手封装下
我们定义一个中间件函数式接口
@FunctionalInterface public interface Middleware { void excute(MiddlewareContext ctx, MiddlewareNext next); }
有2个参数
-
中间件执行的上下文
-
下一个中间件执行委托
/** * 中间件方法的参数 * 上下文 */ public class MiddlewareContext { private final Map<String,Object> contextMapObjectCache; public MiddlewareContext() { contextMapObjectCache = new HashMap<>(); } public <T> void set(String key,T value) { contextMapObjectCache.put(key,value); } public <T> T get(String key){ if(contextMapObjectCache.containsKey(key)){ return (T)contextMapObjectCache.get(key); } return null; } }
//下一个中间件委托
@FunctionalInterface public interface MiddlewareNext { void excute(); }
中间件管理器
/** * 中间件管理器 */ public class MiddlewareBuilder { private final List<Middleware> middlewareList; public MiddlewareBuilder() { this.middlewareList = new ArrayList<>(); } public MiddlewareBuilder use(Middleware middleware) { this.middlewareList.add(middleware); return this; } public Consumer<MiddlewareContext> build(){ MiddlewarePipeline pipeline = null; Collections.reverse(middlewareList); for (Middleware middleware : middlewareList) { pipeline = pipeline == null ? new MiddlewarePipeline(middleware) : pipeline.addHandler(middleware); } return pipeline::excute; } private static class MiddlewarePipeline { private final Middleware currentHandler; public MiddlewarePipeline(Middleware currentHandler) { this.currentHandler = currentHandler; } public MiddlewarePipeline addHandler(Middleware newHandler) { return new MiddlewarePipeline((input, next1) -> { MiddlewareNext next2 = () -> currentHandler.excute(input, next1); newHandler.excute(input, next2); }); } public void excute(MiddlewareContext ctx){ this.currentHandler.excute(ctx,()->{}); } } }
以上总共代码不超过60行实现了java中间件模式的封装,
对比nodejs或者c#中使用方式基本保持一致,代码高度简洁。
使用中间件
//创建一个中间件构造器 MiddlewareBuilder app = new MiddlewareBuilder(); //添加中间件 app.use((ctx, next) -> { System.out.println("middle-1--->start"); next.excute(); //进入下一个中间件 System.out.println("middle-1--->end"); }); //添加中间件 app.use((ctx, next) -> { System.out.println("middle-2--->start"); long startTime = System.currentTimeMillis(); next.excute(); //进入下一个中间件 long rt = (System.currentTimeMillis() - startTime); ctx.set("X-Response-Time", rt); System.out.println("middle-2--->end"); }); //添加中间件 app.use((ctx, next) -> { System.out.println("middle-3--->start"); ctx.set("body", "Hello World"); System.out.println("middle-3--->end"); }); //执行中间件 app.build().accept(new MiddlewareContext());
执行结果,符合预期:
middle-1--->start
middle-2--->start
middle-3--->start
middle-3--->end
middle-2--->end
middle-1--->end
在实际项目中还可以进一步结合spring的特性来封装一个Middleware注解
新增一个spring后置处理器
@Component public class MiddlewarePointAnnotationProcessor implements BeanPostProcessor { private final ConfigurableListableBeanFactory configurableBeanFactory; @Autowired public MiddlewarePointAnnotationProcessor(ConfigurableListableBeanFactory beanFactory) { this.configurableBeanFactory = beanFactory; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { this.scanAnnotation(bean, beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } protected void scanAnnotation(Object bean, String beanName) { this.configureFieldInjection(bean); } private void configureFieldInjection(Object bean) { Class<?> managedBeanClass = bean.getClass(); ReflectionUtils.FieldCallback fieldCallback = new MiddlewarePointFieldCallback(configurableBeanFactory, bean); ReflectionUtils.doWithFields(managedBeanClass, fieldCallback); } }
在springboot中使用的时候就更加方便了如下图:
我是正东,学的越多不知道也越多。这个公众号是我的实验小天地,我会分享一些我开源的工具(欢迎你来提意见),好玩好用的新技术。如果你也和我一样喜欢折腾技术请关注 !
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,转载文章之后须在文章页面明显位置给出作者和原文连接,谢谢。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?