Netty与Spring Boot的整合

Netty与Spring Boot的整合

​ 最近有朋友向我询问一些Netty与SpringBoot整合的相关问题,这里,我就总结了一下基本整合流程,也就是说,这篇文章 ,默认大家是对netty与Spring,SpringMVC的整合是没有什么问题的。现在,就进入正题吧。

Server端:

总的来说,服务端还是比较简单的,自己一共写了三个核心类。分别是

  • NettyServerListener:服务启动监听器
  • ServerChannelHandlerAdapter:通道适配器,主要用于多线程共享
  • RequestDispatcher:请求分排器

下面开始集成过程:

  1. 在pom.xml中添加以下依赖

    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>5.0.0.Alpha2</version>
    </dependency>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-configuration-processor</artifactId>
       <optional>true</optional>
    </dependency>
    
  2. 让SpringBoot的启动类实现CommandLineRunner接口并重写run方法,比如我的启动类是CloudApplication.java

    @SpringBootApplication
    public class CloudApplication implements CommandLineRunner {
    
        public static void main(String[] args) {
            SpringApplication.run(CloudApplication.class, args);
        }
    
        @Override
        public void run(String... strings) {
        }
    }
    
  3. 创建类NettyServerListener.java

    
    // 读取yml的一个配置类
    import com.edu.hart.modules.constant.NettyConfig;
    // Netty连接信息配置类
    import com.edu.hart.modules.constant.NettyConstant;
    // 
    import com.edu.hart.rpc.util.ObjectCodec;
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
    import io.netty.handler.codec.LengthFieldPrepender;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.PreDestroy;
    import javax.annotation.Resource;
    
    /**
     * 服务启动监听器
     *
     * @author 叶云轩
     */
    @Component
    public class NettyServerListener {
        /**
         * NettyServerListener 日志输出器
         *
         * @author 叶云轩 create by 2017/10/31 18:05
         */
        private static final Logger LOGGER = LoggerFactory.getLogger(NettyServerListener.class);
        /**
         * 创建bootstrap
         */
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        /**
         * BOSS
         */
        EventLoopGroup boss = new NioEventLoopGroup();
        /**
         * Worker
         */
        EventLoopGroup work = new NioEventLoopGroup();
        /**
         * 通道适配器
         */
        @Resource
        private ServerChannelHandlerAdapter channelHandlerAdapter;
        /**
         * NETT服务器配置类
         */
        @Resource
        private NettyConfig nettyConfig;
    
        /**
         * 关闭服务器方法
         */
        @PreDestroy
        public void close() {
            LOGGER.info("关闭服务器....");
            //优雅退出
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }
    
        /**
         * 开启及服务线程
         */
        public void start() {
            // 从配置文件中(application.yml)获取服务端监听端口号
            int port = nettyConfig.getPort();
            serverBootstrap.group(boss, work);
            serverBootstrap.channel(NioServerSocketChannel.class);
                try {
                    //设置事件处理
                    serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(NettyConstant.getMaxFrameLength()
                                    , 0, 2, 0, 2));
                            pipeline.addLast(new LengthFieldPrepender(2));
                            pipeline.addLast(new ObjectCodec());
                            pipeline.addLast(channelHandlerAdapter);
                        }
                    });
                    LOGGER.info("netty服务器在[{}]端口启动监听", port);
                    ChannelFuture f = serverBootstrap.bind(port).sync();
                    f.channel().closeFuture().sync();
                } catch (InterruptedException e) {
                    LOGGER.info("[出现异常] 释放资源");
                    boss.shutdownGracefully();
                    work.shutdownGracefully();
                }
        }
    }
    
  4. 创建类ServerChannelHandlerAdapter.java - 通道适配器

    // 记录调用方法的元信息的类
    import com.edu.hart.rpc.entity.MethodInvokeMeta;
    import io.netty.channel.ChannelHandler.Sharable;
    import io.netty.channel.ChannelHandlerAdapter;
    import io.netty.channel.ChannelHandlerContext;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    
    /**
     * 多线程共享
     */
    @Component
    @Sharable
    public class ServerChannelHandlerAdapter extends ChannelHandlerAdapter {
      	/**
         * 日志处理
         */
        private Logger logger = LoggerFactory.getLogger(ServerChannelHandlerAdapter.class);
    	/**
         * 注入请求分排器
         */
        @Resource
        private RequestDispatcher dispatcher;
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            cause.printStackTrace();
            ctx.close();
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            MethodInvokeMeta invokeMeta = (MethodInvokeMeta) msg;
            // 屏蔽toString()方法
            if (invokeMeta.getMethodName().endsWith("toString()")
                    && !"class java.lang.String".equals(invokeMeta.getReturnType().toString()))
                logger.info("客户端传入参数 :{},返回值:{}",
                        invokeMeta.getArgs(), invokeMeta.getReturnType());
            dispatcher.dispatcher(ctx, invokeMeta);
        }
    }
    
  5. RequestDispatcher.java

    // 封装的返回信息枚举类
    import com.edu.hart.modules.communicate.ResponseCodeEnum;
    // 封装的返回信息实体类
    import com.edu.hart.modules.communicate.ResponseResult;
    // 封装的连接常量类
    import com.edu.hart.modules.constant.NettyConstant;
    // 记录元方法信息的实体类
    import com.edu.hart.rpc.entity.MethodInvokeMeta;
    // 对于返回值为空的一个处理
    import com.edu.hart.rpc.entity.NullWritable;
    // 封装的返回信息实体工具类
    import com.edu.hart.rpc.util.ResponseResultUtil;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelHandlerContext;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 请求分排器
     */
    @Component
    public class RequestDispatcher implements ApplicationContextAware {
        private ExecutorService executorService = Executors.newFixedThreadPool(NettyConstant.getMaxThreads());
        private ApplicationContext app;
    
        /**
         * 发送
         *
         * @param ctx
         * @param invokeMeta
         */
        public void dispatcher(final ChannelHandlerContext ctx, final MethodInvokeMeta invokeMeta) {
            executorService.submit(() -> {
                ChannelFuture f = null;
                try {
                    Class<?> interfaceClass = invokeMeta.getInterfaceClass();
                    String name = invokeMeta.getMethodName();
                    Object[] args = invokeMeta.getArgs();
                    Class<?>[] parameterTypes = invokeMeta.getParameterTypes();
                    Object targetObject = app.getBean(interfaceClass);
                    Method method = targetObject.getClass().getMethod(name, parameterTypes);
                    Object obj = method.invoke(targetObject, args);
                    if (obj == null) {
                        f = ctx.writeAndFlush(NullWritable.nullWritable());
                    } else {
                        f = ctx.writeAndFlush(obj);
                    }
                    f.addListener(ChannelFutureListener.CLOSE);
                } catch (Exception e) {
                    ResponseResult error = ResponseResultUtil.error(ResponseCodeEnum.SERVER_ERROR);
                    f = ctx.writeAndFlush(error);
                } finally {
                    f.addListener(ChannelFutureListener.CLOSE);
                }
            });
        }
    
        /**
         * 加载当前application.xml
         *
         * @param ctx
         * @throws BeansException
         */
        public void setApplicationContext(ApplicationContext ctx) throws BeansException {
            this.app = ctx;
        }
    }
    
  6. application.yml文件中对于netty的一个配置

    netty:
      port: 11111
    
  7. NettyConfig.java

    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.stereotype.Component;
    
    /**
     * 读取yml配置文件中的信息
     * Created by 叶云轩 on 2017/10/31 - 18:38
     * Concat tdg_yyx@foxmail.com
     */
    @Component
    @ConfigurationProperties(prefix = "netty")
    public class NettyConfig {
    
        private int port;
    
        public int getPort() {
            return port;
        }
    
        public void setPort(int port) {
            this.port = port;
        }
    }
    
  8. NettyConstanct.java

    import org.springframework.stereotype.Component;
    
    /**
     * Netty服务器常量
     * Created by 叶云轩 on 2017/10/31 - 17:47
     * Concat tdg_yyx@foxmail.com
     */
    @Component
    public class NettyConstant {
    
        /**
         * 最大线程量
         */
        private static final int MAX_THREADS = 1024;
        /**
         * 数据包最大长度
         */
        private static final int MAX_FRAME_LENGTH = 65535;
    
        public static int getMaxFrameLength() {
            return MAX_FRAME_LENGTH;
        }
    
        public static int getMaxThreads() {
            return MAX_THREADS;
        }
    }
    

    至此,netty服务端算是与SpringBoot整合成功。那么看一下启动情况吧。

    spring-boot netty集成启动

Client端:

Client我感觉要比Server端要麻烦一点。这里还是先给出核心类吧。

  • NettyClient : netty客户端
  • ClientChannelHandlerAdapter : 客户端通道适配器
  • CustomChannelInitalizer:自定义通道初始化工具
  • RPCProxyFactoryBean:RPC通信代理工厂

在Client端里。SpringBoot的启动类要继承SpringBootServletInitializer这个类,并覆盖SpringApplicationBuilder方法

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

@SpringBootApplication
public class OaApplication extends SpringBootServletInitializer {

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

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(OaApplication.class);
    }
}

  1. NettyClient.java

    // 记录元方法信息的实体类
    import com.edu.hart.rpc.entity.MethodInvokeMeta;
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.management.MBeanServer;
    
    /**
     * 客户端发送类
     * Created by 叶云轩 on 2017/6/16-16:58
     * Concat tdg_yyx@foxmail.com
     */
    public class NettyClient {
    
        private Logger logger = LoggerFactory.getLogger(MBeanServer.class);
        private Bootstrap bootstrap;
        private EventLoopGroup worker;
        private int port;
        private String url;
        private int MAX_RETRY_TIMES = 10;
    
        public NettyClient(String url, int port) {
            this.url = url;
            this.port = port;
            bootstrap = new Bootstrap();
            worker = new NioEventLoopGroup();
            bootstrap.group(worker);
            bootstrap.channel(NioSocketChannel.class);
        }
    
        public void close() {
            logger.info("关闭资源");
            worker.shutdownGracefully();
        }
    
        public Object remoteCall(final MethodInvokeMeta cmd, int retry) {
            try {
                CustomChannelInitializerClient customChannelInitializer = new CustomChannelInitializerClient(cmd);
                bootstrap.handler(customChannelInitializer);
                ChannelFuture sync = bootstrap.connect(url, port).sync();
                sync.channel().closeFuture().sync();
                Object response = customChannelInitializer.getResponse();
                return response;
            } catch (InterruptedException e) {
                retry++;
                if (retry > MAX_RETRY_TIMES) {
                    throw new RuntimeException("调用Wrong");
                } else {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    logger.info("第{}次尝试....失败", retry);
                    return remoteCall(cmd, retry);
                }
            }
        }
    }
    
  2. ClientChannelHandlerAdapter.java

    import com.edu.hart.rpc.entity.MethodInvokeMeta;
    import io.netty.channel.ChannelHandlerAdapter;
    import io.netty.channel.ChannelHandlerContext;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * Created by 叶云轩 on 2017/6/16-17:03
     * Concat tdg_yyx@foxmail.com
     */
    public class ClientChannelHandlerAdapter extends ChannelHandlerAdapter {
        private Logger logger = LoggerFactory.getLogger(ClientChannelHandlerAdapter.class);
        private MethodInvokeMeta methodInvokeMeta;
        private CustomChannelInitializerClient channelInitializerClient;
    
        public ClientChannelHandlerAdapter(MethodInvokeMeta methodInvokeMeta, CustomChannelInitializerClient channelInitializerClient) {
            this.methodInvokeMeta = methodInvokeMeta;
            this.channelInitializerClient = channelInitializerClient;
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            logger.info("客户端出异常了,异常信息:{}", cause.getMessage());
            cause.printStackTrace();
            ctx.close();
        }
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            if (methodInvokeMeta.getMethodName().endsWith("toString") && !"class java.lang.String".equals(methodInvokeMeta.getReturnType().toString()))
                logger.info("客户端发送信息参数:{},信息返回值类型:{}", methodInvokeMeta.getArgs(), methodInvokeMeta.getReturnType());
            ctx.writeAndFlush(methodInvokeMeta);
    
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            channelInitializerClient.setResponse(msg);
        }
    }
    
  3. CustomChannelInitializerClient.java

    import com.edu.hart.rpc.entity.MethodInvokeMeta;
    import com.edu.hart.rpc.entity.NullWritable;
    import com.edu.hart.rpc.util.ObjectCodec;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
    import io.netty.handler.codec.LengthFieldPrepender;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    
    /**
     * Created by 叶云轩 on 2017/6/16-15:01
     * Concat tdg_yyx@foxmail.com
     */
    public class CustomChannelInitializerClient extends ChannelInitializer<SocketChannel> {
    
        private Logger logger = LoggerFactory.getLogger(CustomChannelInitializerClient.class);
    
        private MethodInvokeMeta methodInvokeMeta;
    
        private Object response;
    
        public CustomChannelInitializerClient(MethodInvokeMeta methodInvokeMeta) {
            if (!"toString".equals(methodInvokeMeta.getMethodName())) {
                logger.info("[CustomChannelInitializerClient] 调用方法名:{},入参:{},参数类型:{},返回值类型{}"
                        , methodInvokeMeta.getMethodName()
                        , methodInvokeMeta.getArgs()
                        , methodInvokeMeta.getParameterTypes()
                        , methodInvokeMeta.getReturnType());
            }
            this.methodInvokeMeta = methodInvokeMeta;
        }
    
        public Object getResponse() {
            if (response instanceof NullWritable) {
                return null;
            }
            return response;
        }
    
        public void setResponse(Object response) {
            this.response = response;
        }
    
        @Override
        protected void initChannel(SocketChannel ch) {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new LengthFieldPrepender(2));
            pipeline.addLast(new LengthFieldBasedFrameDecoder(1024 * 1024, 0, 2, 0, 2));
            pipeline.addLast(new ObjectCodec());
            pipeline.addLast(new ClientChannelHandlerAdapter(methodInvokeMeta, this));
        }
    }
    
  4. RPCProxyFactoryBean.java

    import com.edu.hart.rpc.entity.MethodInvokeMeta;
    import com.edu.hart.rpc.util.WrapMethodUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.config.AbstractFactoryBean;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * Created by 叶云轩 on 2017/6/16-17:16
     * Concat tdg_yyx@foxmail.com
     */
    public class RPCProxyFactoryBean extends AbstractFactoryBean<Object> implements InvocationHandler {
        private Logger logger = LoggerFactory.getLogger(RPCProxyFactoryBean.class);
    
        private Class interfaceClass;
    
        private NettyClient nettyClient;
    
        @Override
        public Class<?> getObjectType() {
            return interfaceClass;
        }
    
        @Override
        protected Object createInstance() throws Exception {
            logger.info("[代理工厂] 初始化代理Bean : {}", interfaceClass);
            return Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            final MethodInvokeMeta methodInvokeMeta = WrapMethodUtils.readMethod(interfaceClass, method, args);
            if (!methodInvokeMeta.getMethodName().equals("toString")) {
                logger.info("[invoke] 调用接口{},调用方法名:{},入参:{},参数类型:{},返回值类型{}",
                        methodInvokeMeta.getInterfaceClass(), methodInvokeMeta.getMethodName()
                        , methodInvokeMeta.getArgs(), methodInvokeMeta.getParameterTypes(), methodInvokeMeta.getReturnType());
            }
            return nettyClient.remoteCall(methodInvokeMeta, 0);
        }
    
        public void setInterfaceClass(Class interfaceClass) {
            this.interfaceClass = interfaceClass;
        }
    
        public void setNettyClient(NettyClient nettyClient) {
            this.nettyClient = nettyClient;
        }
    }
    

    至此,netty-client与SpringBoot的集成了算完毕了。同样 ,在netty-client中也要加入相应的依赖

    不过上面server与client使用了一些公共的类和工具。下面也给列举中出来。

    MethodInvokeMeta.java
    import org.springframework.stereotype.Component;
    
    import java.io.Serializable;
    
    /**
     * 记录调用方法的元信息
     * Created by 叶云轩 on 2017/6/7-15:41
     * Concat tdg_yyx@foxmail.com
     */
    @Component
    public class MethodInvokeMeta implements Serializable {
    
        private static final long serialVersionUID = 8379109667714148890L;
        //接口
        private Class<?> interfaceClass;
        //方法名
        private String methodName;
        //参数
        private Object[] args;
        //返回值类型
        private Class<?> returnType;
        //参数类型
        private Class<?>[] parameterTypes;
    
        public Object[] getArgs() {
            return args;
        }
    
        public void setArgs(Object[] args) {
            this.args = args;
        }
    
        public Class<?> getInterfaceClass() {
            return interfaceClass;
        }
    
        public void setInterfaceClass(Class<?> interfaceClass) {
            this.interfaceClass = interfaceClass;
        }
    
        public String getMethodName() {
            return methodName;
        }
    
        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }
    
        public Class[] getParameterTypes() {
            return parameterTypes;
        }
    
        public void setParameterTypes(Class<?>[] parameterTypes) {
            this.parameterTypes = parameterTypes;
        }
    
        public Class getReturnType() {
            return returnType;
        }
    
        public void setReturnType(Class returnType) {
            this.returnType = returnType;
        }
    }
    
    NullWritable.java
    import java.io.Serializable;
    
    /**
     * 服务器可能返回空的处理
     * Created by 叶云轩 on 2017/6/16-16:46
     * Concat tdg_yyx@foxmail.com
     */
    public class NullWritable implements Serializable {
    
        private static final long serialVersionUID = -8191640400484155111L;
        private static NullWritable instance = new NullWritable();
    
        private NullWritable() {
        }
    
        public static NullWritable nullWritable() {
            return instance;
        }
    }
    
    ObjectCodec.java
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.MessageToMessageCodec;
    
    import java.util.List;
    
    public class ObjectCodec extends MessageToMessageCodec<ByteBuf, Object> {
    
        @Override
        protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) {
            byte[] data = ObjectSerializerUtils.serilizer(msg);
            ByteBuf buf = Unpooled.buffer();
            buf.writeBytes(data);
            out.add(buf);
        }
    
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {
            byte[] bytes = new byte[msg.readableBytes()];
            msg.readBytes(bytes);
            Object deSerilizer = ObjectSerializerUtils.deSerilizer(bytes);
            out.add(deSerilizer);
        }
    
    }
    
    ObjectSerializerUtils.java
    package com.edu.hart.rpc.util;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.*;
    
    /**
     * 对象序列化工具
     */
    public class ObjectSerializerUtils {
    
        private static final Logger logger = LoggerFactory.getLogger(ObjectSerializerUtils.class);
    
        /**
         * 反序列化
         *
         * @param data
         * @return
         */
        public static Object deSerilizer(byte[] data) {
            if (data != null && data.length > 0) {
                try {
                    ByteArrayInputStream bis = new ByteArrayInputStream(data);
                    ObjectInputStream ois = new ObjectInputStream(bis);
                    return ois.readObject();
                } catch (Exception e) {
                    logger.info("[异常信息] {}", e.getMessage());
                    e.printStackTrace();
                }
                return null;
            } else {
                logger.info("[反序列化] 入参为空");
                return null;
            }
        }
    
        /**
         * 序列化对象
         *
         * @param obj
         * @return
         */
        public static byte[] serilizer(Object obj) {
            if (obj != null) {
                try {
                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    ObjectOutputStream oos = new ObjectOutputStream(bos);
                    oos.writeObject(obj);
                    oos.flush();
                    oos.close();
                    return bos.toByteArray();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            } else {
                return null;
            }
        }
    }
    

    下面主要是用于Client端的:

    NettyBeanSacnner.java
    import com.edu.hart.rpc.client.RPCProxyFactoryBean;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.support.BeanDefinitionBuilder;
    import org.springframework.beans.factory.support.DefaultListableBeanFactory;
    
    import java.util.List;
    
    /**
     * 动态加载代理bean到Spring bean工厂
     */
    public class NettyBeanScanner implements BeanFactoryPostProcessor {
    
        private DefaultListableBeanFactory beanFactory;
    
        private String basePackage;
    
        private String clientName;
    
        public NettyBeanScanner(String basePackage, String clientName) {
            this.basePackage = basePackage;
            this.clientName = clientName;
        }
    
    
        /**
         * 注册Bean到Spring的bean工厂
         */
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            this.beanFactory = (DefaultListableBeanFactory) beanFactory;
            // 加载远程服务的接口
            List<String> resolverClass = PackageClassUtils.resolver(basePackage);
            for (String clazz : resolverClass) {
                String simpleName;
                if (clazz.lastIndexOf('.') != -1) {
                    simpleName = clazz.substring(clazz.lastIndexOf('.') + 1);
                } else {
                    simpleName = clazz;
                }
                BeanDefinitionBuilder gd = BeanDefinitionBuilder.genericBeanDefinition(RPCProxyFactoryBean.class);
                gd.addPropertyValue("interfaceClass", clazz);
                gd.addPropertyReference("nettyClient", clientName);
                this.beanFactory.registerBeanDefinition(simpleName, gd.getRawBeanDefinition());
            }
        }
    }
    
    PackageClassUtils.java

    这个类要说一下,主要是用来加载Server对应的接口的。因为在Client中RPC接口没有实现类,所以要自己将这些接口加载到Spring工厂里面。但是现在有个问题就是需要使用

    SpringBoot中application.yml
    basePackage: com.edu.hart.rpc.service.login;com.edu.hart.rpc.service.employee;com.edu.hart.rpc.service.authorization;
    

    这样的方式来加载,使用通配符的时候会加载不到,这个问题我还没有解决。

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.File;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 字节文件加载
     */
    public class PackageClassUtils {
    
        private final static Logger LOGGER = LoggerFactory.getLogger(PackageClassUtils.class);
    
        /**
         * 解析包参数
         *
         * @param basePackage 包名
         * @return 包名字符串集合
         */
        public static List<String> resolver(String basePackage) {
            //以";"分割开多个包名
            String[] splitFHs = basePackage.split(";");
            List<String> classStrs = new ArrayList<>();
            //s: com.yyx.util.*
            for (String s : splitFHs) {
                LOGGER.info("[加载类目录] {}", s);
                //路径中是否存在".*" com.yyx.util.*
                boolean contains = s.contains(".*");
                if (contains) {
                    //截断星号  com.yyx.util
                    String filePathStr = s.substring(0, s.lastIndexOf(".*"));
                    //组装路径 com/yyx/util
                    String filePath = filePathStr.replaceAll("\\.", "/");
                    //获取路径 xxx/classes/com/yyx/util
                    File file = new File(PackageClassUtils.class.getResource("/").getPath() + "/" + filePath);
                    //获取目录下获取文件
                    getAllFile(filePathStr, file, classStrs);
                } else {
                    String filePath = s.replaceAll("\\.", "/");
                    File file = new File(PackageClassUtils.class.getResource("/").getPath() + "/" + filePath);
                    classStrs = getClassReferenceList(classStrs, file, s);
                }
            }
            return classStrs;
        }
    
        /**
         * 添加全限定类名到集合
         *
         * @param classStrs 集合
         * @return 类名集合
         */
        private static List<String> getClassReferenceList(List<String> classStrs, File file, String s) {
            File[] listFiles = file.listFiles();
            if (listFiles != null && listFiles.length != 0) {
                for (File file2 : listFiles) {
                    if (file2.isFile()) {
                        String name = file2.getName();
                        String fileName = s + "." + name.substring(0, name.lastIndexOf('.'));
                        LOGGER.info("[加载完成] 类文件:{}", fileName);
                        classStrs.add(fileName);
                    }
                }
            }
            return classStrs;
        }
    
    
        /**
         * 获取一个目录下的所有文件
         *
         * @param s
         * @param file
         * @param classStrs
         */
        private static void getAllFile(String s, File file, List<String> classStrs) {
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files != null)
                    for (File file1 : files) {
                        getAllFile(s, file1, classStrs);
                    }
            } else {
                String path = file.getPath();
                String cleanPath = path.replaceAll("/", ".");
                String fileName = cleanPath.substring(cleanPath.indexOf(s), cleanPath.length());
                LOGGER.info("[加载完成] 类文件:{}", fileName);
                classStrs.add(fileName);
            }
        }
    }
    
    RemoteMethodInvokeUtil.java
    import com.edu.hart.rpc.entity.MethodInvokeMeta;
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * 消息处理类
     * Created by 叶云轩 on 2017/6/7-15:49
     * Concat tdg_yyx@foxmail.com
     */
    public class RemoteMethodInvokeUtil implements ApplicationContextAware {
    
        private ApplicationContext applicationContext;
    
        public Object processMethod(MethodInvokeMeta methodInvokeMeta) throws InvocationTargetException, IllegalAccessException {
            Class interfaceClass = methodInvokeMeta.getInterfaceClass();
            Object bean = applicationContext.getBean(interfaceClass);
            Method[] declaredMethods = interfaceClass.getDeclaredMethods();
            Method method = null;
            for (Method declaredMethod : declaredMethods) {
                if (methodInvokeMeta.getMethodName().equals(declaredMethod.getName())) {
                    method = declaredMethod;
                }
            }
            Object invoke = method.invoke(bean, methodInvokeMeta.getArgs());
            return invoke;
        }
    
        @Override
        public void setApplicationContext(ApplicationContext app) throws BeansException {
            applicationContext = app;
        }
    }
    
    WrapMethodUtils.java
    import com.edu.hart.rpc.entity.MethodInvokeMeta;
    
    import java.lang.reflect.Method;
    
    public class WrapMethodUtils {
        /**
         * 获取 method的元数据信息
         *
         * @param interfaceClass
         * @param method
         * @param args
         * @return
         */
        public static MethodInvokeMeta readMethod(Class interfaceClass, Method method, Object[] args) {
            MethodInvokeMeta mim = new MethodInvokeMeta();
            mim.setInterfaceClass(interfaceClass);
            mim.setArgs(args);
            mim.setMethodName(method.getName());
            mim.setReturnType(method.getReturnType());
            Class<?>[] parameterTypes = method.getParameterTypes();
            mim.setParameterTypes(parameterTypes);
            return mim;
        }
    }
    

下面的这些类我也会用在与前台通信时使用:

ResponseEnum.java
import java.io.Serializable;

/**
 * 响应码枚举类
 * Created by 叶云轩 on 2017/6/13-11:53
 * Concat tdg_yyx@foxmail.com
 */
public enum ResponseCodeEnum implements Serializable {

    // region authentication code
    REQUEST_SUCCESS(10000, "请求成功"),
    SERVER_ERROR(99999, "服务器内部错误"),;


    //region 提供对外访问的方法,无需更改
    /**
     * 响应码
     */
    private Integer code;
    /**
     * 响应信息
     */
    private String msg;

    ResponseCodeEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }


    public String getMsg() {
        return msg;
    }

    //endregion
}
ResponseResult.java
import java.io.Serializable;

/**
 * 数据返回实体封装
 * <p>
 * Created by 叶云轩 on 2017/6/13-11:38
 * Concat tdg_yyx@foxmail.com
 *
 * @param <T> 通用变量
 */
public class ResponseResult<T> implements Serializable {


    private static final long serialVersionUID = -3411174924856108156L;
    /**
     * 服务器响应码
     */
    private Integer code;
    /**
     * 服务器响应说明
     */
    private String msg;
    /**
     * 服务器响应数据
     */
    private T data;

    public ResponseResult() {

    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ResponseResult<?> that = (ResponseResult<?>) o;

        return (code != null ? code.equals(that.code) : that.code == null) && (msg != null ? msg.equals(that.msg) : that.msg == null) && (data != null ? data.equals(that.data) : that.data == null);
    }

    public Integer getCode() {

        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public int hashCode() {
        int result = code != null ? code.hashCode() : 0;
        result = 31 * result
                + (msg != null ? msg.hashCode() : 0);
        result = 31 * result + (data != null ? data.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "ResponseResult{"
                + "code="
                + code
                + ", msg='"
                + msg
                + '\''
                + ", data="
                + data
                + '}';
    }
}
ResponseResultUtil.java
import com.edu.hart.modules.communicate.ResponseCodeEnum;
import com.edu.hart.modules.communicate.ResponseResult;

/**
 * 返回结果工具类
 * Created by 叶云轩 on 2017/5/29-10:37
 * Concat tdg_yyx@foxmail.com
 */
public class ResponseResultUtil {

    /**
     * 请求失败返回的数据结构
     *
     * @param responseCodeEnum 返回信息枚举类
     * @return 结果集
     */
    public static ResponseResult error(ResponseCodeEnum responseCodeEnum) {
        ResponseResult ResponseResult = new ResponseResult();
        ResponseResult.setMsg(responseCodeEnum.getMsg());
        ResponseResult.setCode(responseCodeEnum.getCode());
        ResponseResult.setData(null);
        return ResponseResult;
    }

    /**
     * 没有结果集的返回数据结构
     *
     * @return 结果集
     */
    public static ResponseResult success() {
        return success(null);
    }

    /**
     * 成功返回数据结构
     *
     * @param o 返回数据对象
     * @return 返回结果集
     */
    public static ResponseResult success(Object o) {
        ResponseResult responseResult = new ResponseResult();
        responseResult.setMsg(ResponseCodeEnum.REQUEST_SUCCESS.getMsg());
        responseResult.setCode(ResponseCodeEnum.REQUEST_SUCCESS.getCode());
        responseResult.setData(o);
        return responseResult;
    }

    /**
     * 判断是否成功
     *
     * @param responseResult 请求结果
     * @return 判断结果
     */
    public static boolean judgementSuccess(ResponseResult responseResult) {
        return responseResult.getCode().equals(ResponseCodeEnum.REQUEST_SUCCESS.getCode());
    }
}

来,我们测试一下远程通信:

  1. Client调用Server的一个接口。可以看到在hart-oa项目中,RPCEmployeeService没有任何实现类,控制台中打印了方法的调用 以及入参信息

Client调用Server的一个接口

  1. Server断点监听到远程调用,CloudApplication项目为Server端,我们可以看到接收到来自hart-oa的一个请求,参数一致。在CloudApplication中进行相应的处理后,返回到Client(hart-oa)

Server断点监听到远程调用

  1. 返回信息到Client,可以看到我们(hart-oa)收到了来自CloudApplication的响应,结果是我们封装好的ResponseResult.

返回信息到Client


~至此整合测试完成。要有想知道与Spring整合的,也可以留言或者邮箱:tdg_yyx@foxmail.com给我~~~

若是想学习Netty的,对上面 代码看不懂的也可以留言或者邮箱:tdg_yyx@foxmail.com给我~~~

转载于:https://my.oschina.net/yzwjyw/blog/1614889

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值