2017-03-02 26 views
2

我在Java中有一個Socket-Server。該套接字將接收具有特定結構的json字符串。代碼設計:從套接字到自定義處理程序類的JSON

{ 
    "command": "test", 
    "name": "Hallo Welt" 
} 

我不能改變這種結構。 「命令」的值將聲明內容的類型。

我收到從插座在此之後,我想調用不同的處理程序,處理這些不同的命令:

  • 命令「測試」> TestHandler實現CommandHandler
  • 命令「富」> FooHandler工具CommandHandler

如何將json轉換爲對象並將該對象綁定到特定處理程序?

這是我目前的做法: 我有一個名爲BaseCommand的模型類,它包含一個枚舉命令字段。

class BaseCommand { 
    public CommandType command; 
} 

class TestCommand extends BaseCommand { 
    public String name; 
} 

使用GSON我將JSON解析爲BaseCommand類。 之後我可以讀取命令類型。

我宣佈一個ENUM的命令類型映射到處理程序:

enum CommandType { 
    test(TestHandler.class), 
    foo(FooHandler.class); 

    public final Class<? extends CommandHandler> handlerClass;   

    public CommandTypes(Class<? extends CommandHandler> handlerClass) { 
     this.handlerClass = handlerClass; 
    } 
} 

我處理的是實現了這個接口:

public interface CommandHandler<T extends BaseCommand> { 
    void handle(T command); 
} 

現在我命令枚舉類型,並通過谷歌Guices MapBinder我可以讓Handler實例處理請求。 This works

// in class ... 
private final Map<CommandType, CommandHandler> handlers; 

@Inject ClassName(Map<CommandType, CommandHandler> handlers) { 
    this.handlers = handlers; 
} 

// in converter method 
private void convert(String json) { 
    BaseCommand baseCommand = GSONHelper().fromJson(json, BaseCommand.class); 

    // How can I get the CommandModel? 
    // If the commandType is "test" how can I parse TestCommand automatically? 

    ??? commandModel = GSONHelper().fromJson(json, ???); 

    handlers.get(baseCommand.command).handle(commandModel); 
} 

有沒有人知道我的問題的解決方案? 或者完全不同的方法呢?

最好的問候,邁克爾

回答

1

我怎樣才能獲得CommandModel?
如果commandType是「test」,我怎樣才能自動解析TestCommand?

您可以使用TypeAdapterFactory以最準確和最靈活的方式獲得最合適的類型適配器。下面的例子與您的類命名稍有不同,但我認爲這不是一個大問題。所以,讓我們假設你有下面的命令參數DTO聲明:

abstract class AbstractCommandDto { 

    final String command = null; 

} 
final class HelloCommandDto 
     extends AbstractCommandDto { 

    final String name = null; 

} 

現在你可以做一個特殊的TypeAdapterFactory做出某種期待,提前確定由命令參數名稱輸入命令。它可能看起來很複雜,但實際上TypeAdapterFactory並不是很難實現。請注意,JsonDeserializer可能是您的另一種選擇,但是除非您將其deserialize()方法委託給另一個支持Gson實例,否則您將失去自動反序列化。

final class AbstractCommandDtoTypeAdapterFactory 
     implements TypeAdapterFactory { 

    // The factory handles no state and can be instantiated once  
    private static final TypeAdapterFactory abstractCommandDtoTypeAdapterFactory = new AbstractCommandDtoTypeAdapterFactory(); 

    // Type tokens are used to define type information and are perfect value types so they can be instantiated once as well 
    private static final TypeToken<CommandProbingDto> abstractCommandProbingDtoTypeToken = new TypeToken<CommandProbingDto>() { 
    }; 

    private static final TypeToken<HelloCommandDto> helloCommandDtoTypeToken = new TypeToken<HelloCommandDto>() { 
    }; 

    private AbstractCommandDtoTypeAdapterFactory() { 
    } 

    static TypeAdapterFactory getAbstractCommandDtoTypeAdapterFactory() { 
     return abstractCommandDtoTypeAdapterFactory; 
    } 

    @Override 
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) { 
     // First, check if the incoming type is AbstractCommandDto 
     if (AbstractCommandDto.class.isAssignableFrom(typeToken.getRawType())) { 
      // If yes, then build a special type adapter for the concrete type 
      final TypeAdapter<AbstractCommandDto> abstractCommandDtoTypeAdapter = new AbstractCommandDtoTypeAdapter(
        gson, 
        gson.getDelegateAdapter(this, abstractCommandProbingDtoTypeToken), 
        (commandName, jsonObject) -> deserialize(gson, commandName, jsonObject), 
        dto -> getTypeAdapter(gson, dto) 
      ); 
      // Some cheating for javac... 
      @SuppressWarnings("unchecked") 
      final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) abstractCommandDtoTypeAdapter; 
      return typeAdapter; 
     } 
     // If it's something else, just let Gson pick up the next type adapter 
     return null; 
    } 

    // Create an AbstractCommandDto instance out of a ready to use JsonObject (see the disadvantages about JSON trees below) 
    private AbstractCommandDto deserialize(final Gson gson, final String commandName, final JsonObject jsonObject) { 
     @SuppressWarnings("unchecked") 
     final TypeToken<AbstractCommandDto> typeToken = (TypeToken<AbstractCommandDto>) resolve(commandName); 
     final TypeAdapter<AbstractCommandDto> typeAdapter = gson.getDelegateAdapter(this, typeToken); 
     return typeAdapter.fromJsonTree(jsonObject); 
    } 

    private TypeAdapter<AbstractCommandDto> getTypeAdapter(final Gson gson, final AbstractCommandDto dto) { 
     @SuppressWarnings("unchecked") 
     final Class<AbstractCommandDto> clazz = (Class<AbstractCommandDto>) dto.getClass(); 
     return gson.getDelegateAdapter(this, TypeToken.get(clazz)); 
    } 

    // Or any other way to resolve the class. This is just for simplicity and can be even extract elsewhere from the type adapter factory class 
    private static TypeToken<? extends AbstractCommandDto> resolve(final String commandName) 
      throws IllegalArgumentException { 
     switch (commandName) { 
     case "hello": 
      return helloCommandDtoTypeToken; 
     default: 
      throw new IllegalArgumentException("Cannot handle " + commandName); 
     } 
    } 

    private static final class AbstractCommandDtoTypeAdapter 
      extends TypeAdapter<AbstractCommandDto> { 

     private final Gson gson; 
     private final TypeAdapter<CommandProbingDto> probingTypeAdapter; 
     private final BiFunction<? super String, ? super JsonObject, ? extends AbstractCommandDto> commandNameToCommand; 
     private final Function<? super AbstractCommandDto, ? extends TypeAdapter<AbstractCommandDto>> commandToTypeAdapter; 

     private AbstractCommandDtoTypeAdapter(
       final Gson gson, 
       final TypeAdapter<CommandProbingDto> probingTypeAdapter, 
       final BiFunction<? super String, ? super JsonObject, ? extends AbstractCommandDto> commandNameToCommand, 
       final Function<? super AbstractCommandDto, ? extends TypeAdapter<AbstractCommandDto>> commandToTypeAdapter 
     ) { 
      this.gson = gson; 
      this.probingTypeAdapter = probingTypeAdapter; 
      this.commandNameToCommand = commandNameToCommand; 
      this.commandToTypeAdapter = commandToTypeAdapter; 
     } 

     @Override 
     public void write(final JsonWriter out, final AbstractCommandDto dto) 
       throws IOException { 
      // Just pick up a delegated type adapter factory and use it 
      // Or just throw an UnsupportedOperationException if you're not going to serialize command arguments 
      final TypeAdapter<AbstractCommandDto> typeAdapter = commandToTypeAdapter.apply(dto); 
      typeAdapter.write(out, dto); 
     } 

     @Override 
     public AbstractCommandDto read(final JsonReader in) { 
      // Here you can two ways: 
      // * Either "cache" the whole JSON tree into memory (JsonElement, etc,) and simplify the command peeking 
      // * Or analyze the JSON token stream in a more efficient and sophisticated way 
      final JsonObject jsonObject = gson.fromJson(in, JsonObject.class); 
      final CommandProbingDto commandProbingDto = probingTypeAdapter.fromJsonTree(jsonObject); 
      // Or just jsonObject.get("command") and even throw abstractCommandDto, AbstractCommandProbingDto and all of it gets away 
      final String commandName = commandProbingDto.command; 
      return commandNameToCommand.apply(commandName, jsonObject); 
     } 

    } 

    // A synthetic class just to obtain the command field 
    // Gson cannot instantiate abstract classes like what AbstractCommandDto is 
    private static final class CommandProbingDto 
      extends AbstractCommandDto { 
    } 

} 

以及它如何使用:

public static void main(final String... args) { 
    // Build a command DTO-aware Gson instance 
    final Gson gson = new GsonBuilder() 
      .registerTypeAdapterFactory(getAbstractCommandDtoTypeAdapterFactory()) 
      .create(); 
    // Build command registry 
    final Map<Class<?>, Consumer<?>> commandRegistry = new LinkedHashMap<>(); 
    commandRegistry.put(HelloCommandDto.class, new HelloCommand()); 
    // Simulate and accept a request 
    final AbstractCommandDto abstractCommandDto = gson.fromJson("{\"command\":\"hello\",\"name\":\"Welt\"}", AbstractCommandDto.class); 
    // Resolve a command 
    final Consumer<?> command = commandRegistry.get(abstractCommandDto.getClass()); 
    if (command == null) { 
     throw new IllegalArgumentException("Cannot handle " + abstractCommandDto.command); 
    } 
    // Dispatch 
    @SuppressWarnings("unchecked") 
    final Consumer<AbstractCommandDto> castCommand = (Consumer<AbstractCommandDto>) command; 
    castCommand.accept(abstractCommandDto); 
    // Simulate a response 
    System.out.println(gson.toJson(abstractCommandDto)); 
} 

private static final class HelloCommand 
     implements Consumer<HelloCommandDto> { 

    @Override 
    public void accept(final HelloCommandDto helloCommandDto) { 
     System.out.println("Hallo " + helloCommandDto.name); 
    } 

} 

輸出:

喂世界報