SpringBoot+Netty Json文字列の転送を実現(4)
パケット構造の解析と符号化プロセスの理解により、符号化段階は2つの段階に分割された.
1つの段階はJavaBeanとJson列の相互変換である.
もう一つの段階は GenericPackageとバイナリ情報ストリームの相互変換;
本稿で紹介する主な内容は,上述したJavaBeanがどのように定義すればより使いやすくなるか,また,別の段階の符号化作業を容易に完了できるかである.
1.パケットのバージョン、チャネル番号、コマンドワードを表す3つの列挙クラスを定義します.
2.PackageというAnnotationを定義し、上記の3つの列挙変数を設定できます.
これにより,JavaBeanのclassを設計する際にPackage注釈を追加するだけでパケットのタイプ情報をバインドすることができる.
3.JavaBeanのタイプに対応するPackageTypeクラスを定義します.
このクラスは上記の注釈の機能と基本的に同じですが、このクラスのオブジェクトは格納しやすく、操作しやすいだけです.
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 fromString(String json, Class 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> classes = new HashMap<>();
protected final HashMap, 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 Class
getClass(short version, short channel, short command) {
PackageType key = new PackageType(version, channel, command);
return (Class
) classes.get(key);
}
public
P create(short version, short channel, short command) {
Class
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 create(Class
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> loadClasses(String packageName, ClassLoader loader) {
LinkedList classFiles = new LinkedList<>();
try {
Enumeration 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 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 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);
}
}
}
}
上のコード例では、いくつかのクラスのコードをコピーして、自分で分割しましょう.