netty整合springboot

1.原理说明

在网络编程领域,Netty是Java的一个优秀的框架,他将java的复杂和难以使用的关于NIO框架进行了封装,使其隐藏在易用的api后面。总之,编写高性能的网络服务,使用Netty就很方便。

Netty传输框架,接收传输信息,截取访问路径,并使用java反射原理,得到对应spingboot 的controller类的ReqestMapping方法上,以此使用高性能网络服务,进行数据传输。

2.代码说明

2.1 建springboot项目

(1). pom.xml

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.25.Final</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <version>2.0.3.RELEASE</version>
</dependency>

<dependency>
     <groupId>com.google.code.gson</groupId>
     <artifactId>gson</artifactId>
     <version>2.8.4</version>
 </dependency>
 <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.16.8</version>
       <scope>provided</scope>
   </dependency>

   <!-- springboot -->
   <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <compile>test</compile>
    <version>2.0.3.RELEASE</version>
</dependency> 
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <version>2.0.3.RELEASE</version>
</dependency>

<!-- 日志 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

(2).main方法

@SpringBootApplication
public class AppRun {
public static void main(String[] args) {
    SpringApplication.run(AppRun.class, args);
}
}

(3).controller

@RestController
@RequestMapping("/hello")
public class HelloController {
    @RequestMapping("/hello")
    public String testHello(String message){
        System.out.println("####$$$$$$$$$$$########:"+message);
        return "test";
    }
}

(4).application.yml

server: 
  port: 8889

(5).测试说明

访问:http://192.168.18.106:8889/hello/hello?message=”ss”
返回: test

2.2.创建netty项目

(1).main方法改造

实现了CommandLineRunner接口,并重写了run方法,以便在springboot启动后就立即启动netty服务。

@SpringBootApplication
public class AppRun implements CommandLineRunner{

    @Autowired
    NettyServer nettyServer; //netty服务器

    @Autowired
    CommandBuilder commandBuilder;//注解解析器

    public static void main(String[] args) {
        SpringApplication.run(AppRun.class, args);
    }
    public void run(String... args) throws Exception {
        // TODO Auto-generated method stub
        commandBuilder.setUp(); //注解解析启动
        nettyServer.run();//netty服务器启动
    }
}

(2)java反射演示

java bean

public class Command {
    public void print(String ss){
        System.out.println("string  "+ss);
    }
    public void print(){
        System.out.println("string null  ");
    }
    public void print(int ss){
        System.out.println("int   "+ss);
    }
}

反射获取方法

public class TestCommand {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
        //获取类
        Class<?> forName = Command.class;
        //类实例化对象
        Object newInstance = forName.newInstance();
        //方法  (方法名称,参数类型)
        Method declaredMethod =              forName.getDeclaredMethod("print",int.class);
        //java invoke使用
        declaredMethod.invoke(newInstance, 2);
    //返回  int   2
    }
}

(3).注解解析器

反射需要的命令参数java bean

@Data
public class CommandAction {
    private Object className;
    private Method method;
    private String path;
    public CommandAction(){}
    public CommandAction(String path,Object cmd,Method method){
        this.path = path;
        this.method = method;
        this.className = cmd;
    }
}

注解解析类

@Component
public class CommandBuilder implements ApplicationContextAware{
    private Logger logger = LoggerFactory.getLogger(CommandBuilder.class);

    private ApplicationContext applicationContext; //获取上下文

    //封装反射需要的命令参数
    private Map<String, CommandAction> map = new HashMap<String, CommandAction>();

    public void setApplicationContext(ApplicationContext context) throws BeansException {
        // TODO Auto-generated method stub
        this.applicationContext = context;
    }
    public void setUp(){
        //通过上下文获取项目的 所有注解@RestController
        Map<String, Object> controllerBean = this.applicationContext.getBeansWithAnnotation(RestController.class);

        for(String key: controllerBean.keySet()){
            //获取类
            Object cmd = controllerBean.get(key);
//            @org.springframework.web.bind.annotation.RequestMapping
//            (path=[], headers=[], method=[], name=, produces=[], params=[], value=[/hello], consumes=[])

            //全路径变量
            String route = "";
            //获取类上注解@RequestMapping
            RequestMapping annotation = cmd.getClass().getAnnotation(RequestMapping.class);
            if(annotation!=null){
                //取前段路径
                route = annotation.value()[0];
            }

            //获取当前类的所有方法
            Method[] methods = cmd.getClass().getMethods();
            for(Method m: methods){
                try {
                    //获取当前方法的注解@RequestMapping
                    RequestMapping value = m.getAnnotation(RequestMapping.class);
                    if(value!=null){
                        //取方法路径
                        route+=value.value()[0];
                        logger.info("found routes: "+route);

                        //将路径 和 反射需要的命令参数封装到map
                        map.put(route, new CommandAction(route, cmd, m));
                    }

                } catch (Exception e) {
                    // TODO: handle exception
                    e.printStackTrace();
                    logger.error(e.getMessage());
                }
            }

        }
    }
    /**
     * 通过路径 获取 对应的controller
     * @param path
     * @return
     */
    public CommandAction getCommandAction(String path){
        return map.get(path);
    }
}

(4).netty 服务器

nettyServer

@Component
public class NettyServer {

    private Logger logger = LoggerFactory.getLogger(NettyServer.class);

    private EventLoopGroup bossgroup; //创建mainReactor

    private EventLoopGroup workgroup; //创建subReactor

    @Bean
    public EventLoopGroup bossGroup(){
        bossgroup = new NioEventLoopGroup();
        return bossgroup;
    }

    @Bean
    public EventLoopGroup workGroup(){
        workgroup = new NioEventLoopGroup();
        return workgroup;
    }

    //将对象  nettyServerHandler 交给spring容器管理
    private NettyServerHandler nettyServerHandler;
    @Bean
    public NettyServerHandler nettyServerHandler(){
        nettyServerHandler = new NettyServerHandler();
        return nettyServerHandler;
    }

    //这样不会获取到 ApplicationContext 上下文对象 (则不能使用注解解析器)
//    @Bean
//    public NettyServerHandler nettyServerHandler(){
//        return new NettyServerHandler();
//    }
//    

    @PreDestroy
    public void close(){
        logger.info("服务器关闭...");
        bossgroup.shutdownGracefully();
        workgroup.shutdownGracefully();
    }


    public void run(){
        try {
            //服务启动引导
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //组装NioEventLoopGroup
            serverBootstrap.group(bossGroup(), workGroup()) 
            //设置通道类型
            .channel(NioServerSocketChannel.class)
            //设置连接参数
            .option(ChannelOption.SO_BACKLOG, 1024)
            .option(ChannelOption.SO_KEEPALIVE, true)
            //设置日志
            .handler(new LoggingHandler(LogLevel.INFO))
            //配置入栈出栈handler
            .childHandler(new ChannelInitializer<SocketChannel>() {

                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    // TODO Auto-generated method stub
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(new StringDecoder(Charset.forName("UTF-8")));
                    pipeline.addLast(new StringEncoder(Charset.forName("UTF-8")));
                    pipeline.addLast("nettyhandler",nettyServerHandler);

                }

            });
            //绑定端口号
            ChannelFuture sync = serverBootstrap.bind(8888).sync()
                    //添加监听
                    .addListener(new ChannelFutureListener() {

                        public void operationComplete(ChannelFuture future) throws Exception {
                            // TODO Auto-generated method stub
                            if(future.isSuccess()){
                    logger.info("netty服务器在端口:"+8888+" 启动监听!");
                            }else{
            logger.info(TimeUtils.getNowTime()+"端口:"+8888+" 绑定失败!");
                            }
                        }
                    });
            sync.channel().closeFuture().sync();

        } catch (Exception e) {
            // TODO: handle exception
            logger.error("服务器出现异常:"+e.getCause()+"");
            bossgroup.shutdownGracefully();
            workgroup.shutdownGracefully();
        }finally {
            //优雅关闭
            bossgroup.shutdownGracefully();
            workgroup.shutdownGracefully();
        }
    }
}

nettyServerHandler

@Component
public class NettyServerHandler extends ChannelInboundHandlerAdapter implements ApplicationContextAware{
    private Logger logger = LoggerFactory.getLogger(NettyServerHandler.class);

// 同(3)注解解析类
    private CommandBuilder commandBuilder;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //通过上下文获取 已经注入的CommandBuilder对象(有且只有这一种方法)
        this.commandBuilder = applicationContext.getBean(CommandBuilder.class);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // TODO Auto-generated method stub
        logger.info("服务器连接客户端成功");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // TODO Auto-generated method stub
        logger.info("服务器收到的消息:"+msg);
        if(msg instanceof String){
            String receive = (String)msg;
            logger.info("receiveMessage: "+receive);
//                                "/hello/hello|12";
            String[] split = receive.split("[|]");
            String cmd = split[0]; 
            String sendMessage = split[1]; 
            logger.info("cmd:"+cmd);
            logger.info("sendMessage:"+sendMessage);
            try {
                CommandAction commandAction = this.commandBuilder.getCommandAction(cmd);
                //使用反射原理 将消息发送给  controller
                Method method = commandAction.getMethod();
                Object invoke = method.invoke(commandAction.getClassName(), sendMessage);
                logger.info("返回信息:"+invoke.toString());

            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
                logger.error(""+e.getCause());
            }
        }
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // TODO Auto-generated method stub
        logger.info("服务器异常:"+cause.getMessage());
        ctx.channel().close();
    }    
}

(5).netty客户端连接测试

TestClient

public class TestClient {
    public static void main(String[] args) {
        EventLoopGroup workgroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workgroup).channel(NioSocketChannel.class)
            .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    // TODO Auto-generated method stub
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(new StringEncoder(Charset.forName("UTF-8")));
                    pipeline.addLast(new StringDecoder(Charset.forName("UTF-8")));
                    pipeline.addLast(new ClientHandler());
                }

            });
            ChannelFuture future = bootstrap.connect("192.168.18.106",8888).sync();
            future.addListener(new ChannelFutureListener() {

                public void operationComplete(ChannelFuture future) throws Exception {
                    // TODO Auto-generated method stub
                    if(future.isSuccess()){
                        System.out.println("客户端连接成功");
                    }else{
                        System.out.println("客户端连接失败");                
                    }
                }
            });
            String message = "/hello/hello|12";
            System.out.println("客户端发送的消息:"+message);
            future.channel().writeAndFlush(message);

            future.channel().closeFuture().sync();

        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

ClientHandler

public class ClientHandler extends ChannelInboundHandlerAdapter{

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // TODO Auto-generated method stub
        System.out.println("客户端收到的消息:"+msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // TODO Auto-generated method stub
        super.exceptionCaught(ctx, cause);
        System.out.println("客户端异常:"+cause.getMessage());
    }
}

文章标题:netty整合springboot

发布时间:2019-11-29, 15:02:14

最后更新:2019-11-29, 15:02:15