Netty:我的小故事

我的韩国欧巴,让我从 JDK 的混杂的环境中诞生。workaround 过不予修复“陷阱(BUG,BUG..)”默认失败地,跳过不可使用的 IP_TOS 选项,放弃 AIO 这个鸡肋。这一切让我变得如此与众不同,要说特别之处,我行异步,事件驱动之道;内有性能加持,外有呼风唤雨之术法;以三大形态互换,三大法相容相生,三几数笔达万丈之渊。现在的互联网,非复吴下阿蒙,一变不知样。以前尚且可以 CRUDER,现在只能去学习我,然后用上 Spring WebFlux 再与微服务搞上。此外,还得我来搞个前所未有的话术,提提性能冲点业绩,换点银子讨好老婆,养养儿子,爽歪歪(欧巴,我**你) 。在爽之余,要知道有我曾经有孪生兄弟他叫 5阿发,不过早早出家不问世事了(我会继承你的遗志的)。。。
那,我们就从 Echo 与 Http 开始,入门 Netty 吧! 首先,我的韩国欧巴把我放在 https://netty.io 上,如果要见我那你得去下载页面找到我,然后把我配置到 项目 中。不然你用 Maven 帮助你把我从另外一个地方出来帮你也行,别忘了我的版本号是3个点。 二话不说,先来个我需要的几个小家伙的介绍: 如果要搞个自定义协议的服务器端,要知道这几个 Bigman ,大人物 skr,skr,skr。
  • EventLoopGroup:任务分组,最多可以使用两组(是的,你没看错)
  • ServerBootstrap:Server 运作组织类,指挥部
  • SocketChannel:工作频道
  • ChannelHandler/ChannelInboundHandler:提供各种事件处理
ServerBootstrap 需要指定什么呢?
  • group():指定 事件循环 组
  • channel():指定 SocketChannel 类
  • handler:也是指定事件(对于 第一个 EventLoopGroup)
  • childHandler:指定事件处理
  • option():链接选项
  • childOption():链接选项
来看一段代码就明白了:
package com.codimiracle.practice;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class EchoServer {
private int port;

private EchoServer(int port) {
this.port = port;
}

public static void main(String[] args) {
EchoServer server = new EchoServer(3400);
server.start();
}

private void start() {
NioEventLoopGroup majorEventLoopGroup = new NioEventLoopGroup();
NioEventLoopGroup seniorEventLoopGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
try {
serverBootstrap
.group(majorEventLoopGroup, seniorEventLoopGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new LoggingHandler(LogLevel.INFO))
.addLast(new EchoService());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture channelFuture = serverBootstrap.bind(this.port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
majorEventLoopGroup.shutdownGracefully();
seniorEventLoopGroup.shutdownGracefully();
}
}
}
Handler 代码:
package com.codimiracle.practice;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class EchoService extends ChannelInboundHandlerAdapter {

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 内部缓存写入
ctx.write(msg);
// 把缓存发出
ctx.flush();
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
  • EventLoopGroup:任务分组,最多一个
  • Bootstrap:连长
  • SocketChannel:是的还是它
  • ChannelHandler/ChannelInboundHandler:没错,也是它
如果要搞个自定义的客户端,需要知道这几个 小东西。 就换了指挥的人。
package com.codimiracle.practice;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class EchoClient {
private String host;
private int port;

public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void connect() {
NioEventLoopGroup clientEventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(clientEventLoopGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new LoggingHandler(LogLevel.INFO))
.addLast(new EchoServiceProxy());
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
clientEventLoopGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
EchoClient client = new EchoClient("localhost", 3400);
client.connect();
}
}
Client Handler 代码:
package com.codimiracle.practice;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class EchoServiceProxy extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.wrappedBuffer("Hello world\n".getBytes()));
}
}

我的三个模式

  • 排队打饭(排队模式 OIO/BIO,后来放弃了)
  • 点单自领(NIO模式)
  • 包厢等吃(AIO模式,后来移除了)
要更换模式,只需要 把 EventLoopGroup,ServerSocketChannel 的对象换成对应前缀的对象即可(如 OioEventLoopGroup,OioSocketChannel/OioServerSocketChannel) NIO 实现: EventLoopGroup:如果通过源码追溯进去,实际上是一个特殊化的 ExecutorService(线程池),特殊在那里呢? EventLoop:看一看代码,可以发现 EventLoop 处理了选择器当前(selectNow() 方法)的选择策略 SocketChannel:对应的事件频道

我的三种 Reactor

Reactor更像是一种事件的派发机制,但这些派发机制与响应的 IO 模式相关联。
  • ThreadPreConnection:使用 new NioEventLoopGroup(1) 创建,即可使用单 Reactor 单线程模式
  • Reactor:使用 new NioEventLoopGroup() 创建,即可使用单 Reactor 多线程模式
  • Proactor:使用两个 NioEventLoopGroup() 对象,即可创建主从 Reactor 多线程模式

Spring MVC:DispatcherServlet

在 Spring MVC 中,有一个类是必定要去了解和熟悉的,这个类就是 DispatcherServlet,该类负责了 MVC 中最主要的内容——请求转发处理。 在本文中,我们会在源码级别讲述 DispatcherServlet 的运行机制和高度灵活的扩展性。

DispatcherServlet 的处理过程

DispatcherServlet 的处理过程更像是一个循环执行接口程序的过程,只是有几个相对独立的部分。如果使用 IDE 进行断点你会发现它的流程如下:
  1. doService
    1. 解析请求属性
  2. doDispatch
    1. getHandler( 请求到 Handler 对象映射 )
    2. getHanderAdapter
    3. preHandle
    4. handle
    5. postHandle
    6. processDispatchResult
      1. 完成异常解析
      2. render
        1. 区域处理
        2. 视图名解析
        3. 视图呈现
    7. afterCompletion

DispatcherServlet 的功能

DispatcherServlet 运行过程有以下几个大的功能板块:
  • 基于 JavaBeans 的方式进行配置
  • 控制 Handler 对象路由:HandlerMapping
  • Handler 适配处理:HandlerAdapter
  • 异常处理策略
  • 视图解析策略
  • 请求视图名转译
  • 复合表单内容处理
  • 区域解析
  • 主题解析
Java Beans 方式配置是 Java EE 中的一个规范,其定义 Java Beans 支持属性,事件和方法。如果你对 Java Beans 还不够熟悉可以查看 Oracle 对 Java Beans 是怎样编写的来了解具体的信息
HandlerMapping 负责了请求的路由,也就是说在接收到请求后,请求会经过它的处理最后返回 Handler 对象(可以是任意的类型)。其运行机制由两个组件组成:HandlerInterceptor 和 HandlerExecutionChain。HandlerInterceptor 有三个主要方法:preHandle(),postHandle(),afterCompletion()。
  • preHandle() 方法在 HandlerAdapter 发起调用 Handler 时触发。如果返回 true 表示会处理下一个 Interceptor,否则会认为以及处理完请求。
  • postHandle() 方法在 HandlerAdapter 调用 Handler 并且在 呈现视图 之前调用
  • afterCompletion() 仅在 preHandle() 方法返回 true 并且呈现视图之后执行
HandlerExcutionChain 类真正地组织 HandlerInterceptor 和 Handler 对象。并且真正在调用preHandle(),postHandle(),afterCompletion()。最后这个对象在 HandlerMapping 中 getHandler() 进行返回。
HandlerAdapter (适配器模式)负责完成 Handler 的实际处理。在 HandlerMapping 的处理结果的基础上,HandlerAdapter 表达了如何运作 Handler 对象,如 SimpleControllerHandlerAdapter 是针对 Controller 接口调用 Handler 对象的 handleRequest() 方法完成处理的。
异常处理由 HandlerExceptionResolver 负责,在流程 3-7 步中,如果抛出了异常会经由 processDispatchResult() 进行判断最后交由 processHandlerException() 进行检查异常解析结果,循环判断(最后一个
HandlerExceptionResolver 为第一个判断)有一个可以处理(判断 ModelAndView 返回值不为空)那么就会认为处理完成并把 ModelAndVIew 进行返回,间接使用 视图解析 的执行路径 。
视图解析,由 ViewResolver 负责,ViewResolver 会通过 viewName 获取到相应的 View 对象最后完成 ModelAndView 的呈现工作。与上面的做法先类似,都是使用循环的方式进行解析,一旦解析成功直接返回。 processDispatchResult() 负责调用 render() 方法呈现相应的页面结果。在 render() 方法中,地区解析是在这里完成的,它可以使用 Accept-Language 请求头来给 response 对象设置区域(Content-Language)响应头。最后通过 resolveViewName 获取到交由 View 对象进行实际的呈现工作。
请求视图名转译,通过请求获取相应的视图名,最后经过 ViewResolver 解析出相应的 View 对象,默认的转译类(DefaultRequestToViewNameTranslator)是把请求路径和资源名返回,如 http://localhost/a/b/c.html 将会返回 a/b/c

DispatchServlet 应用

  1. 自定义 HandlerIntercepter ,可以完成与 Handler 进行绑定的拦截处理,而不需要 AOP 编程
  2. 自定义 HandlerAdapter,可以实现自定义的 Handler 如实现一套不同的控制器处理逻辑
  3. 自定义 ViewResovler 视图解析,实现定制的 视图 呈现方式
  4. 自定义 HandlerExceptionResolver 异常处理,

毕业回顾

回想已经经过的时间,总是让人着迷和反思。每个选择的节点依然清晰无比,却又那么的扎心。每个人或许都觉得之前能够选择得更好,不过我至少现在的我看来这一切都是固定的。现在的你觉得之前可以做得更好是因为你得到了成长,那么接下来会是什么等待自己,重蹈覆辙还是踏出与之前完全不同的一步呢?
自出生尔来,每个人的际遇不同从而塑造了不一样的人格和性格。对于事物和道理的意识到和行动到,两者之间永远有差距。我于之前环境和周遭所赋予我的,暂且只是记录和要求自己改变,受现实的曲折,行赤子之心,结果云云。改变和成长是多么有趣的词语,让人生的体验更为完好,想改变吗?想,如同孤独形影相随。《那年那兔那些事儿》告诉我,理想从现在开始就会早一步实现。那么如何改变?以同理之心,上善若水,心诚而明理,不妄自菲薄,亦不增长自负之心。《认知的尺度》引用“最难的事情是认清自己”,告诉我了解了自己是最有意思的事情啊。 一时迷失,一时失去,一时怨恨,患得患失。这样的人又如何担起大任来呢?却也承受精神之涣散,肉体之酸痛,明力之未逮,失道以无能。矛盾啊,我本是平凡之人,却强迫自己如神仙下凡一般,动指而移山,挥手而洒雨,飞天而畅游,遁地如游戏。其种种,不如相信积累的力量,要说结果,总得畜够结果的养分啊。

计划篇

回顾所学,自我 Debug 之余,我想一个败笔就是没有好好地把写过的东西进行汇总和整理。现我有一点空余,不如整理、反思和改进。maven的 jira 账号已经注册了太久了是时候发起 issue 了。

Java Web 整理

概念: contract:指以特定的对象来实现特定的重复,这个重复具备稳定的约束。 包: basic-contract:基础约束,这个约束处理的是 controller 中一些重复使用的部分。(即将发布 Jar 包到中央仓库)
mybatis-contract:mybatis 约束,定制了自己常用的 关于 mybatis 的约束,如分页器的复用,Mapper 一些常常出现的方法。
content-middleware:企图把 内容作为一个单独的模块抽象出来。

Spring Boot:AbstractMethodError

场景

这个异常由虚拟机在运行时抛出。其原因是程序调用了抽象方法,这说明有一些方法没有被实现。在 Java 编译器编译时,会告知类的抽象方法没有实现。一些可能的情况如下:
  • 使用了版本不正确的包(新版本提供了额外的抽象方法)

实际例子

在使用 Jadira Usertype Core 时,在 Spring Boot 2.1.x 与 JPA、Jadira 3.2.0.GA、Joda Money 0.10.0 配合下,出现了 AbstractMethodError 。

解决过程

开始发现这个问题后,我呆滞了半响。花费很多的时间寻求问题无果后,实施排除法。 在排除之前,我先使用官方的 JPA 例子运行一个 JPA 程序,结果发现这个例子能够正确地运行。这说明Spring Boot JPA 本身没有什么问题。加入 自行编写部分 如实体类与 Repository 以及 POM 依赖,执行出现 AbstractMethodError 异常。 逐步减少自行编写部分,在减少 POM 中依赖时发现这个问题发生的原因:Jadira Usertype 使用了不正确的版本。更换更新的的版本后解决了问题。

参考

Oracle Java SE Docs:
https://docs.oracle.com/javase/8/docs/api/java/lang/AbstractMethodError.html

给予帮助何须问这里问那里

纠结之人莫纠结。无意义的纠结不会给你带来任何有意义的结果。

《我给自己写的名言》——codimiralce

为啥要问来问去?

问来问去,对于一些重要的决策而言,的确是需要的。但对于举手之劳而言,又那里需要问来问去,就像慈善机构募捐赈灾过程中,不断问捐款的人会不会捐款一样,多此一举,毫无必要,人家都来捐款了还要你问捐不捐款。。。。这样问来问去不过是对能够判断的事情极度不自信,极度消极,不清楚自己的位置,既毫无作为,又毫无担当的做法而已,总是在心里问我这么做真的好吗?说得有多么善解人意,多么征求他人的见解。实际上,这不过是另外一种披上了“我征求过别人同意才做,大家都好。”的华丽的个人正确的外衣的逃避罢了。

我该这么做吗?

在你这么想的时候,可能你已经想着要做了。如果公正客观来说的确是好事不妨去做啊?想什么乱七八糟的事情,八竿子打不着。这时候就应该问自己这么做会给自己和他人带来痛苦吗?想做的事情多数并不会给自己和他人带来任何痛苦,不过是有抵触心里而已。这比起精神上的痛苦有什么可比的呢?倘若你没做这件事情,你会感到无力的痛苦,感到无动于衷的失败感,落魄感。那你还不如做了这件事情。至少这让你减少消极感。

这么做好吗?

好个**,先回到开头,问这个不过是重蹈覆辙。事实上,自己无法确定,但可以确定的是——每个人做一件事是通过别人评价来确定做的好不好的。但这样是否是好呢?不该是听从内心吗?自己的内心告诉你,你可以这么做,为何要呆呆傻傻的坐着站着不动,仅仅是怕引发后续的事情,达不到别人的期盼吗?到这里还是傻傻的,不愿意做,不会做,拒绝不就好了吗?搞什么,拒绝多不好意思,毕竟是认识的,哪里帮助过自己。这还是傻,你都没办法做,不拒绝干啥子飞机呀,这只会给别人添麻烦而已嘛。反观过来,如果这件事情对于自己,对于他人没有什么坏处,为什么不做呢?不知道有什么值得纠结来纠结去的,为了征求他人同意你这么做吗?这么做除了不愿承担结果还有什么。不要给自己找“我只是想要确定好,再做而已,这样不会有问题。”的借口,明明错误只能避免,不管这么个修补法都会有不可磨灭的印记。

不要问那么多

不要问那么多,问自己会不会因此痛苦。许多的事情根本不需要问这问那,往往迈开步伐,行动就好。2019/9/17 这天的珠海,傍晚的时候下了一场雨,我的两位朋友在食堂等着雨停。在我回到宿舍后,我问她们需不需送伞,在日常生活中的这明明不过是常见的举手之劳而已。可能是觉得我刚刚回到宿舍不好让我送伞,也许是当时雨水也蛮大的所以也就说先等雨变小。 结果雨水变小,我却没有去送伞,让一位好友淋雨去拿伞,自己心情也不好。何必呢?今日看来,回想错过友人的生日,我却也这么问需不需补偿,作为朋友当然会不好意思呀。今日之言,莫忘,谨记。

第一期 编程我建议你定好概念

为什么要注意概念

编程本身是一个思维活动过程,思维的清晰程度体现在每个编程中创建或主观确定的概念上,思路清晰要求我们确立的概念是清楚明了的。每一个概念都是必须清楚而明确的,如果没有明确的概念,就无法让代码产生真正的力量。知识的作用在于其清楚明白,而不在于其“熵增”。在不断的概念定义后,软件便慢慢的集中了庞大的知识体系,这个知识体系慢慢地成为领域语言。这个领域语言的完善程度越高,意味着软件实现越稳定。邻域语言的合理程度越高,意味着软件实现越正确。 这些概念的作用体现在哪里呢?首先,顶层概念决定了编码的方式。例如,在使用 MVC 模式时,编码的方式就变成了先写一下 V,再写一下 C,最后再写一下M。实际上MVC实质上与分层区别不了多少,分层会引入相应的层次概念。例如,View 层的概念:视图,负责页面展示与数据输入。Controller 层:控制器层,负责数据处理与数据流转。Model:模型层,保存数据,提供数据。其次,中层概念决定了业务编码。例如,对于消息业务,我们大可能使用发布订阅模式,这样我们会引入主题,订阅者,消息这三个概念。再次,底层概念决定了数据流转。例如,如果你有使用消息队列,你可能会把消息队列作为一个桥梁,用以与其它应用程序进行通信、协作,这样相比与其它进程通信如管道,共享内存而言更加灵活。在软件世界,概念无处不在。

如何确立一个概念

通常我们会在编程中直接引入概念,例如:我们定义这个类完成文件操作。那么,什么是“文件操作”?可能的一种情况是我们在进行 Java IO 流的操作,从而定义了文件操作是指Java 中 IO流的操作。由此我们得到一个定义概念的一种形式:<什么概念>是指<什么地方>的<什么处理>。这个形式界定了概念的名称,概念有效的范围,概念的作用,这样可以让这个概念明确起来。那这时概念确立了吗?是的,这样一个简单的概念就确立起来了。另外一个可能的情况就是文件系统的操作,文件系统操作更多地是新建文件,编辑文件,删除文件,移动文件,复制文件等一系列操作。在这样看来,我们所定义的是一个二义性概念,二义性概念以及泛义性概念都是具有一个以上的定义,如果没有找好定义就会产生不一样的理解,让人对代码晦涩难懂。那我们如何确立一个概念呢?单义概念?还是多义概念?答案是:视情况而定。对于需要高稳定性的应用,它需要概念都清楚明确,没有二义性。这样该软件缺陷数量便会相应地少,每个处理都有着自身实际定义。对于高扩展的应用,它需要包容度高的概念作为基本概念作为入口。例如,在 Tomcat 的架构中,Tomcat 由Server到Acceptor层层递进的定义使得 Tomcat具有稳定的结构和易于配置。在Jetty 的架构中Handler 的核心概念让 Jetty 便于扩展形成链式