SpringBoot + Netty 實現 Json字符串 的傳輸(五)

編解碼和數據包都解決了,下面來關注一下,業務處理方面的功能怎樣進行設計。

1. 構建 NettyServerHandler 類,完成業務邏輯的處理功能。

    A. 我們需要一個自定義的線程池,用來執行業務邏輯的處理代碼;

    B. 我們需要封裝一下業務處理環節,服務端的業務處理模式比較簡單,基本上採用一個請求對應一個應答的操作,所以,可以提取出統一的調用接口 ServerAction 進行業務處理,之後針對不同的請求設計對應的實現類即可。業務處理環節除了 ServerAction 以外,還需要 請求包的類型 和 應答包的類型 信息。

    C. 構建一個映射關係,Key是請求包的類型,Value是對應的業務處理環節。我們可以通過映射關係快速定位業務處理的 ServerAction 實現類的對象,以及,應答包JavaBean的實例化。

2. 與SpringBoot的集成

    A. 配置參數的自動裝配

    B. 映射關係的自動註冊

package houlei.net.tcp.hdr;

import io.netty.channel.ChannelHandlerContext;

@FunctionalInterface
public interface ServerAction<REQ, RSP> {

    void execute(ChannelHandlerContext ctx, REQ request, RSP response);

}
package houlei.net.tcp.hdr;

import houlei.net.tcp.pkg.PackageFactory;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Sharable
@Component
public class NettyServerHandler extends SimpleChannelInboundHandler  {

    private static Logger logger = LoggerFactory.getLogger(NettyServerHandler.class);

    private static class ServerActionBean {
        public Class<?> requestType;
        public Class<?> responseType;
        public ServerAction action;
    }
    private static final HashMap<Class, ServerActionBean> actions = new HashMap<>();

    @Resource
    private ApplicationContext applicationContext;
    @Value("${action.executor.corePoolSize:0")
    private int corePoolSize;
    @Value("${action.executor.maxPoolSize:16}")
    private int maxPoolSize;
    @Value("${action.executor.keepAliveTime:5000}")
    private int keepAliveTime;

    private ExecutorService serverActionExecutor;

    @PostConstruct
    public void init(){
        serverActionExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
        String[] names = applicationContext.getBeanNamesForType(ServerAction.class);
        for (String name : names) {
            ServerAction sa = applicationContext.getBean(name, ServerAction.class);
            regist(sa);
        }
    }

    public void regist(ServerAction action) {
        Type[] typeArgs = findActualTypeArguments(action, ServerAction.class);
        if (typeArgs!=null && typeArgs.length>1) {
            ServerActionBean bean = new ServerActionBean();
            bean.requestType  = (Class)typeArgs[0];
            bean.responseType = (Class)typeArgs[1];
            bean.action       = action;
            actions.put(bean.requestType, bean);
            logger.info("[NettyServerHandler][regist] regist server action : {}", action.getClass().getName());
        }
    }

    private static Type[] findActualTypeArguments(ServerAction sa, Class interfaceClass) {
        Type[] genTypes = sa.getClass().getGenericInterfaces();
        for (Type type : genTypes) {
            if (type instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) type;
                if (pt.getRawType() == interfaceClass) {
                    return pt.getActualTypeArguments();
                }
            }
        }
        return null;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {
        ServerActionBean bean = actions.get(o.getClass());

        if (bean != null) {
            serverActionExecutor.submit(()->{
                try {
                    Object request = o;
                    Object response = PackageFactory.create(bean.responseType);
                    bean.action.execute(ctx, request, response);
                    if (response != null) {
                        ctx.writeAndFlush(response);
                    }
                } catch (Exception ex) {
                    logger.error("[NettyServerHandler][ServerActionExecutor] execute action {} failed.", bean.action.getClass().getName(), ex);
                }
            });
        }

    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        logger.debug("NettyServerHandler#channelActive");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        logger.debug("NettyServerHandler#channelInactive");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        ctx.close();
        logger.error("NettyServerHandler#exceptionCaught", cause);
    }

}
package houlei.net.tcp.hdr.action;

import houlei.net.tcp.hdr.ServerAction;
import houlei.net.tcp.pkg.chat.ChatMessage;
import io.netty.channel.ChannelHandlerContext;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;

@Component
public class ChatMessageAction implements ServerAction<ChatMessage, ChatMessage> {

    @Override
    public void execute(ChannelHandlerContext ctx, ChatMessage request, ChatMessage response) {
        BeanUtils.copyProperties(request, response);
    }

}



@Component
public class HartbeatAction implements ServerAction<HartbeatPackage, HartbeatPackage> {

    @Override
    public void execute(ChannelHandlerContext ctx, HartbeatPackage request, HartbeatPackage response) {

    }

}


@Component
public class LoginAction implements ServerAction<LoginRequestPackage, LoginResponsePackage> {

    @Override
    public void execute(ChannelHandlerContext ctx, LoginRequestPackage request, LoginResponsePackage response) {
        response.setSucceed(false);
    }

}

代碼雖短,思想重要。所有 ServerAction 接口的實現類只要和SpringBoot集成了,就會被自動註冊,整合到業務處理流程當中。

由於是Demo程序,就沒有添加數據庫相關的代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章