Redis+spring+Netty 实现 dubbo框架-优化版本

之前写的东西因为有些netty的知识了解不够多,导致写的有点乱,这次花了一点时间重新整理了一下,写了此次版本,可能后续还可能优化,因为不足之处还有点多。。。

此次主要实现了对应的,心跳的机制,对应的断线重连,服务自动加入,并且能够自动的进行对应的随机访问的负载的功能。还实现了对应的自动扫描接口,并生产对应的代理类注入到对应的ioc容器中,并加上对应的自动DI操作,服务端对应的消息的分发模式,代码更加简单明白,网络部分,加上了对应的tcp粘包,json编解码。具体代码分析:

 

首先cient端:

 

package edu.mbb.config;

import edu.mbb.netty.ServerInfo;
import edu.mbb.netty.ServerSelector;
import edu.mbb.util.RedisUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.util.*;

@Component
public class RpcClientConfiguration implements ApplicationListener<ContextRefreshedEvent>, BeanDefinitionRegistryPostProcessor, ApplicationContextAware, DisposableBean {
    private NettyConfig nettyConfig;
    @Autowired
    private ServerSelector serverSelector;
    public static ApplicationContext applicationContext;

    private volatile boolean contextStart = false;
    /**
     * 扫包地址 多个包请逗号分割
     */
    @Value("${rpc.basePackage}")
    private String basePackage = "edu.mbb.service";


    @PostConstruct
    public void init() {
        //把对应的服务注册进入对应的环境中
        System.out.println("启动完成");

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        RpcInterfaceScanner scanner = new RpcInterfaceScanner(registry);
        scanner.scan(basePackage.split(","));
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RpcClientConfiguration.applicationContext = applicationContext;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    //容器启动完成时,开始定时任务
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!contextStart) {
            //启动定时任务,同步redis注册中心的服务
            System.out.println("开始进行对应的redis数据同步任务。一直从redis中获取最新的服务列表信息");
            SynServer();
            contextStart = true;
        }
    }

    @Override
    public void destroy() throws Exception {
        //关闭socket服务




        nettyConfig.close();
    }

    public NettyConfig getNettyConfig() {
        return nettyConfig;
    }

    public void setNettyConfig(NettyConfig nettyConfig) {
        this.nettyConfig = nettyConfig;
    }

    public String getBasePackage() {
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }


    public void SynServer() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("同步");
                    try {
                        Thread.sleep(1000 * 2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        RedisUtil redisUtil = RpcClientConfiguration.applicationContext.getBean(RedisUtil.class);
                        Set<Object> serverNames = redisUtil.sGet(Constant.REDIS_SERVER_LIST);
                        Set<String> serverList = ServerInfo.serverList;
                        /**
                         * 同步思路解析:
                         * 首页我们肯定只同步我们本地用的到的服务 也就是本地扫描包下面的对应的bean
                         * 所以我们的接口名称必须保持一直
                         * 因为我们只有按照一个标准 都按照 首字母小的方式 当作key
                         *我们从redis 上面 同步到 我们本地不不存在的 我们不需要的服务
                         * 我们不管他我们忽略这样的 服务 我们同步的也就是 增加ip
                         */
                        for (Object serverName : serverNames) {
                            //所以第一步我们过滤出我们需要的服务
                            //考虑后续加上namespace
                            if (ServerInfo.clientBeanNames.contains(serverName)) {
                                if (serverList.contains(serverName)) {
                                    //如果服务器上面没有服务,从服务器列表删除
                                    //虽然存在服务名称 没有ip和port
                                    //有可能提供的服务的 机器已经下线了 我们做下线处理
                                    long size = redisUtil.sGetSetSize(serverName.toString());
                                    //移除对应的 服务名称
                                    if (size == 0) {
                                        System.out.println("服务器上面当前的服务名称已经没有ip了,进行对应的移除name操作");
                                        redisUtil.del(serverName.toString());
                                        //本地移除对应的服务
                                        ServerInfo.clientBeanNames.remove(serverName);
                                        ServerInfo.serverList.remove(serverName);
                                        ServerInfo.serverBeanNameMap.remove(serverName);
                                        ServerInfo.serverNameMap.remove(serverName);
                                        continue;
                                    } else {
                                        //也放入对应的beanName
                                        String beanName = redisUtil.get(Constant.SERVER_NAME_BEAN + serverName).toString();
                                        if (StringUtils.isEmpty(beanName)) {
                                            System.out.println("当前服务找不到远程的包地址进行移除" + serverName);
                                            redisUtil.setRemove(Constant.REDIS_SERVER_LIST, serverName);
                                            ServerInfo.clientBeanNames.remove(serverName);
                                            ServerInfo.serverList.remove(serverName);
                                            ServerInfo.serverBeanNameMap.remove(serverName);
                                            ServerInfo.serverNameMap.remove(serverName);
                                            continue;
                                        }
                                        //看当前服务名称下面的ip 有没有新的
                                        //有新的 进行链接
                                        Set<Object> IPAndPorts = redisUtil.sGet(serverName.toString());

                                        IPAndPorts.stream()
                                                .forEach(ip -> {
                                                    System.out.println("ip同步开始" + ip);
                                                    //查看当前通道有没有这个ip
                                                    if (!ServerInfo.serverChannels.containsKey(ip)) {
                                                        //新增了ip
                                                        System.out.println("有服务上线了,开始同步ip" + ip);
                                                        if (ServerInfo.newServerNameMap.size() > 0 && ServerInfo.newServerNameMap.containsKey(serverName)) {
                                                            ServerInfo.newServerNameMap.get(serverName).add(ip.toString());
                                                        } else {
                                                            Set<String> ips = new HashSet<>();
                                                            ips.add(ip.toString());
                                                            ServerInfo.newServerNameMap.put(serverName.toString(), ips);
                                                        }

                                                    }
                                                });
                                        //同步服务列表

                                    }
                                }
                            }
                        }
                        System.out.println("一次同步完成!");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

}
ApplicationListener接口,为了在容器启动之后,启动对应的数据同步线程,而且只启动一次,加上对应的判断条件,
BeanDefinitionRegistryPostProcessor接口(重要接口mybatis-spring整合也是实现了这个接口哦)。Spring对外提供的扫包接口,可以自己指定对应的扫描包路径并且可以指定对应的类的规则,还有对应的加入到ioc容器并且,可以支持Autowired注解,此接口提供是为了外部的组件能够注册进入到spring中。

ApplicationContextAware接口,为了把Spring的Ioc容器注入到方法中,为了从ioc中取类方便使用。

DisposableBean接口:为了当容器销毁的时候,释放对应的链接,当前只有优雅的推出才有用。。。。。
basePackage:扫描的接口路径,就是在需要链接远端的接口
package edu.mbb.config;

import edu.mbb.netty.ServerInfo;
import edu.mbb.proxy.RpcInterfaceProxyFactroyBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;
import java.util.Set;

public class RpcInterfaceScanner extends ClassPathBeanDefinitionScanner {
    public RpcInterfaceScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        addFliter();
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        //记录存在的连接端 接口

        if (beanDefinitionHolders!=null&&beanDefinitionHolders.size()>0){
            //生成接口的动态代理类,类似MyBatis为mapper生成代理类
            for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
                String beanName = beanDefinitionHolder.getBeanName();
                ServerInfo.clientBeanNames.add(beanName);
                GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
                beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
                //通过FactoryBean创建
                beanDefinition.setBeanClass(RpcInterfaceProxyFactroyBean.class);
                beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            }
        }
        return beanDefinitionHolders;
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        return metadata.isInterface()&&metadata.isIndependent();
    }

    private void addFliter(){
        addIncludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                return true;
            }
        });
    }

}
RpcInterfaceScanner类。对应的自定义的扫包类,继承了对应的地址类扫描类ClassPathBeanDefinitionScanner 其中isCandidateComponent是为了过滤对应的类的规则,比如我定义了对应的必须是接口,没有进行继承的接口;
重要的实现:doScan 扫描类的关键,此处是吧对应的类变为对应的BeanDefinition的关键,
//把对应的当前的类的class 当成构造参数传入对应的 构建类的参数,所以我们可以在对应的factory中直接把当前的类的class 当成构造参数可以取到要构造的类的class beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
//指定对应的工厂类
beanDefinition.setBeanClass(RpcInterfaceProxyFactroyBean.class);

//使当前类支持对应的autoWired

beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);

//代表我们需要链接远端的类的接口总和,因为可能远端很多服务,我们只关心当前系统需要关心的服务。 

ServerInfo.clientBeanNames.add(beanName);

 

public class RpcInterfaceProxyFactroyBean<T> implements FactoryBean {
    private Class<T> clzz;

    public RpcInterfaceProxyFactroyBean(Class<T> clzz) {
        this.clzz = clzz;
    }

    @Override
    public T getObject() throws Exception {
        RedisUtil redisUtil = RpcClientConfiguration.applicationContext.getBean(RedisUtil.class);
        RpcClientConfiguration rpcClientConfiguration = RpcClientConfiguration.applicationContext.getBean(RpcClientConfiguration.class);
        NettyConfig nettyConfig=rpcClientConfiguration.getNettyConfig();
        //初始化配置
        if (nettyConfig==null){
            nettyConfig=new NettyConfig();
            rpcClientConfiguration.setNettyConfig(nettyConfig);
        }
        //获取可以提供该接口服务的IP地址和端口
        //缓存起来
        List<ServerInfo> serverIpList=new ArrayList<>();
        try {
            synchronized (ServerInfo.isInit){
                //如果服务器列表没有获取过,则先在redis获取服务器列表
                if (!ServerInfo.isInit){
                    //获取服务列表
                    Set<Object> servers = redisUtil.sGet(Constant.REDIS_SERVER_LIST);
                    ServerInfo.isInit=true;
                    if (servers==null||servers.size()==0){
                        throw new RuntimeException("has no servers in center!");
                    }
                    Set<String> serversList=new HashSet<>();
                    servers.stream()
                            .forEach(
                                    key->{
                                        if(ServerInfo.clientBeanNames.contains(key.toString())){
                                            serversList.add(key.toString());
                                        }
                                    }
                            );
                    ServerInfo.serverList.addAll(serversList);
                }
            }
            //获取对应的 serverName
            for (String serverName : ServerInfo.serverList) {
                //如果服务列表没有获取过,则从redis服务器中获取
                if (!ServerInfo.serverNameMap.containsKey(serverName)){
                    Set<Object> IPPortLists = redisUtil.sGet(serverName);
                    if (IPPortLists==null||IPPortLists.size()==0){
                        redisUtil.setRemove(Constant.REDIS_SERVER_LIST, serverName);
                    }
                    Set<String> iplist=new HashSet<>();
                    for(Object key : IPPortLists){
                        iplist.add(key.toString());
                    }
                    ServerInfo.serverNameMap.put(serverName,iplist);
                }
                if (serverName!=null&&serverName.equalsIgnoreCase(clzz.getSimpleName())){
                    Set<String> strings = ServerInfo.serverNameMap.get(serverName);
                    Iterator<String> iterator = strings.iterator();
                    while (iterator.hasNext()){
                        String ipAndPort = iterator.next();
                        String[] analysisIpAndPort = StringUtils.analysisIpAndPort(ipAndPort);
                        //分解服务器IP和端口成功
                        if (ipAndPort != null) {
                            //如果该客户端已经和该服务器连接,则不发起连接
                            if (ServerInfo.serverChannels.containsKey(ipAndPort)) {
                                ServerInfo serverInfo = new ServerInfo(analysisIpAndPort[0], Integer.parseInt(analysisIpAndPort[1]));
                                if(serverIpList.contains(serverInfo)){
                                    System.out.println("当前信道中已经存在此链接不在新增此链接");
                                    continue;
                                }
                                serverIpList.add(new ServerInfo(analysisIpAndPort[0], Integer.parseInt(analysisIpAndPort[1])));
                            } else {//否则和服务器发起连接
                                try {
                                    nettyConfig.clientInit(analysisIpAndPort[0], Integer.parseInt(analysisIpAndPort[1]));
                                }catch (Exception e){
                                    iterator.remove();
                                    e.printStackTrace();
                                    System.out.println("链接失败,选择下一个对应的服务列表数据");
                                    continue;
                                }
                                serverIpList.add(new ServerInfo(analysisIpAndPort[0], Integer.parseInt(analysisIpAndPort[1])));
                                String s = redisUtil.get(Constant.SERVER_NAME_BEAN + serverName).toString();
                                System.out.println(s);
                                ServerInfo.serverBeanNameMap.put(serverName, s);
                            }
                        }
                    }
                    Iterator<Map.Entry<String, Set<String>>> iterator1 = ServerInfo.serverNameMap.entrySet().iterator();
                    while (iterator1.hasNext()){
                        Set<String> value = iterator1.next().getValue();
                        if(value==null && value.size()==0){
                            iterator1.remove();
                        }
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }

        if (serverIpList.size()==0){
            throw new RuntimeException("["+clzz.getName() +"] has no implements in register center! ");
        }

        //生成连接对象
        Object o = Proxy.newProxyInstance(clzz.getClassLoader(), new Class[]{clzz}, new ClientProxyInvocation(serverIpList,clzz));
        return (T)o;
    }

    @Override
    public Class<T> getObjectType() {
        return clzz;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public Class<T> getItfClass() {
        return clzz;
    }

    public void setItfClass(Class<T> itfClass) {
        this.clzz = itfClass;
    }


}

 

//对应的接口的工厂bean

//因为在前面我们指定了 扫描的接口的class类为构造函数注入进来我们需要对应的构造函数接入对应的class类

public RpcInterfaceProxyFactroyBean(Class<T> clzz) {
    this.clzz = clzz;
}

对应的结构图:

 

//对应原子变量加锁,防止对此初始化

synchronized (ServerInfo.isInit){
    //如果服务器列表没有获取过,则先在redis获取服务器列表
    if (!ServerInfo.isInit){
        //获取服务列表
   //对应的就是serverName 的集合
        Set<Object> servers = redisUtil.sGet(Constant.REDIS_SERVER_LIST);
//表示已经初始化不在初始化
        ServerInfo.isInit=true;
        if (servers==null||servers.size()==0){
//假如不存在服务端服务,直接报错,不在进行初始化和启动
            throw new RuntimeException("has no servers in center!");
        }
        Set<String> serversList=new HashSet<>();
        servers.stream()
                .forEach(
                        key->{
//我们只关心我们当前系统中要调用的服务。
                            if(ServerInfo.clientBeanNames.contains(key.toString())){

                                serversList.add(key.toString());
                            }
                        }
                );
//把服务列表中远端上的服务
        ServerInfo.serverList.addAll(serversList);
    }
}

 

通过上面一部我们已经筛选出我们需要的服务名称列表了

 

下面需要把 提供这些服务名称的ip和port的进行链接;

 

 

ServerInfo.serverNameMap 记录对应的name 和ipPort的对应关系

假如当前的类和对应的serverName能对应上(保证接口和服务端接口名字一致。。。。。)

serverName!=null&&serverName.equalsIgnoreCase(clzz.getSimpleName())

//因为可能一个服务器提供了很多的服务,假如之前的服务也在这台服务器上,因为之前已经链接过了,此时不在进行链接,直接记录服务信息即可

if (ServerInfo.serverChannels.containsKey(ipAndPort)) 

 

通过ip和port 进行链接

nettyConfig.clientInit(analysisIpAndPort[0], Integer.parseInt(analysisIpAndPort[1]));

解读连接服务器方法

package edu.mbb.config;

import edu.mbb.handler.ClientJsonDecoder;
import edu.mbb.handler.ClientJsonEncoder;
import edu.mbb.handler.ResponseHandler;
import edu.mbb.listener.CollectionListener;
import edu.mbb.netty.ServerInfo;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class NettyConfig {
    private int workerGroupCount = Constant.NETTY_WORKERROUP_DEFAULT;
    //业务线程池相关配置
    private volatile NioEventLoopGroup worker = new NioEventLoopGroup(workerGroupCount);

    public NioEventLoopGroup getWorker() {
        return worker;
    }

    public void setWorker(NioEventLoopGroup worker) {
        this.worker = worker;
    }

    //客户端连接服务初始化
    public synchronized void clientInit(String ip, int port) {
        RpcClientConfiguration rpcClientConfiguration = RpcClientConfiguration.applicationContext.getBean(RpcClientConfiguration.class);
        NioEventLoopGroup worker = getWorker();
        rpcClientConfiguration.getNettyConfig().setWorker(worker);
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(worker)
                .channel(NioSocketChannel.class)
                //tcp/ip模式
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_KEEPALIVE, true)

                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000 * 10);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ChannelPipeline pipeline = socketChannel.pipeline();
                //加上对应的 心跳保活机制
                //读写超时时间 为 1分钟
                pipeline.addLast(new IdleStateHandler(1, 1, 1, TimeUnit.MINUTES));
                //参考我的 之前的文档对 tcp 粘包和分包的讲解
                //代表最大为长度为4的长度数据 只有一个length字段没有其他请求头字段
                pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                //编码的时候编上请求头 4个字节的长度字段
                pipeline.addLast(new LengthFieldPrepender(4));
                //入站消息从上往下 出站消息从下往上
                //入站消息 先解码为String类型
                pipeline.addLast(new StringDecoder());
                pipeline.addLast(new StringEncoder());
                //Json编解码器
                pipeline.addLast(new ClientJsonDecoder());
                pipeline.addLast(new ClientJsonEncoder());


                /**
                 * 此处流程为: 首先一个消息进来了 这个消息长成这个样子
                 *
                 * |——————————————————————|————————————————|
                 * |   length字段代表后面的报文长度(4个字节) |报文内容                         |
                 * |—————————————————————|—————————————————|
                 * 报文进来后-》LengthFieldBasedFrameDecoder——>报文内容(byteBuf)->StringDecoder(byteBuf->String)->ClientJsonDecoder(json String->ResponseImpl)->
                 *ResponseHandler(ResponseImpl)->
                 * ClientJsonEncoder(ResponseImpl->Json String)->
                 * StringEncoder(String)->LengthFieldPrepender(String->ByteBuf,并在这一步加上了length字段)
                 *
                 *
                 */
                pipeline.addLast(new ResponseHandler());
            }
        });
        try {
            //回执接口
            ChannelFuture channelFuture = bootstrap.connect(ip, port).sync();
            //监听回执接口
            channelFuture.addListener(new CollectionListener(channelFuture.channel(), ip, port));
        } catch (Exception ie) {
            throw new RuntimeException("connect server[" + ip + ":" + port + "] error!", ie);
        }
    }

    protected void close() {
        if (this.getWorker() != null) {
            this.getWorker().shutdownGracefully();
            System.out.println("客户端已经关闭对应的通过进行关闭");
        }
    }
}

 

catch (Exception e){
    iterator.remove();
    e.printStackTrace();
    System.out.println("链接失败,选择下一个对应的服务列表数据");
    continue;
}

//一定要catch 因为可能redis 上面有一些服务无法进行连接了,此时要把不能连接的服务剔除,并且能尝试下一个ip和port 并不能添加到通道list

 

 

/**
添加连接到对应的连接list
因为我远程的服务端的对应的接口包名和对应的client的包名是不一致的,我这里记录下对应的对应的关系
为了发给远端使用、
*/
serverIpList.add(new 
  ServerInfo(analysisIpAndPort[0],Integer.parseInt(analysisIpAndPort[1]))); 
String s = redisUtil.get(Constant.SERVER_NAME_BEAN + serverName).toString(); System.out.println(s);
ServerInfo.serverBeanNameMap.put(serverName, s);

 

为接口生成对应的代理对象:

参看代码,注释写的很多

 

服务端:值得说的是
对应的一个分发器的模式,因为我们要处理的消息类型有很多。我们通过对应的消息类型分发到不同的消息处理器中去,

private Map<MessageType, MessageHandler> handlerHashMap= new HashMap<>();

/**
 *  把消息处理类和消息类型进行绑定
 * @param messageType 消息类型
 * @param messageHandler 消息类型处理类
 */
public void resit(MessageType messageType, MessageHandler messageHandler){
    handlerHashMap.put(messageType,messageHandler);
}

/**
 * 通过消息类型 得到对应的处理类
 * @param messageType 消息类型
 * @return
 */
public MessageHandler getMessageHandler(MessageType messageType){
    return handlerHashMap.get(messageType);
}

采用的是map的方式,key是对应的消息类型,而对应的处理类是实现了统一接口的各种处理类,他们有共同的功能,处理的方法和对应的标识他是什么类型的处理类。

 

 

 

/**
 * 对应的消息类型的处理类方法
 * @param channelHandlerContext 通道
 * @param request 消息
 */
public void handler(ChannelHandlerContext channelHandlerContext,RequestImpl request);

/**
 * 得到当前类的消息类型
 * @return
 */
public MessageType getMessageType();

当对应的处理类实现了该方法,所有的类型消息,都能得到对应的处理类,当客户端来消息的时候。

   private ApplicationContext applicationContext;


/*    @Autowired
    @Qualifier(value = "MessageServerType")
    private MessageHandler messageServerType;

    @Autowired
    @Qualifier(value = "MessageHeartType")
    private MessageHandler messageHeartType;*/


    @Bean
    public MessageTypeHandlerMap messageTypeHandlerMap(){
       MessageTypeHandlerMap messageTypeHandlerMap=new MessageTypeHandlerMap();
      /*   messageTypeHandlerMap.resit(MessageType.HEARTBEAT,messageHeartType);

        messageTypeHandlerMap.resit(MessageType.HEARTBEAT,messageHeartType);*/
        /**
         * 通过类型加载出所有的 当前类型的bean
         */
        Map<String, MessageHandler> beansOfType = applicationContext.getBeansOfType(MessageHandler.class);
        for (String key: beansOfType.keySet()){
            MessageHandler messageHandler = beansOfType.get(key);
            //得到当前类的消息
            MessageType messageType = messageHandler.getMessageType();
            //注册到 map中
            messageTypeHandlerMap.resit(messageType,messageHandler);
        }
        return messageTypeHandlerMap;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }

注册采用的是注入对应的ioc容器 ,从容器中取出所有的实现该接口的实现类,并根据他们的对应类型注册到map中,方便后续使用。

 

其他的在说下run方法

 

 

   //通过反射拿到接口对象的class对象 beanName
    String itfName = request.getItfName();
    //Object bean = applicationContext.getBean(itfName);
    Class<?> clazz = Class.forName(itfName);
    // Class<?> clazz = Class.forName(itfName);
    //再从Spring容器里面拿出其实现类
    Object bean = applicationContext.getBean(clazz);
    //获取实现类的class对象
    Class<?> implClass = bean.getClass();
    //获取请求方法的Method对象
    Method method = implClass.getMethod(request.getMethodName(), request.getParamClassType());
    //通过反射执行其方法
    Object retObj = method.invoke(bean, request.getArgs());
    //组织返回对象
    ResponseImpl response = new ResponseImpl(request.getRequestId(), ResponseStatus.OK);
    response.setMessageType(request.getMessageType());
    if (retObj == null || retObj instanceof Void) {
        response.setResponseType(Void.class);
    } else {
        response.setContent(JacksonUtil.bean2Json(retObj));
        boolean isarray = isArray(retObj);
        boolean iscollection = isCollection(retObj);
        response.setArrayFlag(isarray);
        response.setCollectionFlag(iscollection);
        Class<?> retClass = retObj.getClass();
        if (response.isArrayFlag()) {
            response.setArrayType(retClass);
            response.setResponseType(retClass.getComponentType());
        } else if (response.isCollectionFlag()) {
            Collection collection = (Collection) retObj;
            for (Object o : collection) {
                response.setResponseType(o.getClass());
                break;
            }
        } else {
            response.setResponseType(retClass);
        }
        ;
    }
    handlerContext.channel().writeAndFlush(response);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
    ResponseImpl response = new ResponseImpl(request.getRequestId(), ResponseStatus.ERROR);
    response.setErrorMsg("server [" + request.getItfName() + "] not found!");
    response.setMessageType(MessageType.SERVER);
    handlerContext.channel().writeAndFlush(response);
} catch (NoSuchMethodException me) {
    me.printStackTrace();
    ResponseImpl response = new ResponseImpl(request.getRequestId(), ResponseStatus.ERROR);
    response.setErrorMsg("method [" + request.getMethodName() + "] args[" + Arrays.toString(request.getParamClassType()) + "] not found!");
    response.setMessageType(MessageType.SERVER);
    handlerContext.channel().writeAndFlush(response);
} catch (Exception ee) {
    ee.printStackTrace();
    ResponseImpl response = new ResponseImpl(request.getRequestId(), ResponseStatus.ERROR);
    response.setErrorMsg("method [" + request.getMethodName() + "] invoke error!" + ee.toString());
    response.setMessageType(MessageType.SERVER);
    handlerContext.channel().writeAndFlush(response);
}

因为从客户端发过来的时候 是带上对应的服务端的全限定类名的。我们在redis中,存储了对应的全限定类名。然后从ioc容器中取出对应的处理类,根据从客户端发过来的方法名,去调用对应的方法,更具是否有返回值,返回值类型,和参数类型,做对应的封装。数组的方法需要单独处理,还有对应的 list map。。。java类型,后续优化,可以自己实现,这个我留个接口在这里。

 

 

 

本人所有代码上传码云还有csdn 

地址:https://download.csdn.net/download/drsbbbl/19774196

码云:https://gitee.com/mibaowei/mbb-rpc.git

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值