Netty與SpringBoot整合
最近有朋友向我詢問一些Netty與SpringBoot整合的相關問題,這裡,我就總結了一下基本整合流程,也就是說,這篇文章 ,預設大家是對netty與Spring,SpringMVC的整合是沒有什麼問題的。現在,就進入正題吧。
Server端:
總的來說,服務端還是比較簡單的,自己一共寫了三個核心類。分別是
- NettyServerListener:服務啟動監聽器
- ServerChannelHandlerAdapter:通道介面卡,主要用於多執行緒共享
- RequestDispatcher:請求分排器
下面開始整合過程:
-
在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>
-
讓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) { } }
-
建立類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(); } } }
-
建立類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); } }
-
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; } }
-
application.yml檔案中對於netty的一個配置
netty: port: 11111
-
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; } }
-
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整合成功。那麼看一下啟動情況吧。
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);
}
}
-
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); } } } }
-
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); } }
-
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
//region 提供對外訪問的方法,無需更改
REQUEST_SUCCESS(10000, "請求成功"),
SERVER_ERROR(99999, "伺服器內部錯誤"),;
/**- 響應碼
*/
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());
}
}
來,我們測試一下遠端通訊:
- Client呼叫Server的一個介面。可以看到在hart-oa專案中,RPCEmployeeService沒有任何實現類,控制檯中列印了方法的呼叫 以及入參資訊
- Server斷點監聽到遠端呼叫,CloudApplication專案為Server端,我們可以看到接收到來自hart-oa的一個請求,引數一致。在CloudApplication中進行相應的處理後,返回到Client(hart-oa)
- 返回資訊到Client,可以看到我們(hart-oa)收到了來自CloudApplication的響應,結果是我們封裝好的ResponseResult.
嗯 ~至此整合測試完成。