dubbo源码解析(三)serialize
serialize层
dubbo的序列化方式包括:hessian2、fastjson、fst、jdk、kryo
只要通过配置<dubbo:protocol serialization="">
或<dubbo:service serialization="">
就可以实现
serialize层
dubbo的序列化方式包括:hessian2、fastjson、fst、jdk、kryo
只要通过配置<dubbo:protocol serialization="">
或<dubbo:service serialization="">
就可以实现
本篇将着重分析dubbo的remoting模块
Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,以及SOA服务治理方案,和spring框架无缝集成。
dubbo作为一个非常好的rpc项目,广泛在国内使用。
java字节码被存储在一个叫做类文件的二进制文件。CtClass的对象代表一个类文件。ClassPool是存放CtClass的hash列表,用类名做key,如果CtClass没发现get()会读取一个class建造一个新的类记录到hash表并返回。
1 | ClassPool pool = ClassPool.getDefault(); |
如果系统使用多个类装载器,getDefault()只能搜索当前jvm的路径,可能加载不到对象。
1 | pool.insertClassPath(new ClassClassPath(this.getClass())); |
甚至你可以使用:
1 | ClassPool pool = ClassPool.getDefault(); |
1 | ClassPool cp = ClassPool.getDefault(); |
一个新类可以被定义为一个现有的类的一个副本
1 | ClassPool pool = ClassPool.getDefault(); |
1 |
|
关于内省
http://jboss-javassist.github.io/javassist/tutorial/tutorial2.html#intro
$0, $1, $2, … | 参数
$args | 数组参数Object[]
$$ | 所有参数m($$) 相当于m($1,$2,…)
$cflow(…) | cflow variable
$r | 结果类型.
$w | The wrapper type. It is used in a cast expression.
$_ | 结果值
$sig | An array of java.lang.Class objects representing the formal parameter types.
$type | A java.lang.Class object representing the formal result type.
$class | A java.lang.Class object representing the class currently edited.
1 | class Point { |
1 | ClassPool pool = ClassPool.getDefault(); |
结果》
1 | class Point { |
Integer i = ($w)5;
通过spring的asm
1 | Method[] methods = CarServiceImpl.class.getMethods(); |
通过javassist
1 | Class<?> clazz = Class.forName("com.absurd.rick.service.impl.CarServiceImpl"); |
我们先来看下spring如何手动初始化一个对象
1 | ClassPathResource res = new ClassPathResource("beans.xml"); |
所以我们先从DefaultListableBeanFactory开始了解
1 | public <T> T getBean(String name, Class<T> requiredType, Object... args) throws BeansException { |
1 | protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) |
1 | final String beanName = transformedBeanName(name); |
1 | protected String transformedBeanName(String name) { |
1 | Object bean; |
1 | else { |
1 | BeanFactory parentBeanFactory = getParentBeanFactory(); |
pom.xml
1 | <plugin> |
关于这部分的参数,详见链接
${project.basedir}/src/main/resources/jetty-context.xml
1 |
|
ctrl+shift+f9编译当前class
ctrl+f9编译全部
1.utf8mb4的最低mysql版本支持版本为5.5.3+,若不是,请升级到较新版本。
2.修改mysql配置文件my.cnf(windows为my.ini)
my.cnf一般在etc/mysql/my.cnf位置。找到后请在以下三部分里添加如下内容:
1 | [client] |
在mysql中执行:
1 | set character_set_client = utf8mb4; |
重启mysql
Linux:service mysql restart
3.修改database、table和column字符集。参考以下语句:
1 | ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; |
查看是否修改成功SHOW VARIABLES WHERE Variable_name LIKE 'character\_set\_%' OR Variable_name LIKE 'collation%';
4.如果你用的是java服务器,升级或确保你的mysql connector版本高于5.1.13,否则仍然无法使用utf8mb4
5.jdbc的url必须&characterEncoding=utf8
6.备份数据库的时候mysqldump -uroot -p --default-character-set=utf8mb4 --hex-blob databasename > databasename.sql
注:在navicat里面会看到乱码
可选:
7.解决不兼容问题
1 | public class EmojiUtil { |
另:
如果你不想重启数据库,可以这样做:
1.表必须是utf8mb4
2.连接池
<property name="connectionInitSqls" value="set names utf8mb4;"/>
或者每次插入前执行set names utf8mb4
原文地址: http://www.infoq.com/cn/author/%E8%AE%B8%E6%99%93%E6%96%8C
这里只是摘录点笔记
1 | <dependency> |
滥用坐标、错用坐标的样例比比皆是,在中央仓库中我们能看到SpringFramework有两种坐标,其一是直接使用springframework作为groupId,如springframework:spring-beans:1.2.6,另一种是用org.springframework作为groupId,如org.springframework:spring-beans:2.5。细心看看,前一种方式显得比较随意,后一种方式则是基于域名衍生出来的,显然后者更合理,因为用户能一眼根据域名联想到其Maven坐标,方便寻找。因此新版本的SpringFramework构件都使用org.springframework作为groupId。由这个例子我们可以看到坐标规划一个原则是基于项目域名衍生。其实很多流行的开源项目都破坏了这个原则,例如JUnit,这是因为Maven社区在最开始接受构件并部署到中央仓库的时候,没有很很严格的限制,而对于这些流行的项目来说,一时间更改坐标会影响大量用户,因此也算是个历史遗留问题了。
还有一个常见的问题是将groupId直接匹配到公司或者组织名称,因为乍一看这是显而易见的。例如组织是zoo.com,有个项目是dog,那有些人就直接使用groupId com.zoo了。如果项目只有一个模块,这是没有什么问题的,但现实世界的项目往往会有很多模块,Maven的一大长处就是通过多模块的方式管理项目。那dog项目可能会有很多模块,我们用坐标的哪个部分来定义模块呢?groupId显然不对,version也不可能是,那只有artifactId。因此要这里有了另外一个原则,用artifactId来定义模块,而不是定义项目。接下来,很显然的,项目就必须用groupId来定义。因此对于dog项目来说,应该使用groupId com.zoo.dog,不仅体现出这是zoo.com下的一个项目,而且可以与该组织下的其他项目如com.zoo.cat区分开来。
除此之外,artifactId的定义也有最佳实践,我们常常可以看到一个项目有很多的模块,例如api,dao,service,web等等。Maven项目在默认情况下生成的构件,其名称不会是基于artifactId,version和packaging生成的,例如api-1.0.jar,dao-1.0.jar等等,他们不会带有groupId的信息,这会造成一个问题,例如当我们把所有这些构件放到Web容器下的时候,你会发现项目dog有api-1.0.jar,项目cat也有api-1.0.jar,这就造成了冲突。更坏的情况是,dog项目有api-1.0.jar,cat项目有api-2.0.jar,其实两者没什么关系,可当放在一起的时候,却很容易让人混淆。为了让坐标更加清晰,又出现了一个原则,即在定义artiafctId时也加入项目的信息,例如dog项目的api模块,那就使用artifactId dog-api,其他就是dog-dao,dao-service等等。虽然连字号是不允许出现在Java的包名中的,但Maven没这个限制。现在dog-api-1.0.jar,cat-2.0.jar被放在一起时,就不容易混淆了。
关于坐标,我们还没谈到version,这里不再详述因为读者可以从Maven: The Complete Guide中找到详细的解释,简言之就是使用这样一个格式:
<主版本>.<次版本>.<增量版本>-<限定符>
其中主版本主要表示大型架构变更,次版本主要表示特性的增加,增量版本主要服务于bug修复,而限定符如alpha、beta等等是用来表示里程碑。当然不是每个项目的版本都要用到这些4个部分,根据需要选择性的使用即可。在此基础上Maven还引入了SNAPSHOT的概念,用来表示活动的开发状态,由于不涉及坐标规划,这里不进行详述。不过有点要提醒的是,由于SNAPSHOT的存在,自己显式地在version中使用时间戳字符串其实没有必要。
2.
1 | <plugin> |
mvn dependency:analyze
分析依赖
最后,还一些重要的POM内容通常被大多数项目所忽略,这些内容不会影响项目的构建,但能方便信息的沟通,它们包括项目URL,开发者信息,SCM信息,持续集成服务器信息等等,这些信息对于开源项目来说尤其重要。对于那些想了解项目的人来说,这些信息能他们帮助找到想要的信息,基于这些信息生成的Maven站点也更有价值。相关的POM配置很简单,如:
1 | <project> |
1 | <dependency> |
你会在一个项目中使用不同版本的SpringFramework组件么?答案显然是不会。因此这里就没必要重复写三次<version>2.5</version>
,使用Maven属性将2.5提取出来如下:
1 | <properties> |
现在2.5只出现在一个地方,虽然代码稍微长了点,但重复消失了,日后升级依赖版本的时候,只需要修改一处,而且也能避免漏掉升级某个依赖。
读者可能已经非常熟悉这个例子了,我这里再啰嗦一遍是为了给后面做铺垫,多模块POM重构的目的和该例一样,也是为了消除重复,模块越多,潜在的重复就越多,重构就越有必要。
dependencyManagement只会影响现有依赖的配置,但不会引入依赖。例如我们可以在父模块中配置如下:
1 | <dependencyManagement> |
这段配置不会给任何子模块引入依赖,但如果某个子模块需要使用JUnit和Log4j的时候,我们就可以简化依赖配置成这样:
1 | <dependency> |
你可以把dependencyManagement放到单独的专门用来管理依赖的POM中,然后在需要使用依赖的模块中通过import scope依赖,就可以引入dependencyManagement。例如可以写这样一个用于依赖管理的POM:
1 | <project> |
然后我就可以通过非继承的方式来引入这段依赖管理配置:
1 | <dependencyManagement> |
这样,父模块的POM就会非常干净,由专门的packaging为pom的POM来管理依赖,也契合的面向对象设计中的单一职责原则。此外,我们还能够创建多个这样的依赖管理POM,以更细化的方式管理依赖。这种做法与面向对象设计中使用组合而非继承也有点相似的味道。
与dependencyManagement类似的,我们也可以使用pluginManagement元素管理插件。一个常见的用法就是我们希望项目所有模块的使用Maven Compiler Plugin的时候,都使用Java 1.5,以及指定Java源文件编码为UTF-8,这时可以在父模块的POM中如下配置pluginManagement:
1 | <build> |
这段配置会被应用到所有子模块的maven-compiler-plugin中,由于Maven内置了maven-compiler-plugin与生命周期的绑定,因此子模块就不再需要任何maven-compiler-plugin的配置了。
持续集成?
现在,我们希望Maven在integration-test阶段执行所有以IT结尾命名的测试类,配置Maven Surefire Plugin如下:
1 | <plugin> |
对应于同样的package生命周期阶段,Maven为jar项目调用了maven-jar-plugin,为war项目调用了maven-war-plugin,换言之,packaging直接影响Maven的构建生命周期。了解这一点非常重要,特别是当你需要自定义打包行为的时候,你就必须知道去配置哪个插件。一个常见的例子就是在打包war项目的时候排除某些web资源文件,这时就应该配置maven-war-plugin如下:
1 | <plugin> |
本专栏的《坐标规划》一文中曾解释过,一个Maven项目只生成一个主构件,当需要生成其他附属构件的时候,就需要用上classifier。源码包和Javadoc包就是附属构件的极佳例子。它们有着广泛的用途,尤其是源码包,当你使用一个第三方依赖的时候,有时候会希望在IDE中直接进入该依赖的源码查看其实现的细节,如果该依赖将源码包发布到了Maven仓库,那么像Eclipse就能通过m2eclipse插件解析下载源码包并关联到你的项目中,十分方便。由于生成源码包是极其常见的需求,因此Maven官方提供了一个插件来帮助用户完成这个任务:
1 | <plugin> |
类似的,生成Javadoc包只需要配置插件如下:
1 | <plugin> |
为了帮助所有Maven用户更方便的使用Maven中央库中海量的资源,中央仓库的维护者强制要求开源项目提交构件的时候同时提供源码包和Javadoc包。这是个很好的实践,读者也可以尝试在自己所处的公司内部实行,以促进不同项目之间的交流。
除了前面提到了常规JAR包、WAR包,源码包和Javadoc包,另一种常被用到的包是在命令行可直接运行的CLI(Command Line)包。默认Maven生成的JAR包只包含了编译生成的.class文件和项目资源文件,而要得到一个可以直接在命令行通过java命令运行的JAR文件,还要满足两个条件:
JAR包中的/META-INF/MANIFEST.MF元数据文件必须包含Main-Class信息。
项目所有的依赖都必须在Classpath中。
Maven有好几个插件能帮助用户完成上述任务,不过用起来最方便的还是maven-shade-plugin,它可以让用户配置Main-Class的值,然后在打包的时候将值填入/META-INF/MANIFEST.MF文件。关于项目的依赖,它很聪明地将依赖JAR文件全部解压后,再将得到的.class文件连同当前项目的.class文件一起合并到最终的CLI包中,这样,在执行CLI JAR文件的时候,所有需要的类就都在Classpath中了。下面是一个配置样例:
1 | <plugin> |
上述例子中的,我的Main-Class是com.juvenxu.mavenbook.HelloWorldCli,构建完成后,对应于一个常规的hello-world-1.0.jar文件,我还得到了一个hello-world-1.0-cli.jar文件。细心的读者可能已经注意到了,这里用的是cli这个classifier。最后,我可以通过java -jar hello-world-1.0-cli.jar命令运行程序。
自定义格式包
实际的软件项目常常会有更复杂的打包需求,例如我们可能需要为客户提供一份产品的分发包,这个包不仅仅包含项目的字节码文件,还得包含依赖以及相关脚本文件以方便客户解压后就能运行,此外分发包还得包含一些必要的文档。这时项目的源码目录结构大致是这样的:
pom.xml
src/main/java/
src/main/resources/
src/test/java/
src/test/resources/
src/main/scripts/
src/main/assembly/
README.txt
除了基本的pom.xml和一般Maven目录之外,这里还有一个src/main/scripts/目录,该目录会包含一些脚本文件如run.sh和run.bat,src/main/assembly/会包含一个assembly.xml,这是打包的描述文件,稍后介绍,最后的README.txt是份简单的文档。
我们希望最终生成一个zip格式的分发包,它包含如下的一个结构:
bin/
lib/
README.txt
其中bin/目录包含了可执行脚本run.sh和run.bat,lib/目录包含了项目JAR包和所有依赖JAR,README.txt就是前面提到的文档。
描述清楚需求后,我们就要搬出Maven最强大的打包插件:maven-assembly-plugin。它支持各种打包文件格式,包括zip、tar.gz、tar.bz2等等,通过一个打包描述文件(该例中是src/main/assembly.xml),它能够帮助用户选择具体打包哪些文件集合、依赖、模块、和甚至本地仓库文件,每个项的具体打包路径用户也能自由控制。如下就是对应上述需求的打包描述文件src/main/assembly.xml:
1 | <assembly> |
首先这个assembly.xml文件的id对应了其最终生成文件的classifier。
其次formats定义打包生成的文件格式,这里是zip。因此结合id我们会得到一个名为hello-world-1.0-bin.zip的文件。(假设artifactId为hello-world,version为1.0)
dependencySets用来定义选择依赖并定义最终打包到什么目录,这里我们声明的一个depenencySet默认包含所有所有依赖,而useProjectArtifact表示将项目本身生成的构件也包含在内,最终打包至输出包内的lib路径下(由outputDirectory指定)。
fileSets允许用户通过文件或目录的粒度来控制打包。这里的第一个fileSet打包README.txt文件至包的根目录下,第二个fileSet则将src/main/scripts下的run.sh和run.bat文件打包至输出包的bin目录下。
打包描述文件所支持的配置远超出本文所能覆盖的范围,为了避免读者被过多细节扰乱思维,这里不再展开,读者若有需要可以去参考这份文档。
最后,我们需要配置maven-assembly-plugin使用打包描述文件,并绑定生命周期阶段使其自动执行打包操作:
1 | <plugin> |
运行mvn clean package之后,我们就能在target/目录下得到名为hello-world-1.0-bin.zip的分发包了。
入门
1 | public void run() { |
业务代码
1 | public class EchoServerHandler extends SimpleChannelUpstreamHandler { |
精通
手册:http://netty.io/3.7/guide/
入门
服务端
1 | /** |
1 | public class HelloServerInitializer extends ChannelInitializer<SocketChannel> { |
1 | public class HelloServerHandler extends SimpleChannelInboundHandler<String> { |
客户端
1 | public static String host = "127.0.0.1"; |
1 | public class HelloClientHandler extends SimpleChannelInboundHandler<String>{ |
1 | public class HelloClientInitializer extends ChannelInitializer<SocketChannel>{ |
方法:
类型 | 描述 |
---|---|
channelRegistered | 当一个Channel注册到EventLoop上,可以处理I/O时被调用 |
channelUnregistered | 当一个Channel从它的EventLoop上解除注册,不再处理I/O时被调用 |
channelActive | 当Channel变成活跃状态时被调用;Channel是连接/绑定、就绪的 |
channelInactive | 当Channel离开活跃状态,不再连接到某个远端时被调用 |
channelReadComplete | 当Channel上的某个读操作完成时被调用 |
channelRead | 当从Channel中读数据时被调用 |
channelWritabilityChanged | 当Channel的可写状态改变时被调用。通过这个方法,用户可以确保写操作不会进行地太快(避免OutOfMemoryError)或者当Channel又变成可写时继续写操作。Channel类的isWritable()方法可以用来检查Channel的可写状态。可写性的阈值可以通过Channel.config().setWriteHighWaterMark()和Channel.config().setWriteLowWaterMark()来设定。 |
userEventTriggered | 因某个POJO穿过ChannelPipeline引发ChannelnboundHandler.fireUserEventTriggered()时被调用 |
当一个ChannelInboundHandler实现类重写channelRead()方法时,它要负责释放ByteBuf相关的内存
1 | public class DiscardHandler extends ChannelInboundHandlerAdapter { |
一个更简单的替代方法就是用SimpleChannelInboundHandler
1 | public class SimpleDiscardHandler extends SimpleChannelInboundHandler<Object> { |
1 | public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter { |
类型 | 描述 |
---|---|
bind(ChannelHandlerContext,SocketAddress,ChannelPromise) | 请求绑定Channel到一个本地地址 |
connect(ChannelHandlerContext, SocketAddress,SocketAddress,ChannelPromise) | 请求连接Channel到远端 |
disconnect(ChannelHandlerContext, ChannelPromise) | 请求从远端断开Channel |
close(ChannelHandlerContext,ChannelPromise) | 请求关闭Channel |
deregister(ChannelHandlerContext, ChannelPromise) | 请求Channel从它的EventLoop上解除注册 |
read(ChannelHandlerContext) | 请求从Channel中读更多的数据 |
flush(ChannelHandlerContext) | 请求通过Channel刷队列数据到远端 |
write(ChannelHandlerContext,Object, ChannelPromise) | 请求通过Channel写数据到远端 |
CHANNELPROMISE VS. CHANNELFUTURE
ChannelOutboundHandler的大部分方法都用了一个ChannelPromise输入参数,用于当操作完成时收到通知。ChannelPromise是ChannelFuture的子接口,定义了可写的方法,比如setSuccess(),或者setFailure(),而ChannelFuture则是不可变对象。
无论何时你对数据操作ChannelInboundHandler.channelRead()或者ChannelOutboundHandler.write(),你需要确保没有资源泄露。也许你还记得上一章我们提到过,Netty采用引用计数来处理ByteBuf池。所以,在你用完一个ByteBuf后,调整引用计数的值是很重要的。
为了帮助你诊断潜在的问题, Netty提供了ResourceLeakDetector类,它通过采样应用程序1%的buffer分配来检查是否有内存泄露。这个过程的开销是很小的。
如果泄露被检测到,会产生类似下面这样的日志消息:
LEAK: ByteBuf.release() was not called before it’s garbage-collected. Enable
advanced leak reporting to find out where the leak occurred. To enable
advanced leak reporting, specify the JVM option
‘-Dio.netty.leakDetectionLevel=ADVANCED’ or call
ResourceLeakDetector.setLevel().
级别 | 描述 |
---|---|
DISABLED | 关闭内存泄露检测。 只有在大量测试后,才能用这个级别 |
SIMPLE | 报告默认的1%采样率中发现的任何泄露。这是默认的级别,在大部分情况下适用 |
ADVANCED | 报告发现的泄露和消息的位置。使用默认的采样率。 |
PARANOID | 类似ADVANCED级别,但是每个消息的获取都被检测采样。这对性能有很大影响,只能在调试阶段使用。 |
用上表中的某个值来配置下面这个Java系统属性,就可以设定内存泄露检测级别:
java -Dio.netty.leakDetectionLevel=ADVANCED
如果你设定这个JVM选项然后重启你的应用,你会看到应用中泄露buffer的最新位置。下面是一个单元测试产生的典型的内存泄露报告:
Running io.netty.handler.codec.xml.XmlFrameDecoderTest
15:03:36.886 [main] ERROR io.netty.util.ResourceLeakDetector - LEAK:
ByteBuf.release() was not called before it’s garbage-collected.
Recent access records: 11:io.netty.buffer.AdvancedLeakAwareByteBuf.toString(AdvancedLeakAwareByteBuf.java:697)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:157)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)
在你实现ChannelInboundHandler.channelRead()或者ChannelOutboundHandler.write()时,你怎样用这个诊断工具来防止内存泄露呢?让我们来看下ChannelRead()操作“消费(consume)”输入数据这个情况:就是说,当前handler没有通过ChannelContext.fireChannelRead()把消息传递到下一个ChannelInboundHandler。下面的代码说明了如何释放这条消息占用的内存。
1 | public class DiscardInboundHandler extends ChannelInboundHandlerAdapter { |
1 | public class DiscardOutboundHandler extends ChannelOutboundHandlerAdapter { |
重要的是,不仅要释放资源,而且要通知ChannelPromise,否则会出现某个ChannelFutureListener没有被通知到消息已经被处理的情况。
总之,如果一个消息被“消费”或者丢弃,没有送到ChannelPipeline中的下一个ChannelOutboundHandler,用户就要负责调用ReferenceCountUtil.release()。如果消息到达了真正的传输层,在它被写到socket中或者Channel关闭时,会被自动释放(这种情况下用户就不用管了)。
https://segmentfault.com/a/1190000007308934
如果你把一个ChannelPipeline看成是一串ChannelHandler实例,拦截穿过Channel的输入输出event,那么就很容易明白这些ChannelHandler的交互是如何构成了一个应用程序数据和事件处理逻辑的核心。
每个新创建的Channel都会分配一个新的ChannelPipeline。这个关系是恒定的;Channel不可以换别ChannelPipeline,也不可以解除掉当前分配的ChannelPipeline。在Netty组件的整个生命周期中这个关系是固定的,不需要开发者采取什么操作。
根据来源,一个event可以被一个ChannelInboundHandler或者ChannelOutboundHandler处理。接下来,通过调用ChannelHandlerContext的方法,它会被转发到下一个同类型的handler。
方法名 | 描述 |
---|---|
fireChannelRegistered | 调用ChannelPipeline中下一个ChannelInboundHandler的channelRegistered(ChannelHandlerContext) |
fireChannelUnregistered | 调用ChannelPipeline中下一个ChannelInboundHandler的channelUnRegistered(ChannelHandlerContext) |
fireChannelActive | 调用ChannelPipeline中下一个ChannelInboundHandler的channelActive(ChannelHandlerContext) |
fireChannelInactive | 调用ChannelPipeline中下一个ChannelInboundHandler的channelInactive(ChannelHandlerContext) |
fireExceptionCaught | 调用ChannelPipeline中下一个ChanneHandler的exceptionCaught(ChannelHandlerContext,Throwable) |
fireUserEventTriggered | 调用ChannelPipeline中下一个ChannelInboundHandler的userEventTriggered(ChannelHandlerContext, Object) |
fireChannelRead | 调用ChannelPipeline中下一个ChannelInboundHandler的channelRead(ChannelHandlerContext, Object msg) |
fireChannelReadComplete | 调用ChannelPipeline中下一个ChannelStateHandler的channelReadComplete(ChannelHandlerContext) |
方法名 | 描述 |
---|---|
bind | 绑定Channel到一个本地地址。这会调用ChannelPipeline中下一个ChannelOutboundHandler的bind(ChannelHandlerContext, SocketAddress, ChannelPromise) |
connect | 连接Channel到一个远端地址。这会调用ChannelPipeline中下一个ChannelOutboundHandler的connect(ChannelHandlerContext, SocketAddress, ChannelPromise) |
disconnect | 断开Channel。这会调用ChannelPipeline中下一个ChannelOutboundHandler的disconnect(ChannelHandlerContext, ChannelPromise) |
close | 关闭Channel。这会调用ChannelPipeline中下一个ChannelOutboundHandler的close(ChannelHandlerContext,ChannelPromise) |
deregister | Channel从它之前分配的EventLoop上解除注册。这会调用ChannelPipeline中下一个ChannelOutboundHandler的deregister(ChannelHandlerContext, ChannelPromise) |
flush | 刷所有Channel待写的数据。这会调用ChannelPipeline中下一个ChannelOutboundHandler的flush(ChannelHandlerContext) |
write | 往Channel写一条消息。这会调用ChannelPipeline中下一个ChannelOutboundHandler的write(ChannelHandlerContext, Object msg, ChannelPromise) 注意:不会写消息到底层的Socket,只是排队等候。如果要写到Socket中,调用flush()或者writeAndFlush() |
writeAndFlush | 这是先后调用write()和flush()的便捷方法。 |
read | 请求从Channel中读更多的数据。这会调用ChannelPipeline中下一个ChannelOutboundHandler的read(ChannelHandlerContext) |
ChannelHandlerContext代表了一个ChannelHandler和一个ChannelPipeline之间的关系,它在ChannelHandler被添加到ChannelPipeline时被创建。ChannelHandlerContext的主要功能是管理它对应的ChannelHandler和属于同一个ChannelPipeline的其他ChannelHandler之间的交互。
ChannelHandlerContext有很多方法,其中一些方法Channel和ChannelPipeline也有,但是有些区别。如果你在Channel或者ChannelPipeline实例上调用这些方法,它们的调用会穿过整个pipeline。而在ChannelHandlerContext上调用的同样的方法,仅仅从当前ChannelHandler开始,走到pipeline中下一个可以处理这个event的ChannelHandler。
方法名 | 描述 |
---|---|
bind | 绑定到给定的SocketAddress,返回一个ChannelFuture |
channel | 返回绑定的Channel |
close | 关闭Channel,返回一个ChannelFuture |
connect | 连接到给定的SocketAddress,返回一个ChannelFuture |
deregister | 从先前分配的EventExecutor上解除注册,返回一个ChannelFuture |
disconnect | 从远端断开,返回一个ChannelFuture |
executor | 返回分发event的EventExecutor |
fireChannelActive | 触发调用下一个ChannelInboundHandler的channelActive()(已连接) |
fireChannelInactive | 触发调用下一个ChannelInboundHandler的channelInactive()(断开连接) |
fireChannelRead | 触发调用下一个ChannelInboundHandler的channelRead()(收到消息) |
fireChannelReadComplete | 触发channelWritabilityChanged event到下一个ChannelInboundHandler |
handler | 返回绑定的ChannelHandler |
isRemoved | 如果绑定的ChannelHandler已从ChannelPipeline中删除,返回true |
name | 返回本ChannelHandlerContext 实例唯一的名字 |
Pipeline | 返回绑定的ChannelPipeline |
read | 从Channel读数据到第一个输入buffer;如果成功,触发一条channelRead event,通知handler channelReadComplete |
write | 通过本ChannelHandlerContext写消息穿过pipeline |
在使用ChannelHandlerContext API时,请牢记下面几点: |
1.添加ChannelFutureListener就是为了在ChannelFuture实例上调用addListener(ChannelFutureListener)方法,有两种方法可以做到这个。最常用的方法是在输出操作(比如write())返回的ChannelFuture上调用addListener()。
1 | ChannelFuture future = channel.write(...); |
2.添加一个ChannelFutureListener到ChannelPromise,然后将这个ChannelPromise作为参数传入ChannelOutboundHandler方法。下面的代码和前一段代码有相同的效果。
1 | public class OutboundExceptionHandler extends ChannelOutboundHandlerAdapter { |
1 | ByteBufAllocator byteBufAllocator = channel.alloc(); |
UnpooledByteBufAllocator:不池化,每次调用返回新实例
PooledByteBufAllocator:池化了ByteBuf并最大限度减少内存碎片。使用jemalloc(https://www.cnblogs.com/gaoxing/p/4253833.html)
创建未池化ByteBuf