Netty5+Spring+Maven简易框架环境搭建

文章结构说明

  • Maven引入
  • Netty5简易使用
  • Netty5的Spring封装
  • Spring配置
  • 程序打包、运行

Netty5+Maven+Spring环境搭建

环境说明

  1. IDEA
  2. jdk1.7+
  3. maven3

Maven引入

在IDEA中新建maven项目

可以用模板导入也可以不用 如下没有模板导入

  1. File –> New –> Project. 如图

  2. 选择maven,选择jdk1.8 –> Next

  3. 填好maven三坐标 –> Next

  4. 填好项目信息 –> Finish

  5. 等项目自动创建成功,项目如图

    pom文件现在还是空的

  6. 笔者的习惯会补全项目的文件,如图

以上我们建立了一个空的maven项目,由于netty是一个非web应用,所有没有webapp的结构

在maven的pom文件中引入我们需要的依赖 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<!-- netty5 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
<!-- spring的包,其它的包都会通过依赖间接的引入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- 方便测试的Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

Netty5

先看一个Netty5的简单例子

  1. Server启动类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public class EchoServer {
    private int port;
    public EchoServer(int port) {
    this.port = port;
    }
    public void run() throws Exception {
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    try {
    ServerBootstrap b = new ServerBootstrap();// Server启动类
    b.group(bossGroup, workerGroup)
    .channel(NioServerSocketChannel.class)// 通道类型
    .childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel socketChannel)
    throws Exception {
    socketChannel.pipeline().addLast(new EchoServerHandler());
    }
    })// Server的处理类通常是chiledHandler,
    // 需要添加一个channel处理类的工厂方法ChannelInitializer,
    //通过覆盖initChannel方法,增添处理类
    .option(ChannelOption.SO_BACKLOG, 128)
    .childOption(ChannelOption.SO_KEEPALIVE, true);
    ChannelFuture f = b.bind(port).sync(); //让主线程阻塞
    f.channel().closeFuture().sync();// 主线程退出时异步关闭通道操作
    } finally {
    workerGroup.shutdownGracefully();// 一定要加上
    bossGroup.shutdownGracefully();
    }
    }
    public static void main(String[] args) throws Exception {
    int port;
    if (args.length > 0) {
    port = Integer.parseInt(args[0]);
    } else {
    port = 8080;
    }
    new EchoServer(port).run();
    }
    }
  2. Server数据处理Handler

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class EchoServerHandler extends ChannelHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ctx.write(msg);
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
    ctx.flush();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
    // Close the connection when an exception is raised.
    cause.printStackTrace();
    ctx.close();
    }
    }
  3. 启动测试

  • 运行Server类的main方法
  • 终端输入命令 telnet localhost 8080
  • 任何输入Server都会原样返回
    如图

Netty面向Spring封装

为了将上述的过程通过Spring管理,需要对NettyServer做两件事

  • 将NettyServer的配置POJO化,以方便Spring的Bean组装和管理
  • 将NettyServer的管理面向接口,以方便Spring的接入
    以下是具体的实现过程
类关系图

  1. Server、NettyServer是服务器的接口
  2. AbstractNettyServer是服务器的抽象类,封装部分通用实现
  3. NettyTcpServer是基于Tcp协议的具体实现类
  4. NettyConfig是面向Spring封装的Server配置类
  5. 通过ServerManager实现对Server生命周期的管理
  6. MyChannelInitializer是类似数据流处理Handler类工厂,在这里添加(多个)数据流处理Handler。
  7. EchoHandler是数据流处理的具体实现类,Server的具体业务逻辑在这里实现。
  8. Entrance是程序的启动入口
每个类的具体实现
  1. NettyConfig class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    public class NettyConfig {
    private NioEventLoopGroup bossGroup;
    private NioEventLoopGroup workerGroup;
    private int bossCount;
    private int workerCount;
    private int port = 12345;
    public NioEventLoopGroup getBossGroup() {
    if (null == bossGroup) {
    if (0 >= bossCount) {
    bossGroup = new NioEventLoopGroup();
    } else {
    bossGroup = new NioEventLoopGroup(bossCount);
    }
    }
    return bossGroup;
    }
    public void setBossGroup(NioEventLoopGroup bossGroup) {
    this.bossGroup = bossGroup;
    }
    public synchronized NioEventLoopGroup getWorkerGroup() {
    if (null == workerGroup) {
    if (0 >= workerCount) {
    workerGroup = new NioEventLoopGroup();
    } else {
    workerGroup = new NioEventLoopGroup(workerCount);
    }
    }
    return workerGroup;
    }
    public void setWorkerGroup(NioEventLoopGroup workerGroup) {
    this.workerGroup = workerGroup;
    }
    public int getBossCount() {
    return bossCount;
    }
    public void setBossCount(int bossCount) {
    this.bossCount = bossCount;
    }
    public int getWorkerCount() {
    return workerCount;
    }
    public void setWorkerCount(int workerCount) {
    this.workerCount = workerCount;
    }
    public int getPort() {
    return port;
    }
    public void setPort(int port) {
    this.port = port;
    }
    }
  2. Server interface

    1
    2
    3
    4
    5
    public interface Server {
    void start() throws Exception;
    void start(int port) throws Exception;
    void stop() throws Exception;
    }
  3. NettyServer interface

    1
    2
    3
    4
    5
    public interface NettyServer extends Server {
    ChannelInitializer<? extends Channel> getChannelInitializer();
    void setChannelInitializer(ChannelInitializer<? extends Channel> initializer);
    NettyConfig getNettyConfig();
    }
  4. AbstractNettyServer abstract class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public abstract class AbstractNettyServer implements NettyServer {
    protected final NettyConfig nettyConfig;
    protected ChannelInitializer<? extends Channel> channelInitializer;
    public static final ChannelGroup ALL_CHANNELS = new DefaultChannelGroup("NADRON-CHANNELS", GlobalEventExecutor.INSTANCE);
    public AbstractNettyServer(NettyConfig nettyConfig, ChannelInitializer<? extends Channel> channelInitializer) {
    this.nettyConfig = nettyConfig;
    this.channelInitializer = channelInitializer;
    }
    @Override
    public void start(int port) throws Exception {
    nettyConfig.setPort(port);
    start();
    }
    @Override
    public void stop() throws Exception {
    ChannelGroupFuture futures = ALL_CHANNELS.close();
    try {
    futures.await();
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally {
    if (null != nettyConfig.getBossGroup()) {
    nettyConfig.getBossGroup().shutdownGracefully();
    }
    if (null != nettyConfig.getWorkerGroup()) {
    nettyConfig.getWorkerGroup().shutdownGracefully();
    }
    }
    }
    @Override
    public ChannelInitializer<? extends Channel> getChannelInitializer() {
    return channelInitializer;
    }
    @Override
    public NettyConfig getNettyConfig() {
    return nettyConfig;
    }
    protected EventLoopGroup getBossGroup() {
    return nettyConfig.getBossGroup();
    }
    protected EventLoopGroup getWorkerGroup() {
    return nettyConfig.getWorkerGroup();
    }
    }
  5. NettyServer class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class NettyTcpServer extends AbstractNettyServer {
    private ServerBootstrap b;
    public NettyTcpServer(NettyConfig nettyConfig, ChannelInitializer<? extends Channel> channelInitializer) {
    super(nettyConfig, channelInitializer);
    }
    @Override
    public void start() throws Exception {
    try {
    b = new ServerBootstrap();
    b.group(getBossGroup(), getWorkerGroup())
    .channel(NioServerSocketChannel.class)
    .childHandler(getChannelInitializer())
    .option(ChannelOption.SO_BACKLOG, 128)
    .childOption(ChannelOption.SO_KEEPALIVE, true);
    ChannelFuture f = b.bind(nettyConfig.getPort()).sync();
    ALL_CHANNELS.add(f.channel());
    f.channel().closeFuture().sync();
    } finally {
    stop();
    }
    }
    @Override
    public void setChannelInitializer(ChannelInitializer<? extends Channel> initializer) {
    this.channelInitializer = initializer;
    b.childHandler(initializer);
    }
    }
  6. MyChannelInitializer class

    1
    2
    3
    4
    5
    6
    public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
    ch.pipeline().addLast("echoHandler", new EchoHandler());
    }
    }
  7. EchoHandler class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class EchoHandler extends ChannelHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ctx.write(msg);
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
    ctx.flush();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
    // Close the connection when an exception is raised.
    cause.printStackTrace();
    ctx.close();
    }
    }

Spring接管配置工作

笔者采用xml配置的方式,利于代码与配置的分离,也可以用配置类的方式

  1. 配置xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.nettyspring"/>
    <bean id="tcpServer" class="com.nettyspring.first.example.server.NettyTcpServer" destroy-method="stop">
    <constructor-arg ref="tcpConfig"/>
    <constructor-arg ref="myChannelInitializer"/>
    </bean>
    <bean id="myChannelInitializer" class="com.nettyspring.first.example.Initializer.MyChannelInitializer"></bean>
    <bean id="tcpConfig" class="com.nettyspring.first.example.NettyConfig">
    <property name="bossGroup" ref="bossGroup"/>
    <property name="workerGroup" ref="workerGroup"/>
    <property name="port" value="10086"/>
    </bean>
    <bean id="bossGroup" class="io.netty.channel.nio.NioEventLoopGroup" destroy-method="shutdownGracefully">
    <constructor-arg type="int" index="0" value="2"/>
    </bean>
    <bean id="workerGroup" class="io.netty.channel.nio.NioEventLoopGroup" destroy-method="shutdownGracefully">
    <constructor-arg type="int" index="0"
    value="8"/>
    </bean>
    </beans>
  2. AppContext class
    实现自己的AppContext,并在启动Server时调用,让Spring接管bean管理工作,由于上面配置了component-scan,支持Spring注解的使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class AppContext implements ApplicationContextAware {
    public static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    AppContext.applicationContext = applicationContext;
    }
    public static Object fetchBean(String name) {
    if (null == name) {
    return null;
    }
    if (null == applicationContext) {
    applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    }
    return applicationContext.getBean(name);
    }
    }
  3. ServerManager class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class ServerManager {
    private AbstractNettyServer nettyServer;
    public ServerManager() {
    nettyServer = (AbstractNettyServer) AppContext.fetchBean("tcpServer");
    }
    public void startServer(int port) throws Exception {
    nettyServer.start(port);
    }
    public void startServer() throws Exception {
    nettyServer.start();
    }
    public void stopServer() throws Exception {
    nettyServer.stop();
    }
    }
  4. Entrance class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Entrance {
    public static void main(String... args) {
    ServerManager manager = new ServerManager();
    try {
    manager.startServer();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

打包、运行

  1. 打包需要的maven插件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <build>
    <finalName>nettyFirst</finalName>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
    <source>1.6</source>
    <target>1.6</target>
    </configuration>
    </plugin>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>1.2.1</version>
    <executions>
    <execution>
    <phase>package</phase>
    <goals>
    <goal>shade</goal>
    </goals>
    <configuration>
    <transformers>
    <transformer
    implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
    <resource>META-INF/spring.handlers</resource>
    </transformer>
    <transformer
    implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
    <resource>META-INF/spring.schemas</resource>
    </transformer>
    <transformer
    implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
    <mainClass>com.nettyspring.first.example.app.Entrance</mainClass>
    </transformer>
    </transformers>
    </configuration>
    </execution>
    </executions>
    </plugin>
    </plugins>
    </build>

shade 插件可以指定jar文件执行的入口类,<mainClass>需自己指定
上面的两个transformer的作用是将spring依赖包里的xml文件也打进去,如果没指定的话会出现schemas找不到的情况

  1. 打包、执行
  • maven clean install 会在工程目录下生成 target 目录,里面有生成的jar文件 。
  • java -jar XXX.jar
  • 在终端输入 telnet localhost 10086 10086 为spring的配置xml里配置的端口号,如图

上述工作完成后的目录结构如下

Netty5参考资料

文档

Netty5官方用户手册
Netty5官方用户手册中文翻译

Netty5 示例程序

通过maven引入

1
2
3
4
5
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-example</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>