1. 程式人生 > >Netty與SpringBoot整合

Netty與SpringBoot整合

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)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.INFO));
            try {
                //設定事件處理
                serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new LengthFieldBasedFrameDecoder(nettyConfig.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 [email protected]
     */
    @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 [email protected]
     */
    @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 [email protected]
     */
    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 [email protected]
     */
    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 [email protected]
      */
      public class CustomChannelInitializerClient extends ChannelInitializer {
   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 [email protected]
*/
public class RPCProxyFactoryBean extends AbstractFactoryBeanimplements 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 [email protected]
*/
@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 [email protected]
*/
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 [email protected]
*/
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 [email protected]
    */
    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 [email protected]
 *
 * @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 [email protected]
 */
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


嗯 ~至此整合測試完成。