​ 最近有朋友向我询问一些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. nmap 查看主机上开放的端口

    作用: 检测网络上的主机检测主机上开放的端口检测操作系统,硬件地址,以及软件版本检测脆弱性的漏洞(Nmap的脚本) 扫描方式: 1. -sS     Tcp SYN Scan  不需要三次握手,速度快 ...

  2. Redis学习笔记(二)redis 底层数据结构

    在上一节提到的图中,我们知道,可以通过 redisObject 对象的 type 和 encoding 属性.可以决定Redis 主要的底层数据结构:SDS.QuickList.ZipList.Has ...

  3. ApacheCN Angular 译文集 20211114 更新

    Angular 专家级编程 零.前言 一.架构概述和在 Angular 中构建简单应用 二.将 AngularJS 应用迁移到 Angular 应用 三.使用 Angular CLI 生成具有最佳实践 ...

  4. URL跳转空白页参数传递封装

    这东西主要是为了vue和平时打开一个空白界面_blank时的参数传递,不涉及到浏览器存储(session,local等) 众所周知,请求传参无非就用的query和params,相对应就是get和pos ...

  5. Func<>用法

    Func是一个委托,委托里面可以存方法,Func<string,string>或Func<string,string,int,string>等 前几个是输入参数,最后一个是返回 ...

  6. listview界面显示

    1.布局写listview      2.找到listview           3.封装新闻数据到list集合中 ,目的是为adapter提供数据展示.     4.封装一个Adapter类继承B ...

  7. HOOK API(二) —— HOOK自己程序的 MessageBox

    转载来源:https://www.cnblogs.com/hookjc/ 0x00 前言 以下将给出一个简单的例子,作为HOOK API的入门.这里是HOOK 自己程序的MessageBox,即将自己 ...

  8. 循环retian

    1.循环retian基本概念 循环retain的场景 比如A对象retain了B对象,B对象retain了A对象 循环retain的弊端 这样会导致A对象和B对象永远无法释放 循环retain的解决方 ...

  9. iOS App 架构文章推荐

    iOS应用开发架构 iOS应用架构谈系列 阿里技术沙龙 2.2.1. Hybrid App 2.2.2. taobao 客户端架构 2.2.3. alipay 客户端架构   iOS APP 架构漫谈 ...

  10. 使用Reachability监测网络变化-陈鹏

    在appdelegate里面添加观察者,并启动监测 // 使用通知中心监听kReachabilityChangedNotification通知 [[NSNotificationCenter defau ...