在(上)文的方案二中提到动态创建对应多个端口,那么到底如何实现呢?本文将带你真正体验一把。
标题既然是不一样的玩法,自然是有些不同寻常的,通常情况下整合 Netty 会遇到以下几个问题:
1.启动项的动态绑定(ServerBootstrap、EventLoopGroup、ChannelHandler等):常规做法,一对一没有问题。那么多对多时如何保证隔离性,各司其职,互不相干呢?每种配置项都做if
else判断?
2.每新增一种协议都要把步骤1来上一遍?你想这么干,我都不答应。。。多累人啊,没啥意义,都是些没价值的重复性工作,真正的重点应该放在业务数据的处理上(ChannelHandler)
3.根据业务的不同,不同协议上报的数据,处理的 ChannelHandler 也不同,有的甚至有多个 ChannelHandler
进行处理,并且处理顺序也有先后,那么这种情况又该如何保证呢?
以上三个问题,将会在下面为大家揭晓:
为方便大家理解,尽量简化代码,我将单独封装成 starter(快速启动SDK)
后续会介绍整合使用方法(有新知识引入哦 -_-!)
本文重点不是封装 starter,需要了解这方面知识的同学请自行百度,接下来我将聊聊上面几个问题的解决思路:
其实这几个问题既是分开的又是互相关联的,为什么这么说呢?我将通过代码片段来讲解,首先是配置项:
@Getter
@Setter
@ToString
@Accessors(chain = true)
@Component
@ConfigurationProperties(prefix = "netty.servers")
public class ServerProperties {
/**
* 协议枚举
*/
private ProtocolEnum protocol;
/**
* Netty 服务的日志级别,不是 spring-boot 服务
*/
private LogLevel logLevel;
/**
* 名称
*/
private String name;
/**
* 对外暴露端口
*/
private Integer port;
/**
* 服务线程数
*/
private Integer bossCount;
/**
* 工作线程数
*/
private Integer workerCount;
/**
* 读超时时间
*/
private Integer readerIdleTime;
/**
* 写超时时间
*/
private Integer writerIdleTime;
/**
* 读、写超时时间
*/
private Integer allIdleTime;
public LogLevel getLogLevel() {
return Objects.isNull(this.logLevel) ? LogLevel.INFO : this.logLevel;
}
}
@Getter
@Setter
@ToString
@Accessors(chain = true)
@Component
@ConfigurationProperties(prefix = "netty")
public class NettyProperties {
/**
* Netty 服务端配置
*/
private List<ServerProperties> servers = new ArrayList<>();
}
上面两段配置代码在配置文件中就该是这样的:
netty:
servers:
- protocol: TCP
name: binary
port: 30000
bossCount: 1
workerCount: 2
readerIdleTime: 180
writerIdleTime: 180
allIdleTime: 180
- protocol: TCP
name: protobuf
port: 31000
bossCount: 1
workerCount: 2
readerIdleTime: 180
writerIdleTime: 180
allIdleTime: 180
没错,就是一个列表配置,其中要注意的是 name,下面步骤会用到。
接下来是写一个 Netty 服务的启动项,重点如下:
@Slf4j
@Getter
@Setter
@ToString
@Accessors(chain = true)
public class NettyServer {
private AbstractBootstrap bootstrap;
private ChannelFuture channelFuture;
private ThreadFactory bossThreadFactory;
private ThreadFactory workerThreadFactory;
private EventLoopGroup bossEventLoopGroup;
private EventLoopGroup workerEventLoopGroup;
private Class<? extends Channel> channelType;
private ChannelInitializer channelInitializer;
private Map<ChannelOption<?>, Object> channelOptions = new LinkedHashMap<>();
private Map<ChannelOption<?>, Object> childChannelOptions = new LinkedHashMap<>();
private LinkedHashMap<String, ChannelHandlerAdapter> channelHandlerAdapterLinkedHashMap;
private final ServerProperties properties;
public NettyServer(ServerProperties properties, List<Object> beansWithNettyHandlerAnnotation) {
this.properties = properties;
this.channelHandlerAdapterLinkedHashMap = this.parseChannelHandler(beansWithNettyHandlerAnnotation);
this.init();
log.info("Netty server start successful : {}", properties);
}
public synchronized void init() {
this.initEventLoopGroup();
this.initChannelType();
this.initChannelOptions();
this.initSocketChannelInitializer();
ProtocolEnum protocol = this.properties.getProtocol();
switch (protocol) {
case TCP:
this.bootstrap = new ServerBootstrap();
ServerBootstrap serverBootstrap = (ServerBootstrap) this.bootstrap;
serverBootstrap.group(this.bossEventLoopGroup, this.workerEventLoopGroup);
serverBootstrap.channel((Class<? extends ServerChannel>) this.channelType)
.handler(new LoggingHandler(this.properties.getLogLevel())).childHandler(this.channelInitializer);
Iterator<ChannelOption<?>> iterator = this.channelOptions.keySet().iterator();
ChannelOption childOpt;
while (iterator.hasNext()) {
childOpt = iterator.next();
serverBootstrap.option(childOpt, this.channelOptions.get(childOpt));
}
iterator = this.childChannelOptions.keySet().iterator();
while (iterator.hasNext()) {
childOpt = iterator.next();
serverBootstrap.childOption(childOpt, this.childChannelOptions.get(childOpt));
}
break;
case UDP:
this.bootstrap = new Bootstrap();
this.bootstrap.group(this.bossEventLoopGroup);
this.bootstrap.channel(this.channelType).handler(this.channelInitializer);
for (ChannelOption<?> channelOption : this.channelOptions.keySet()) {
this.bootstrap.option(channelOption, this.channelOptions.get(channelOption));
}
break;
}
this.start();
Runtime.getRuntime().addShutdownHook(new Thread(this::stop, this.properties.getName() + "-shutdownHook"));
}
省略。。。。。。
}
启动项里似乎看不出什么门道,也没有什么具体的实现方法,那么如何进行 ChannelHandler 的加载呢?真正的重点来了
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NettyHandler {
/**
* Bean 实例化名称
*/
String name();
/**
* Bean 注册加载顺序
*/
int order();
}
@Component
@RequiredArgsConstructor
public class AnnotationListener implements ApplicationListener<ApplicationStartedEvent> {
private final NettyProperties nettyProperties;
public void onApplicationEvent(ApplicationStartedEvent event) {
if (event.getApplicationContext() instanceof ConfigurableWebServerApplicationContext) {
Map<String, Object> beans = event.getApplicationContext().getBeansWithAnnotation(NettyHandler.class);
ConfigurableListableBeanFactory conf = event.getApplicationContext().getBeanFactory();
List<Object> beansWithNettyHandlerAnnotation = new ArrayList<>();
for (String beanName : beans.keySet()) {
beansWithNettyHandlerAnnotation.add(conf.getSingleton(beanName));
}
if (!CollectionUtils.isEmpty(beansWithNettyHandlerAnnotation)) {
for (ServerProperties properties : this.nettyProperties.getServers()) {
new NettyServer(properties, beansWithNettyHandlerAnnotation);
}
}
}
}
}
这种方式是不是相当熟悉?没错,在常用整合方式中有提到利用 ApplicationListener 上下文监听器方式,只是监听事件的类型不同。
ApplicationStartedEvent 是在 spring-boot 2.0 中新增的特性,它介于 ApplicationPreparedEvent 和 ApplicationReadyEvent 之间。
本文重点不在此,需要了解的同学请自行百度,这里因(剧情需要 -_-!)需要用到此监听事件。
通过 @NettyHandler 注解,指定 handler 名称(与上面配置项的 name 对应)、order是加载和执行的顺序,按照实际情况编排。
到这里,相信大家对开始的几个问题已经有了答案。什么?还是一脸懵逼吗?那你要恶补下基础知识了。哈哈哈,开个玩笑。。。(不过大家还是要学会自我提升)
未完待续。。。。。。