Java开源框架中的设计模式以及应用场景

private static volatile Configuration CONFIG_INSTANCE = null;

public static Configuration getInstance() {

if (CONFIG_INSTANCE == null) {

synchronized (Configuration.class) {

if (CONFIG_INSTANCE == null) {

CONFIG_INSTANCE = buildConfiguration();

}

}

}

return CONFIG_INSTANCE;

}

}

3、静态内部类

可以看到,通过双重检查锁的方式来创建单例对象,还是比较复杂的。又是加锁,又是判断两次,还需要加volatile修饰的。

使用静态内部类的方式,可以达到双重检查锁相同的功效,但实现上简单了。

在Seata框架中,创建RM事件处理程序器的时候,就使用了静态内部类的方式来创建单例对象。

public class DefaultRMHandler extends AbstractRMHandler{

protected DefaultRMHandler() {

initRMHandlers();

}

private static class SingletonHolder {

private static AbstractRMHandler INSTANCE = new DefaultRMHandler();

}

public static AbstractRMHandler get() {

return DefaultRMHandler.SingletonHolder.INSTANCE;

}

}

还有可以通过枚举的方式来创建单例对象,但这种方式并没有被广泛采用,至少笔者在常见的开源框架中没见过,所以就不再列举。

有人说,饿汉式的单例模式不好,不能做到延迟加载,浪费内存。但笔者认为似乎过于吹毛求疵,事实上很多开源框架中,用的最多的就是这种方式。

如果明确希望实现懒加载效果时,可以考虑用静态内部类的方式;如果还有其他特殊的需求,比如创建对象的过程比较繁琐,可以用双重检查锁的方式。

二、工厂模式

工厂模式是Java中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

简单来说,在工厂模式中,就是代替new实例化具体类的一种模式。

1、简单工厂

简单工厂,的确比较简单,它的作用就是把对象的创建放到一个工厂类中,通过参数来创建不同的对象。

在分布式事务框架Seata中,如果发生异常,则需要进行二阶段回滚。

它的过程是,通过事务id找到undoLog记录,然后解析里面的数据生成SQL,将一阶段执行的SQL给撤销掉。

问题是SQL的种类包含了比如INSERT、UPDATE、DELETE,所以它们反解析的过程也不一样,就需要不同的执行器去解析。

在Seata中,有一个抽象的撤销执行器,可以生成一条SQL。

public abstract class AbstractUndoExecutor{

//生成撤销SQL

protected abstract String buildUndoSQL();

}

然后有一个获取撤销执行器的工厂,根据SQL的类型,创建不同类型的执行器并返回。

public class UndoExecutorFactory {

public static AbstractUndoExecutor getUndoExecutor(String dbType, SQLUndoLog sqlUndoLog) {

switch (sqlUndoLog.getSqlType()) {

case INSERT:

return new MySQLUndoInsertExecutor(sqlUndoLog);

case UPDATE:

return new MySQLUndoUpdateExecutor(sqlUndoLog);

case DELETE:

return new MySQLUndoDeleteExecutor(sqlUndoLog);

default:

throw new ShouldNeverHappenException();

}

}

}

使用的时候,直接通过工厂类获取执行器。

AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(dataSourceProxy.getDbType(),sqlUndoLog);

undoExecutor.executeOn(conn);

简单工厂模式的优点,想必各位都能领会,我们不再赘述。但它还有个小小的缺点:

一旦有了新的实现类,就需要修改工厂实现,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。

2、工厂方法

工厂方法模式解决了上面那个问题。它可以创建一个工厂接口和多个工厂实现类,这样如果增加新的功能,只需要添加新的工厂类就可以,不需要修改之前的代码。

另外,工厂方法模式还可以和模板方法模式结合一起,将他们共同的基础逻辑抽取到父类中,其它的交给子类去实现。

在Dubbo中,有一个关于缓存的设计完美的体现了工厂方法模式+模板方法模式。

首先,有一个缓存的接口,它提供了设置缓存和获取缓存两个方法。

public interface Cache {

void put(Object key, Object value);

Object get(Object key);

}

然后呢,还有一个缓存工厂,它返回一个缓存的实现。

public interface CacheFactory {

Cache getCache(URL url, Invocation invocation);

}

由于结合了模板方法模式,所以Dubbo又搞了个抽象的缓存工厂类,它实现了缓存工厂的接口。

public abstract class AbstractCacheFactory implements CacheFactory {

//具体的缓存实现类

private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

@Override

public Cache getCache(URL url, Invocation invocation) {

url = url.addParameter(Constants.METHOD_KEY, invocation.getMethodName());

String key = url.toFullString();

Cache cache = caches.get(key);

if (cache == null) {

//创建缓存实现类,交给子类实现

caches.put(key, createCache(url));

cache = caches.get(key);

}

return cache;

}

//抽象方法,交给子类实现

protected abstract Cache createCache(URL url);

}

在这里,公共的逻辑就是通过getCahce()创建缓存实现类,那具体创建什么样的缓存实现类,就由子类去决定。

所以,每个子类都是一个个具体的缓存工厂类,比如包括:

ExpiringCacheFactory、JCacheFactory、LruCacheFactory、ThreadLocalCacheFactory。

这些工厂类,只有一个方法,就是创建具体的缓存实现类。

public class ThreadLocalCacheFactory extends AbstractCacheFactory {

@Override

protected Cache createCache(URL url) {

return new ThreadLocalCache(url);

}

}

这里的ThreadLocalCache就是具体的缓存实现类,比如它是通过ThreadLocal来实现缓存功能。

public class ThreadLocalCache implements Cache {

private final ThreadLocal<Map<Object, Object>> store;

public ThreadLocalCache(URL url) {

this.store = new ThreadLocal<Map<Object, Object>>() {

@Override

protected Map<Object, Object> initialValue() {

return new HashMap<Object, Object>();

}

};

}

@Override

public void put(Object key, Object value) {

store.get().put(key, value);

}

@Override

public Object get(Object key) {

return store.get().get(key);

}

}

那在客户端使用的时候,还是通过工厂来获取缓存对象。

public static void main(String[] args) {

URL url = URL.valueOf(“http://localhost:8080/cache=jacache&.cache.write.expire=1”);

Invocation invocation = new RpcInvocation();

CacheFactory cacheFactory = new ThreadLocalCacheFactory();

Cache cache = cacheFactory.getCache(url, invocation);

cache.put(“java”,“java”);

System.out.println(cache.get(“java”));

}

这样做的好处有两点。

第一,如果增加新的缓存实现,只要添加一个新的缓存工厂类就可以,别的都无需改动。

第二,通过模板方法模式,封装不变部分,扩展可变部分。提取公共代码,便于维护。

另外,在Dubbo中,注册中心的获取也是通过工厂方法来实现的。

3、抽象工厂

抽象工厂模式,它能创建一系列相关的对象,而无需指定其具体类。

工厂方法模式和抽象工厂模式,它们之间最大的区别在于:

  • 工厂方法模式只有一个抽象产品类,具体工厂类只能创建一个具体产品类的实例;

  • 抽象工厂模式有多个抽象产品类,具体工厂类可以创建多个具体产品类的实例。

我们拿上面缓存的例子来继续往下说。

如果我们现在有一个数据访问程序,需要同时操作缓存和数据库,那就需要多个抽象产品和多个具体产品实现。

缓存相关的产品类都已经有了,我们接着来创建数据库相关的产品实现。

首先,有一个数据库接口,它是抽象产品类。

public interface DataBase {

void insert(Object tableName, Object record);

Object select(Object tableName);

}

然后,我们创建两个具体产品类MysqlDataBase和OracleDataBase。

public class MysqlDataBase implements DataBase{

Map<Object,Object> mysqlDb = new HashMap<>();

@Override

public void insert(Object tableName, Object record) {

mysqlDb.put(tableName,record);

}

@Override

public Object select(Object tableName) {

return mysqlDb.get(tableName);

}

}

public class OracleDataBase implements DataBase {

Map<Object,Object> oracleDb = new HashMap<>();

@Override

public void insert(Object tableName, Object record) {

oracleDb.put(tableName,record);

}

@Override

public Object select(Object tableName) {

return oracleDb.get(tableName);

}

}

其次,创建抽象的工厂类,它可以返回一个缓存对象和数据库对象。

public interface DataAccessFactory {

Cache getCache(URL url);

DataBase getDb();

}

最后是具体的工厂类,可以根据实际的需求,任意组合每一个具体的产品。

比如我们需要一个基于ThreadLocal的缓存实现和基于MySQL的数据库对象。

public class DataAccessFactory1 implements DataAccessFactory {

@Override

public Cache getCache(URL url) {

return new ThreadLocalCache(url);

}

@Override

public DataBase getDb() {

return new MysqlDataBase();

}

}

如果需要一个基于Lru的缓存实现和基于Oracle的数据库对象。

public class DataAccessFactory2 implements DataAccessFactory {

@Override

public Cache getCache(URL url) {

return new LruCache(url);

}

@Override

public DataBase getDb() {

return new OracleDataBase();

}

}

可以看到,抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。

三、模板方法模式

在模板模式中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。

简单来说,有多个子类共有的方法,且逻辑相同,可以考虑作为模板方法。

在上面Dubbo缓存的例子中,我们已经看到了模板方法模式的应用。但那个是和工厂方法模式结合在一块的,我们再单独找找模板方法模式的应用。

我们知道,当我们的Dubbo应用出现多个服务提供者时,服务消费者需要通过负载均衡算法,选择其中一个服务来进行调用。

首先,有一个LoadBalance接口,返回一个Invoker。

public interface LoadBalance {

 Invoker select(List<Invoker> invokers, URL url, Invocation invocation) throws RpcException;

}

然后定义一个抽象类,AbstractLoadBalance,实现LoadBalance接口。

public abstract class AbstractLoadBalance implements LoadBalance {

@Override

public  Invoker select(List<Invoker> invokers, URL url, Invocation invocation) {

if (invokers == null || invokers.isEmpty()) {

return null;

}

if (invokers.size() == 1) {

return invokers.get(0);

}

return doSelect(invokers, url, invocation);

}

//抽象方法,由子类选择一个Invoker

protected abstract  Invoker doSelect(List<Invoker> invokers, URL url, Invocation invocation);

}

这里的公共逻辑就是两个判断,判断invokers集合是否为空或者是否只有一个实例。如果是的话,就无需调用子类,直接返回就好了。

具体的负载均衡实现有四个:

  • 基于权重随机算法的 RandomLoadBalance

  • 基于最少活跃调用数算法的 LeastActiveLoadBalance

  • 基于 hash 一致性的 ConsistentHashLoadBalance

  • 基于加权轮询算法的 RoundRobinLoadBalance

public class RandomLoadBalance extends AbstractLoadBalance {

@Override

protected  Invoker doSelect(List<Invoker> invokers, URL url, Invocation invocation) {

//省略逻辑…

return invokers.get(ThreadLocalRandom.current().nextInt(length));

}

}

它们根据不同的算法实现,来返回一个具体的Invoker对象。

四、构造器模式

构造器模式使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式,常见于在构建一个复杂的对象,里面可能包含一些业务逻辑,比如检查,属性转换等。如果都在客户端手动去设置,那么会产生大量的冗余代码。那么这时候,我们就可以考虑使用构造器模式。

比如,在MyBatis中,MappedStatement的创建过程就使用了构造器模式。

我们知道,XML文件中的每一个SQL标签就要生成一个MappedStatement对象,它里面包含很多个属性,我们要构造的对象也是它。

public final class MappedStatement {

private String resource;

private Configuration configuration;

private String id;

private SqlSource sqlSource;

private ParameterMap parameterMap;

private List resultMaps;

//…省略大部分属性

}

然后有一个内部类Builder,它负责完成MappedStatement对象的构造。

首先,这个Builder类,通过默认的构造函数,先完成对MappedStatement对象,部分的构造。

public static class Builder {

private MappedStatement mappedStatement = new MappedStatement();

public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {

mappedStatement.configuration = configuration;

mappedStatement.id = id;

mappedStatement.sqlSource = sqlSource;

mappedStatement.statementType = StatementType.PREPARED;

mappedStatement.resultSetType = ResultSetType.DEFAULT;

//…省略大部分过程

}

}

然后,通过一系列方法,可以设置特定的属性,并返回这个Builder类,这里的方法适合处理一些业务逻辑。

public static class Builder {

public Builder parameterMap(ParameterMap parameterMap) {

mappedStatement.parameterMap = parameterMap;

return this;

}

public Builder resultMaps(List resultMaps) {

mappedStatement.resultMaps = resultMaps;

for (ResultMap resultMap : resultMaps) {

mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();

}

return this;

}

public Builder statementType(StatementType statementType) {

mappedStatement.statementType = statementType;

return this;

}

public Builder resultSetType(ResultSetType resultSetType) {

mappedStatement.resultSetType = resultSetType == null ? ResultSetType.DEFAULT : resultSetType;

return this;

}

}

最后呢,就是提供一个build方法,返回构建完成的对象就好了。

public MappedStatement build() {

assert mappedStatement.configuration != null;

assert mappedStatement.id != null;

assert mappedStatement.sqlSource != null;

assert mappedStatement.lang != null;

mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);

return mappedStatement;

}

在客户端使用的时候,先创建一个Builder,然后链式的调用一堆方法,最后再调用一次build()方法,我们需要的对象就有了,这就是构造器模式的应用。

MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)

.resource(resource)

.fetchSize(fetchSize)

.timeout(timeout)

.statementType(statementType)

.keyGenerator(keyGenerator)

.keyProperty(keyProperty)

.keyColumn(keyColumn)

.databaseId(databaseId)

.lang(lang)

.resultOrdered(resultOrdered)

.resultSets(resultSets)

.resultMaps(getStatementResultMaps(resultMap, resultType, id))

.resultSetType(resultSetType)

.flushCacheRequired(valueOrDefault(flushCache, !isSelect))

.useCache(valueOrDefault(useCache, isSelect))

.cache(currentCache);

ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);

MappedStatement statement = statementBuilder.build();

configuration.addMappedStatement(statement);

return statement;

五、适配器模式

适配器模式是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。

适配器模式一般用于屏蔽业务逻辑与第三方服务的交互,或者是新老接口之间的差异。

我们知道,在Dubbo中,所有的数据都是通过Netty来负责传输的,然后这就涉及了消息编解码的问题。

所以,首先它有一个编解码器的接口,负责编码和解码。

@SPI

public interface Codec2 {

@Adaptive({Constants.CODEC_KEY})

void encode(Channel channel, ChannelBuffer buffer, Object message) throws IOException;

@Adaptive({Constants.CODEC_KEY})

Object decode(Channel channel, ChannelBuffer buffer) throws IOException;

enum DecodeResult {

NEED_MORE_INPUT, SKIP_SOME_INPUT

}

}

然后,有几个实现类,比如DubboCountCodec、DubboCodec、ExchangeCodec等。

但是,当我们打开这些类的时候,就会发现,他们都是Dubbo中普通的类,只是实现了Codec2接口,其实不能直接作用于Netty编解码。

这是因为,Netty编解码需要实现ChannelHandler接口,这样才会被声明成Netty的处理组件。比如像MessageToByteEncoder、ByteToMessageDecoder那样。

鉴于此,Dubbo搞了一个适配器,专门来适配编解码器接口。

final public class NettyCodecAdapter {

private final ChannelHandler encoder = new InternalEncoder();

private final ChannelHandler decoder = new InternalDecoder();

private final Codec2 codec;

private final URL url;

private final org.apache.dubbo.remoting.ChannelHandler handler;

public NettyCodecAdapter(Codec2 codec, URL url, org.apache.dubbo.remoting.ChannelHandler handler) {

this.codec = codec;

this.url = url;

this.handler = handler;

}

public ChannelHandler getEncoder() {

return encoder;

}

public ChannelHandler getDecoder() {

return decoder;

}

private class InternalEncoder extends MessageToByteEncoder {

@Override

protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {

org.apache.dubbo.remoting.buffer.ChannelBuffer buffer = new NettyBackedChannelBuffer(out);

Channel ch = ctx.channel();

NettyChannel channel = NettyChannel.getOrAddChannel(ch, url, handler);

codec.encode(channel, buffer, msg);

}

}

private class InternalDecoder extends ByteToMessageDecoder {

@Override

protected void decode(ChannelHandlerContext ctx, ByteBuf input, List out) throws Exception {

ChannelBuffer message = new NettyBackedChannelBuffer(input);

NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);

//解码对象

codec.decode(channel, message);

//省略部分代码…

}

}

}

上面的代码中,我们看到,NettyCodecAdapter类适配的是Codec2接口,通过构造函数传递实现类,然后定义了内部的编码器实现和解码器实现,同时它们都是ChannelHandler。

这样的话,在内部类里面的编码和解码逻辑,真正调用的还是Codec2接口。

最后我们再来看看,该适配器的调用方式。

//通过SPI方式获取编解码器的实现类,比如这里是DubboCountCodec

Codec2 codec = ExtensionLoader.getExtensionLoader(Codec2.class).getExtension(“dubbo”);

URL url = new URL(“dubbo”, “localhost”, 22226);

//创建适配器

NettyCodecAdapter adapter = new NettyCodecAdapter(codec, url, NettyClient.this);

//向ChannelPipeline中添加编解码处理器

ch.pipeline()

.addLast(“decoder”, adapter.getDecoder())

.addLast(“encoder”, adapter.getEncoder())

以上,就是Dubbo中关于编解码器对于适配器模式的应用。

六、责任链模式

责任链模式为请求创建了一个接收者对象的链。允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。

我们来看一个Netty中的例子。我们知道,在Netty中服务端处理消息,就要添加一个或多个ChannelHandler。那么,承载这些ChannelHandler的就是ChannelPipeline,它的实现过程就体现了责任链模式的应用。

ServerBootstrap serverBootstrap = new ServerBootstrap();

serverBootstrap.childHandler(new ChannelInitializer() {

protected void initChannel(NioSocketChannel channel) {

channel.pipeline()

.addLast(new ChannelHandler1())

.addLast(new ChannelHandler2())

.addLast(new ChannelHandler3());

}

});

需要知道的是,在Netty整个框架里面,一条连接对应着一个Channel,每一个新创建的Channel都将会被分配一个新的ChannelPipeline。

ChannelPipeline里面保存的是ChannelHandlerContext对象,它是Channel相关的上下文对象,里面包着我们定义的处理器ChannelHandler。

根据事件的起源,IO事件将会被ChannelInboundHandler或者ChannelOutboundHandler处理。随后,通过调用ChannelHandlerContext实现,它将被转发给同一超类型的下一个ChannelHandler。

1、ChannelHandler

首先,我们来看责任处理器接口,Netty中的ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。

public interface ChannelHandler {

//当把 ChannelHandler 添加到 ChannelPipeline 中时被调用

void handlerAdded(ChannelHandlerContext ctx) throws Exception;

//当从 ChannelPipeline 中移除 ChannelHandler 时被调用

void handlerRemoved(ChannelHandlerContext ctx) throws Exception;

//当处理过程中在 ChannelPipeline 中有错误产生时被调用

void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

}

然后Netty定义了下面两个重要的ChannelHandler子接口:

1、ChannelInboundHandler,处理入站数据以及各种状态变化;

public interface ChannelInboundHandler extends ChannelHandler {

//当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用

void channelRegistered(ChannelHandlerContext ctx) throws Exception;

//当 Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调用

void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

//当 Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪

void channelActive(ChannelHandlerContext ctx) throws Exception;

//当 Channel 离开活动状态并且不再连接它的远程节点时被调用

void channelInactive(ChannelHandlerContext ctx) throws Exception;

当从 Channel 读取数据时被调用

void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

//当 Channel上的一个读操作完成时被调用

void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;

void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;

void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

}

2、ChannelOutboundHandler,处理出站数据并且允许拦截所有的操作;

public interface ChannelOutboundHandler extends ChannelHandler {

//当请求将 Channel 绑定到本地地址时被调用

void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;

//当请求将 Channel 连接到远程节点时被调用

void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,SocketAddress localAddress,

ChannelPromise promise) throws Exception;

//当请求将 Channel 从远程节点断开时被调用

void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

//当请求关闭 Channel 时被调用

void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

//当请求将 Channel 从它的 EventLoop 注销时被调用

void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;

//当请求从 Channel 读取更多的数据时被调用

void read(ChannelHandlerContext ctx) throws Exception;

//当请求通过 Channel 将数据写到远程节点时被调用

void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;

//当请求通过 Channel 将入队数据冲刷到远程节点时被调用

void flush(ChannelHandlerContext ctx) throws Exception;

}

2、ChannelPipeline

既然叫做责任链模式,那就需要有一个“链”,在Netty中就是ChannelPipeline。

ChannelPipeline提供了ChannelHandler链的容器,并定义了用于在该链上传播入站和出站事件流的方法,另外它还具有添加删除责任处理器接口的功能。

public interface ChannelPipeline{

ChannelPipeline addFirst(String name, ChannelHandler handler);

ChannelPipeline addLast(String name, ChannelHandler handler);

ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);

ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler);

ChannelPipeline remove(ChannelHandler handler);

ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler);

@Override

ChannelPipeline fireChannelRegistered();

@Override

ChannelPipeline fireChannelActive();

@Override

ChannelPipeline fireExceptionCaught(Throwable cause);

@Override

ChannelPipeline fireUserEventTriggered(Object event);

@Override

ChannelPipeline fireChannelRead(Object msg);

@Override

ChannelPipeline flush();

//省略部分方法…

}

然后我们看它的实现,默认有两个节点,头结点和尾结点。并在构造函数中,使它们首尾相连。这就是标准的链式结构。

public class DefaultChannelPipeline implements ChannelPipeline {

final AbstractChannelHandlerContext head;

final AbstractChannelHandlerContext tail;

private final Channel channel;

protected DefaultChannelPipeline(Channel channel) {

this.channel = ObjectUtil.checkNotNull(channel, “channel”);

tail = new TailContext(this);

head = new HeadContext(this);

head.next = tail;

tail.prev = head;

}

}

当有新的ChannelHandler被添加时,则将其封装为ChannelHandlerContext对象,然后插入到链表中。

private void addLast0(AbstractChannelHandlerContext newCtx) {

AbstractChannelHandlerContext prev = tail.prev;

newCtx.prev = prev;

newCtx.next = tail;

prev.next = newCtx;

tail.prev = newCtx;

}

3、ChannelHandlerContext

ChannelHandlerContext代表了ChannelHandler和ChannelPipeline之间的关联,每当有ChannelHandler添加到ChannelPipeline中时,都会创建ChannelHandlerContext。

ChannelHandlerContext的主要功能是管理它所关联的ChannelHandler和在同一个ChannelPipeline中的其他ChannelHandler之间的交互。

public interface ChannelHandlerContext{

Channel channel();

EventExecutor executor();

ChannelHandler handler();

ChannelPipeline pipeline();

@Override

ChannelHandlerContext fireChannelRegistered();

@Override

ChannelHandlerContext fireChannelUnregistered();

@Override

ChannelHandlerContext fireChannelActive();

@Override

ChannelHandlerContext fireChannelRead(Object msg);

@Override

ChannelHandlerContext read();

@Override

ChannelHandlerContext flush();

//省略部分方法……

}

ChannelHandlerContext负责在链上传播责任处理器接口的事件。

它有两个重要的方法,查找Inbound类型和Outbound类型的处理器。

值得注意的是,如果一个入站事件被触发,它将被从ChannelPipeline的头部开始一直被传播到ChannelPipeline的尾端;一个出站事件将从ChannelPipeline的最右边开始,然后向左传播。

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {

volatile AbstractChannelHandlerContext next;

volatile AbstractChannelHandlerContext prev;

//查找下一个Inbound类型的处理器,左 > 右

private AbstractChannelHandlerContext findContextInbound(int mask) {

AbstractChannelHandlerContext ctx = this;

EventExecutor currentExecutor = executor();

do {

ctx = ctx.next;

} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND));

return ctx;

}

//查找下一个Outbound类型的处理器,右 > 左

private AbstractChannelHandlerContext findContextOutbound(int mask) {

AbstractChannelHandlerContext ctx = this;

EventExecutor currentExecutor = executor();

do {

ctx = ctx.prev;

} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));

return ctx;

}

}

4、处理流程

当我们向服务端发送消息的时候,将会触发read方法。

public abstract class AbstractNioByteChannel extends AbstractNioChannel {

public final void read() {

//从Channel中获取对应的ChannelPipeline

final ChannelPipeline pipeline = pipeline();

//数据载体

ByteBuf byteBuf = allocHandle.allocate(allocator);

//传递数据

pipeline.fireChannelRead(byteBuf);

}

}

上面的代码中,就会调用到ChannelPipeline,它会从Head节点开始,根据上下文对象依次调用处理器。

最后

image.png

elHandlerContext fireChannelRead(Object msg);

@Override

ChannelHandlerContext read();

@Override

ChannelHandlerContext flush();

//省略部分方法……

}

ChannelHandlerContext负责在链上传播责任处理器接口的事件。

它有两个重要的方法,查找Inbound类型和Outbound类型的处理器。

值得注意的是,如果一个入站事件被触发,它将被从ChannelPipeline的头部开始一直被传播到ChannelPipeline的尾端;一个出站事件将从ChannelPipeline的最右边开始,然后向左传播。

abstract class AbstractChannelHandlerContext implements ChannelHandlerContext, ResourceLeakHint {

volatile AbstractChannelHandlerContext next;

volatile AbstractChannelHandlerContext prev;

//查找下一个Inbound类型的处理器,左 > 右

private AbstractChannelHandlerContext findContextInbound(int mask) {

AbstractChannelHandlerContext ctx = this;

EventExecutor currentExecutor = executor();

do {

ctx = ctx.next;

} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND));

return ctx;

}

//查找下一个Outbound类型的处理器,右 > 左

private AbstractChannelHandlerContext findContextOutbound(int mask) {

AbstractChannelHandlerContext ctx = this;

EventExecutor currentExecutor = executor();

do {

ctx = ctx.prev;

} while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));

return ctx;

}

}

4、处理流程

当我们向服务端发送消息的时候,将会触发read方法。

public abstract class AbstractNioByteChannel extends AbstractNioChannel {

public final void read() {

//从Channel中获取对应的ChannelPipeline

final ChannelPipeline pipeline = pipeline();

//数据载体

ByteBuf byteBuf = allocHandle.allocate(allocator);

//传递数据

pipeline.fireChannelRead(byteBuf);

}

}

上面的代码中,就会调用到ChannelPipeline,它会从Head节点开始,根据上下文对象依次调用处理器。

最后

[外链图片转存中…(img-bMKglZsf-1714370295015)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值