​ 最近有朋友向我询问一些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 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整合成功。那么看一下启动情况吧。

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 {
   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 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;

/**

  • 数据返回实体封装

  • Created by 叶云轩 on 2017/6/13-11:38

  • Concat tdg_yyx@foxmail.com

  • @param 通用变量

    */

    public class ResponseResult 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的一个接口](http://images2017.cnblogs.com/blog/1161505/201801/1161505-20180129131759375-896850881.png) 1. Server断点监听到远程调用,CloudApplication项目为Server端,我们可以看到接收到来自hart-oa的一个请求,参数一致。在CloudApplication中进行相应的处理后,返回到Client(hart-oa) ![Server断点监听到远程调用 ](http://images2017.cnblogs.com/blog/1161505/201801/1161505-20180129131759531-1793044895.png) 1. 返回信息到Client,可以看到我们(hart-oa)收到了来自CloudApplication的响应,结果是我们封装好的ResponseResult. ![返回信息到Client](http://images2017.cnblogs.com/blog/1161505/201801/1161505-20180129131804968-429317475.png) ------ 嗯 ~至此整合测试完成。要有想知道与Spring整合的,也可以留言或者邮箱:tdg_yyx@foxmail.com给我~~~ 若是想学习Netty的,对上面 代码看不懂的也可以留言或者邮箱:tdg_yyx@foxmail.com给我~~~

Netty与Spring Boot的整合的更多相关文章

  1. Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

    简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念.本文重点讲解Spring Boo ...

  2. Spring boot Mybatis 整合(完整版)

    个人开源项目 springboot+mybatis+thymeleaf+docker构建的个人站点开源项目(集成了个人主页.个人作品.个人博客) 朋友自制的springboot接口文档组件swagge ...

  3. Spring boot Mybatis 整合

    PS: 参考博客 PS: spring boot配置mybatis和事务管理 PS: Spring boot Mybatis 整合(完整版)   这篇博客里用到了怎样 生成 mybatis 插件来写程 ...

  4. spring boot 学习(二)spring boot 框架整合 thymeleaf

    spring boot 框架整合 thymeleaf spring boot 的官方文档中建议开发者使用模板引擎,避免使用 JSP.因为若一定要使用 JSP 将无法使用. 注意:本文主要参考学习了大神 ...

  5. Spring Boot 应用系列 5 -- Spring Boot 2 整合logback

    上一篇我们梳理了Spring Boot 2 整合log4j2的配置过程,其中讲到了Spring Boot 2原装适配logback,并且在非异步环境下logback和log4j2的性能差别不大,所以对 ...

  6. Spring boot Mybatis 整合(注解版)

    之前写过一篇关于springboot 与 mybatis整合的博文,使用了一段时间spring-data-jpa,发现那种方式真的是太爽了,mybatis的xml的映射配置总觉得有点麻烦.接口定义和映 ...

  7. Spring Boot:整合Spring Security

    综合概述 Spring Security 是 Spring 社区的一个顶级项目,也是 Spring Boot 官方推荐使用的安全框架.除了常规的认证(Authentication)和授权(Author ...

  8. Spring Boot:整合Swagger文档

    综合概述 spring-boot作为当前最为流行的Java web开发脚手架,越来越多的开发者选择用其来构建企业级的RESTFul API接口.这些接口不但会服务于传统的web端(b/s),也会服务于 ...

  9. Spring Boot:整合MyBatis框架

    综合概述 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以使用简单 ...

随机推荐

  1. python input函数

    函数 input() 让程序暂停运行,等待用户输入值,之后再把值赋给变量,输出.

  2. elasticsearch 申请basic证书

    如果elasticsearch使用低于6.3版本的,basic证书默认1个月,需要申请,可使用时间为1年. 申请地址为: https://license.elastic.co/registration ...

  3. Python初学笔记之可变类型、不可变类型

    python中 可变类型: 列表 list 字典 dict 不可变类型: 数字型:int.float.complex.bool.long 字符型 str 元组 tuple id(i):通过id查看变量 ...

  4. mpfu 位编辑处理?

    1.  国内的不用处理,其余都做 2.  判断是否是mp   的项目 3  设置位数编辑    请求中    和  检查中都要做 4  以某一画面作为基准,修改不同的值. 5  对象外的数据直接设置在 ...

  5. Understanding C++ Modules In C++20 (1)

    Compiling evironment: linux (ubuntu 16.04)+ gcc-10.2. The Post will clarify and discuss what modules ...

  6. checklist 编写指南

    checklist 编写工具 checklist文档格式推荐使用思维导图.比如 MindMaster 和 processon.我喜欢用这些平台或者软件的思维导图大纲模式来编写 checklist. c ...

  7. 社交网络分析的 R 基础:(一)初探 R 语言

    写在前面 3 年的硕士生涯一转眼就过去了,和社交网络也打了很长时间交道.最近突然想给自己挖个坑,想给这 3 年写个总结,画上一个句号.回想当时学习 R 语言时也是非常戏剧性的,开始科研生活时到处发邮件 ...

  8. 云服务器的windows系统上部署项目

    1.购买云服务器 以百度云服务器为例 https://cloud.baidu.com/campaign/midyear2020/index.html?track=cp:npinzhuan|pf:pc| ...

  9. @property中的copy关键字

    1.@property中的copy的作用 防止外界修改内部的值 @interface Person : NSObject @property (nonatomic, retain) NSString ...

  10. 项目架构(结构)搭建:主流结构(UITabBarController + 导航控制器)

    /* 项目架构(结构)搭建:主流结构(UITabBarController + 导航控制器) -> 项目开发方式 1.storyboard 2.纯代码 */ @interface AppDele ...