MessageToByteEncoder
前面说了下解码器,现在说说编码器,就是将一个消息编码下,编码成我们的字节数组形式。我们看到这个地方是有泛型了,为什么前面解码器没有呢,因为解码是把字节数组变成对象,对于我来说输入是字节数组,我不知道什么类型,只要转成对象类型即可。而编码器的泛型是针对特定类型的编码,你不告诉我什么类型,我没办法编码,我只能不处理,所以你要告诉我类型,那我检查到是我要编码的类型,我就进行相应的编码。
解码是针对入站数据,那编码就是出站啦,结构就是这样:
抽象方法:
protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
主要也是靠子类来实现编码方法的。
TypeParameterMatcher
是个抽象类,内部有个类型参数匹配器,也就是上面说的泛型的,只有匹配到相应的泛型,才会进行解码,否则就往前传递。他也是存在于线程本地变量中,在UnpaddedInternalThreadLocalMap
中:
//类型和参数类型匹配器的map
Map<Class<?>, TypeParameterMatcher> typeParameterMatcherGetCache;
//类型和类型和参数类型匹配器的map
Map<Class<?>, Map<String, TypeParameterMatcher>> typeParameterMatcherFindCache;
抽象方法:
public abstract boolean match(Object msg);
这两个东西干嘛用呢,为什么接下来就讲。
空类型参数匹配
这个就是任何对象类型都能匹配。
private static final TypeParameterMatcher NOOP = new TypeParameterMatcher() {
@Override
public boolean match(Object msg) {
return true;
}
};
get获取类型参数匹配器
会根据传进来得Class对象,判断是哪个类型,从而生成相应的匹配器,如果是Object
,就是上面的NOOP
,否则就是ReflectiveMatcher
。这个就是根据类型获取参数类型匹配器,就是用到上面你的UnpaddedInternalThreadLocalMap
的typeParameterMatcherGetCache
public static TypeParameterMatcher get(final Class<?> parameterType) {
final Map<Class<?>, TypeParameterMatcher> getCache =
InternalThreadLocalMap.get().typeParameterMatcherGetCache();
TypeParameterMatcher matcher = getCache.get(parameterType);
if (matcher == null) {
if (parameterType == Object.class) {
matcher = NOOP;
} else {
matcher = new ReflectiveMatcher(parameterType);
}
getCache.put(parameterType, matcher);
}
return matcher;
}
ReflectiveMatcher
其实就是实现match
方法,把相关的类型保存,然后匹配的时候看是否是这个类型的实例。
private static final class ReflectiveMatcher extends TypeParameterMatcher {
private final Class<?> type;
ReflectiveMatcher(Class<?> type) {
this.type = type;
}
@Override
public boolean match(Object msg) {
return type.isInstance(msg);
}
}
find寻找泛型对应的匹配器
这里首先还是从线程本地变量里获取UnpaddedInternalThreadLocalMap
的typeParameterMatcherFindCache
,然后根据当前对象获取对应的Map<String, TypeParameterMatcher>
。如果不存在,就用反射来找出泛型的具体类型,最后根据类型返回匹配器,中间还会缓存类型和匹配器的映射关系。
public static TypeParameterMatcher find(
final Object object, final Class<?> parametrizedSuperclass, final String typeParamName) {
final Map<Class<?>, Map<String, TypeParameterMatcher>> findCache =
InternalThreadLocalMap.get().typeParameterMatcherFindCache();
final Class<?> thisClass = object.getClass();
Map<String, TypeParameterMatcher> map = findCache.get(thisClass);
if (map == null) {
map = new HashMap<String, TypeParameterMatcher>();
findCache.put(thisClass, map);
}
TypeParameterMatcher matcher = map.get(typeParamName);
if (matcher == null) {
matcher = get(find0(object, parametrizedSuperclass, typeParamName));
map.put(typeParamName, matcher);
}
return matcher;
}
find0
这个方法基本就是用反射,根据当前对象获取泛型I
的真实类型。具体代码我就不分析了,比较繁琐,你知道是通过反射分析出具体的泛型类型就好了。
图示结构
显示构造函数
这个构造函数传了要匹配的类型,直接去获取匹配器即可。这里就是直接parameterType
和TypeParameterMatcher
对应起来了。
protected MessageToByteEncoder(Class<? extends I> outboundMessageType, boolean preferDirect) {
matcher = TypeParameterMatcher.get(outboundMessageType);
this.preferDirect = preferDirect;
}
隐式构造函数
这个两个构造函数没有传类型,而是传了字符串I
,也就是底层会通过反射出泛型的具体类型,然后获得匹配器。这里是通过当前对象的Class
对象比如Class1
,对应找到HashMap<String, TypeParameterMatcher>()
,通过字符串I
再找到TypeParameterMatcher
。在这个过程中,会将I
对应的具体类型parameterType
和TypeParameterMatcher
也放入UnpaddedInternalThreadLocalMap
的typeParameterMatcherGetCache
缓存中。
protected MessageToByteEncoder() {
this(true);
}
protected MessageToByteEncoder(boolean preferDirect) {
matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
this.preferDirect = preferDirect;
}
write写方法
关键还是写方法,他会判断消息是否是类型匹配的,是的话才会申请一个缓冲区,然后进行编码,不是就直接往前传递了。编码完了会尝试释放消息。如果编码失败,就往前写一个空缓冲区,把申请的缓冲区释放了。
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf buf = null;
try {
if (acceptOutboundMessage(msg)) {
@SuppressWarnings("unchecked")
I cast = (I) msg;
buf = allocateBuffer(ctx, cast, preferDirect);
try {
encode(ctx, cast, buf);
} finally {
ReferenceCountUtil.release(cast);
}
if (buf.isReadable()) {
ctx.write(buf, promise);
} else {
buf.release();
ctx.write(Unpooled.EMPTY_BUFFER, promise);
}
buf = null;
} else {
ctx.write(msg, promise);
}
} catch (EncoderException e) {
throw e;
} catch (Throwable e) {
throw new EncoderException(e);
} finally {
if (buf != null) {
buf.release();
}
}
}
public boolean acceptOutboundMessage(Object msg) throws Exception {
return matcher.match(msg);
}
public static boolean release(Object msg) {
if (msg instanceof ReferenceCounted) {
return ((ReferenceCounted) msg).release();
}
return false;
}
allocateBuffer申请缓冲区
如果优先是直接缓冲区,就会申请直接缓冲区,否则就是堆内缓冲区。
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, @SuppressWarnings("unused") I msg,
boolean preferDirect) throws Exception {
if (preferDirect) {
return ctx.alloc().ioBuffer();
} else {
return ctx.alloc().heapBuffer();
}
}
比如:
最简单的例子
编码为Long
类型:
public class LongToByteEncoder extends MessageToByteEncoder<Long> {
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
out.writeLong(msg);
}
}
解码器,上篇说过了粘包拆包问题,所以得有判断是否足够Long
的字节:
public class ByteToLongDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if(in.readableBytes() >= 8) {
out.add(in.readLong());
}
}
}
至于MessageToMessageDecoder
与MessageToMessageEncoder
原理其实差不多,自己看下应该能懂, 就不多说了。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。