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

數據包的class類應該怎樣定義?

    通過包結構的分析,以及編解碼過程的瞭解,編解碼階段被拆分成了兩個階段:

        一個階段是 JavaBean 與 Json串 的相互轉換;

        另一個階段是 GenericPackage 與 二進制信息流 的相互轉換;

    本文所介紹的主要內容是上述的 JavaBean 應該怎樣定義,才能更方便的進行使用,同時,也能方便的完成另一個階段的編解碼工作。

1. 定義三個枚舉類,分別表示數據包的 版本、頻道號、命令字。

2. 定義一個名爲Package的Annotation,裏面可以設置上述的三個枚舉變量。

    這樣的話,我們在設計 JavaBean 的 class 時,只要添加 Package 註解,就可以綁定數據包的類型信息。

3. 定義PackageType類,一個PackageType對象與一個JavaBean的類型相對應。

     這個類與上述註解的功能基本相同,只是這個類的對象更容易存儲和方便操作。

4. 具體的JavaBean,暫時提供 心跳,聊天消息,登錄的請求和應答,這幾個數據類型。

5. PackageFactory,能夠註冊PackageType與JavaBean類型的對應關係,並且支持你想查詢和創建JavaBean對象的功能。

package houlei.net.tcp.pkg;

public enum PackageVersion {

    Unknown(0),
    V10(0x0100);

    private short value;
    PackageVersion(int version) {
        this.value = (short)version;
    }

    public short getValue() {
        return value;
    }

    public static PackageVersion valueOf(short version) {
        for (PackageVersion pv : values()) {
            if (pv.value == version) {
                return pv;
            }
        }
        return Unknown;
    }

}

public enum PackageChannel {

    Unknown(0),
    Connect(1),
    Auth(2),
    Chat(3);

    private short value;
    PackageChannel(int channel) {
        this.value = (short)channel;
    }

    public short getValue() {
        return value;
    }

    public static PackageChannel valueOf(short channel) {
        for (PackageChannel pc : values()) {
            if (pc.value == channel) {
                return pc;
            }
        }
        return Unknown;
    }
}


public enum PackageCommand {

    Unknown(0),
    Hartbeat(0x0101),
    LoginReq(0x0201),
    LoginRsp(0x0202),
    ChatMessage(0x0301);

    private short value;

    PackageCommand(int command) {
        this.value = (short)command;
    }

    public short getValue() {
        return value;
    }

    public static PackageCommand valueOf(short channel) {
        for (PackageCommand pc : values()) {
            if (pc.value == channel) {
                return pc;
            }
        }
        return Unknown;
    }
}
package houlei.net.tcp.pkg;

import java.lang.annotation.*;

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Package {

    PackageVersion version() default PackageVersion.V10;
    PackageChannel channel();
    PackageCommand command();

}



public class PackageType {

    private final short version;
    private final short channel;
    private final short command;

    public PackageType(short version, short channel, short command) {
        this.version = version;
        this.channel = channel;
        this.command = command;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj != null && obj instanceof PackageType) {
            PackageType other = (PackageType) obj;
            return this.version == other.version && this.channel == other.channel && this.command == other.command;
        }
        return false;
    }

    @Override
    public int hashCode() {
        return (version&0xFFFF)<<16 | (channel&0xFFFF)<<8 | (command&0xFFFF);
    }

    @Override
    public String toString() {
        return JsonUtil.toString(this);
    }

    public short getVersion() {
        return version;
    }

    public short getChannel() {
        return channel;
    }

    public short getCommand() {
        return command;
    }

}
package houlei.net.tcp.pkg.conn;


import houlei.net.tcp.pkg.Package;
import houlei.net.tcp.pkg.PackageChannel;
import houlei.net.tcp.pkg.PackageCommand;
import houlei.net.tcp.pkg.PackageVersion;

@Package(version = PackageVersion.V10, channel = PackageChannel.Connect, command = PackageCommand.Hartbeat)
public class HartbeatPackage {

}

@Package(version = PackageVersion.V10, channel = PackageChannel.Auth, command = PackageCommand.LoginReq)
public class LoginRequestPackage {

    private String loginName;
    private String loginPwd;

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getLoginPwd() {
        return loginPwd;
    }

    public void setLoginPwd(String loginPwd) {
        this.loginPwd = loginPwd;
    }
}


@Package(version = PackageVersion.V10, channel = PackageChannel.Auth, command = PackageCommand.LoginRsp)
public class LoginResponsePackage {

    private boolean succeed;

    public boolean isSucceed() {
        return succeed;
    }

    public void setSucceed(boolean succeed) {
        this.succeed = succeed;
    }
}


import com.fasterxml.jackson.annotation.JsonValue;
import houlei.net.tcp.pkg.Package;
import houlei.net.tcp.pkg.PackageChannel;
import houlei.net.tcp.pkg.PackageCommand;
import houlei.net.tcp.pkg.PackageVersion;

@Package(version = PackageVersion.V10, channel = PackageChannel.Chat, command = PackageCommand.ChatMessage)
public class ChatMessage {

    public enum Type{
        Unkown,
        Unicast,
        Broadcase;
        @JsonValue
        public int value(){
            return super.ordinal();
        }
    }

    private Type type;
    private String message;
    private String sender;
    private String receiver;

    public Type getType() {
        return type;
    }

    public void setType(Type type) {
        this.type = type;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getReceiver() {
        return receiver;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }
}
package houlei.net.tcp.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonUtil {

    private static final ObjectMapper mapper = new ObjectMapper();


    public static String toString(Object object) {
        try {
            return mapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException(e);
        }
    }

    public static <T> T fromString(String json, Class<T> klass) {
        try {
            return (T)mapper.readValue(json, klass);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException(e);
        }
    }
    
}
package houlei.net.tcp.pkg;

import houlei.net.tcp.utils.LoaderUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.HashMap;

@Component
public class PackageFactory {

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

    protected final HashMap<PackageType, Class<?>> classes = new HashMap<>();
    protected final HashMap<Class<?>, PackageType> types = new HashMap<>();

    public PackageFactory(){
        init();
    }

    public void regist(Class<?> klass, short version, short channel, short command) {
        PackageType key = new PackageType(version, channel, command);
        if (classes.containsKey(key)) {
            throw new IllegalArgumentException("duplicat package type. version:"+version+", channel:"+channel+", command:"+command);
        }
        if (types.containsKey(klass)) {
            throw new IllegalArgumentException("duplicat package class. version:"+version+", channel:"+channel+", command:"+command);
        }
        classes.put(key, klass);
        types.put(klass, key);
        logger.debug("[PackageFactory] regist package => {}", klass.getName());
    }

    public void regist(Class<?> klass) {
        Package configure = klass.getAnnotation(Package.class);
        if (configure == null) {
            throw new IllegalArgumentException("package class ("+klass.getName()+") must have annotation @Package.");
        }
        regist(klass, configure.version().getValue(), configure.channel().getValue(), configure.command().getValue());
    }

    public <P> Class<P> getClass(short version, short channel, short command) {
        PackageType key = new PackageType(version, channel, command);
        return (Class<P>) classes.get(key);
    }

    public <P> P create(short version, short channel, short command) {
        Class<P> klass = getClass(version, channel, command);
        if (klass == null) {
            throw new IllegalArgumentException("[PackageFactory] not regist type . version="+version+", channel="+channel+", command="+command);
        }
        return create(klass);
    }

    public PackageType getPackageType(Class<?> klass) {
        return types.get(klass);
    }

    public static  <P> P create(Class<P> klass) {
        if (klass == null || klass == Void.class) {
            return null;
        }
        try {
            return klass.newInstance();
        } catch (InstantiationException |IllegalAccessException e) {
            throw new IllegalStateException("[PackageFactory] create instance failed.", e);
        }
    }

    protected void init() {

        LoaderUtil.loadClasses("houlei.net", PackageFactory.class.getClassLoader())
                .stream()
                .filter(klass->klass.isAnnotationPresent(Package.class))
                .forEach(type->regist(type));

    }


}
package houlei.net.tcp.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

public class LoaderUtil {

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

    private LoaderUtil(){}

    public static List<Class<?>> loadClasses(String packageName, ClassLoader loader) {
        LinkedList<String> classFiles = new LinkedList<>();
        try {
            Enumeration<URL> urls = loader.getResources("");
            while(urls.hasMoreElements()) {
                URL url = urls.nextElement();
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {
                    findClassFiles(classFiles, url.getPath(), new File(url.getPath()));
                }
                if ("jar".equals(protocol)) {
                    JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                    Enumeration<JarEntry> entries = jarFile.entries();
                    while(entries.hasMoreElements()) {
                        JarEntry jarEntry = entries.nextElement();
                        String file = jarEntry.getName();
                        if (file.endsWith(".class") && file.indexOf('$')<0) {
                            classFiles.add(file);
                        }
                    }
                }
            }
        } catch (IOException e) {
            logger.error("[PackageFactory] init failed. ", e);
        }

        return classFiles.stream()
                .map(file->file.replace('/','.').substring(0,file.length()-6))
                .filter(name->name.startsWith(packageName))
                .map(name-> {
                    try {
                        return loader.loadClass(name);
                    } catch (ClassNotFoundException e) {
                        logger.error("[PackageFactory] load class failed. ", e);
                        return Object.class;
                    }
                }).collect(Collectors.toList());

    }

    private static void findClassFiles(LinkedList<String> classFiles, String basePath, File path) {
        if (path.isFile()) {
            String file = path.getPath();
            if (file.endsWith(".class") && file.indexOf('$')<0) {
                file = file.substring(basePath.length());
                classFiles.add(file);
            }
        } else {
            File[] subs = path.listFiles();
            for (File sub : subs) {
                findClassFiles(classFiles, basePath, sub);
            }
        }
    }


}

上面的代碼示例中,我把幾個類的代碼都拷貝到一起了,自己拆分一下吧。

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