​ 最近有朋友向我询问一些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. Docker 与 K8S学习笔记(二十)—— 使用Downward API向容器注入Pod信息

    Kubernetes在创建Pod时,会为Pod和容器设置一些额外的信息,比如Pod名称.Pod IP.Node IP.Label.Annotation.资源限制等,我们经常会在应用程序中使用到这些数据 ...

  2. JVM专题3: GC 垃圾回收

    合集目录 JVM专题3: GC 垃圾回收 什么是GC? 为什么要有 GC? Garbage Collection, 用于内存回收. 简述一下 Java 垃圾回收机制? 那些内存需要回收 虚拟机中程序计 ...

  3. Python 单元测试 生产HTML测试报告

    使用HTMLTestRunnerNew模块,生成单元测试的html报告,报告标题根据对应测试时间. import unittest from datetime import datetime from ...

  4. springcloud 实现简单的 消费者和生产者 模式(Restfule 的风格)

    一.springcloud 实现简单的 消费者和生产者 模式(Restfule 的风格) 1.实现简单的消费者和生产者 springcloud使用的http协议进行传输数据,也就是说springclo ...

  5. Servlet中的Filter 过滤器的简单使用!

    package com.aaa.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servle ...

  6. 【linux命令】wget

    感谢作者:八雲苗 链接:https://www.jianshu.com/p/1c3847fa7e45 wget官方文档:wget wget是一个使用HTTP,HTTPS,FTP和FTPS协议来下载文件 ...

  7. K8是—— yaml资源清单

    K8是-- yaml资源清单 一.yaml文件详解 1.Kubernetes支持YAML和JSON格式管理资源对象2.JSON格式:主要用于api接口之间消息的传递3.YAML格式:用于配置和管理,Y ...

  8. K8s 部署 Dashboard UI 仪表板 ——让一切可视化

    K8s 部署 Dashboard UI  仪表板   --让一切可视化 Dashboard 介绍 仪表板是基于Web的Kubernetes用户界面.您可以使用仪表板将容器化应用程序部署到Kuberne ...

  9. 长安战疫Wp

    长安战疫wp misc 八卦迷宫 直接走迷宫 cazy{zhanchangyangchangzhanyanghechangshanshananzhanyiyizhanyianyichanganyang ...

  10. Solution -「LOCAL」「cov. HDU 6864」找朋友

    \(\mathcal{Description}\)   Link.(几乎一致)   给定 \(n\) 个点 \(m\) 条边的仙人掌和起点 \(s\),边长度均为 \(1\).令 \(d(u)\) 表 ...