【Netty】netty中都是用了哪些设计模式
对于工程师来说,掌握并理解运用设计模式,是非常重要的,但是除了学习基本的概念之外,需要结合优秀的中间件、框架源码学习其中的优秀软件设计,这样才能以不变应万变。
单例模式
单例模式解决的对象的唯一性,一般来说就是构造方法私有化、然后提供一个静态的方法获取实例。
在netty中,select用于处理CONTINUE、SELECT、BUSY_WAIT 三种策略,通过DefaultSelectStrategy实现饿汉式。
final class DefaultSelectStrategy implements SelectStrategy {
static final SelectStrategy INSTANCE = new DefaultSelectStrategy();
private DefaultSelectStrategy() { }
@Override
public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
// 是否有任务 ,有就不阻塞 ,否则就阻塞
return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
}
}
工厂方法模式
工厂方法解决的是批量同类型的对象的创建过程。
对于netty来说,在客户端和服务端启动过程中,会设置对应的channel类型,如果直接写死,必然不够优雅,所以可以反射方式,通过Class对象,进行不同类的实例化。具体操作就是如下两个方法,先获取Class的构造器,然后在进行newStance实例化。
虽然反射有一定的性能损失,但是这种方式可以有效减少工厂类的数量。所以损失一定的性能,还是非常值得的,但是如果对于性能比较敏感的业务场景,就需要具体问题具体思考了。也就是trade-off,你看不仅仅在分布式系统设计过程中有trade-off,在软件设计,编码层面也需要多思考具体的业务情况。
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Constructor<? extends T> constructor;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
ObjectUtil.checkNotNull(clazz, "clazz");
try {
// 通过Class对象 获取构造参数
// 等待后续的利用反射进行创建对象
this.constructor = clazz.getConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
" does not have a public non-arg constructor", e);
}
}
@Override
public T newChannel() {
try {
// 对象实例化
return constructor.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
}
}
@Override
public String toString() {
return StringUtil.simpleClassName(ReflectiveChannelFactory.class) +
'(' + StringUtil.simpleClassName(constructor.getDeclaringClass()) + ".class)";
}
}
责任链模式
责任链在netty中就非常熟悉了,那就是通过双向链表构建的channelPipeline,具体
protected DefaultChannelPipeline(Channel channel) {
tail = new TailContext(this); // 构建尾部节点
head = new HeadContext(this); // 构建头节点
head.next = tail; // 首尾相连接
tail.prev = head;
}
这里可以看到将添加到handler封装成了一个HandlerContext,为什么要这么做?
ChannelHandlerContext提供了时间处理的时间传播机制,如果在handler中,那么就需要在所有的用户自定义handler中都是实现,显然成本是比较高的。这种方式就只需要实现一次,直接复用就可以。
private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}
建造者模式
构建者模式解决的构建一个复杂对象的过程,而netty服务端和客户端的BootStrap就是这个过程。
比较优雅的是,通过链式编程 可以非常随意的设置对应的配置。
EventLoopGroup bossGrpup = new NioEventLoopGroup(1);
EventLoopGroup wrokerGrpup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGrpup,wrokerGrpup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler());
}
});
ChannelFuture f = bootstrap.bind(8888).sync();
f.channel().closeFuture().sync();
策略模式
策略模式解决的是针对同一个问题 不同算法的处理,策略之间可以互相替换,比如通过手机号 或者 邮箱可以查询用户的个人信息,就需要根据入参的不同 动态选择不同的策略。当有新增的不同的策略时,因为将稳定层进行了封装起来,只需要调整变化层,对于核心代码的修改很少,也容易减少bug。
netty在选择线程的时候,根据当前的是否是2的次幂,选择不同的策略方式,如果是的2的次幂,选择使用 &,否则选择 % ,&的性能相对来说比% 更高。
public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {
public static final DefaultEventExecutorChooserFactory INSTANCE = new DefaultEventExecutorChooserFactory();
private DefaultEventExecutorChooserFactory() { }
@Override
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTwoEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
private static boolean isPowerOfTwo(int val) {
return (val & -val) == val;
}
private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicLong idx = new AtomicLong();
private final EventExecutor[] executors;
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
}
}
}
装饰者模式
装饰者模式的目的解决的为目标类增加功能,比如原来男人,但是可以增加男人会化妆的功能,
在netty中就是实现了对ByteBuf侧重于在原来功能的拓展,增加日志,事务处理等。而装饰者侧重于为目标类增加新的功能。
代理模式和装饰者模式的区别,前者针对目标类是拓展功能
final class UnreleasableByteBuf extends WrappedByteBuf {
private SwappedByteBuf swappedBuf;
UnreleasableByteBuf(ByteBuf buf) {
super(buf instanceof UnreleasableByteBuf ? buf.unwrap() : buf);
}
模板方法模式
模板解决的是拓展问题,通过将一个稳定点,抽象到核心流程,然后交给不同的子类来实现。对于这个实现,在Spring框架中提供了Bean生命周期的拓展点,以实现不同的逻辑。
而在netty中,在初始化的时候,因为客户端和服务端都继承同一个父类,那么将init就可以不同实现。
abstract void init(Channel channel) throws Exception;
总结
设计模式的学习,不仅仅在于了解各个模式后的使用,而要了解设计背后的目的解决的问题,然后在遇到实际问题的时候,思考能不能使用对应的模式解决。而不是蛮干,炫技使用。另外掌握设计模式最有效的方式就是看各种优秀的中间件、框架源码,netty是一个很好的资源,Spring、MyBatis、JDK等都可以去翻看。
当然看源码的时候,一定要带着目的去读,不然就可能迷失在浩瀚的代码细节中。