Websocketを使用してJavaでクライアントサーバーアプリケーションを開発しています。現在、すべてのクライアントメッセージは、以下に示すようにswitch-caseを使用して処理されます。
@OnMessage
public String onMessage(String unscrambledWord, Session session) {
switch (unscrambledWord) {
case "start":
logger.info("Starting the game by sending first word");
String scrambledWord = WordRepository.getInstance().getRandomWord().getScrambledWord();
session.getUserProperties().put("scrambledWord", scrambledWord);
return scrambledWord;
case "quit":
logger.info("Quitting the game");
try {
session.close(new CloseReason(CloseCodes.NORMAL_CLOSURE, "Game finished"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
String scrambledWord = (String) session.getUserProperties().get("scrambledWord");
return checkLastWordAndSendANewWord(scrambledWord, unscrambledWord, session);
}
サーバーはクライアントからの50を超える異なる要求を処理する必要があり、その結果、50を超えるcaseステートメントが生成されます。そして将来的には、それが成長することを期待しています。クライアントからのWebsocketメッセージを処理するためのより良い方法はありますか?または、これは通常どのように行われるのですか?
関数ポインタにマッピングすることで長いswitch-caseシナリオを回避するための、ハッシュテーブルの使用についてどこかで読みました。これはJavaで可能ですか?または、より良い解決策はありますか?
ありがとう。
少しテストと調査を行った後、長いスイッチケースのシナリオを回避するための2つの選択肢を見つけました。
匿名クラスの使用
匿名クラスメソッドが標準であり、次のコードはそれを実装する方法を示しています。この例ではRunnableを使用しました。さらに制御が必要な場合は、カスタムインターフェイスを作成します。
public class ClientMessageHandler {
private final HashMap<String, Runnable> taskList = new HashMap<>();
ClientMessageHandler() {
this.populateTaskList();
}
private void populateTaskList() {
// Populate the map with client request as key
// and the task performing objects as value
taskList.put("action1", new Runnable() {
@Override
public void run() {
// define the action to perform.
}
});
//Populate map with all the tasks
}
public void onMessageReceived(JSONObject clientRequest) throws JSONException {
Runnable taskToExecute = taskList.get(clientRequest.getString("task"));
if (taskToExecute == null)
return;
taskToExecute.run();
}
}
このメソッドの主な欠点は、オブジェクトの作成です。たとえば、実行するタスクは100種類あります。この匿名クラスのアプローチでは、単一のクライアントに対して100個のオブジェクトが作成されます。5,000を超えるアクティブな同時接続が存在するアプリケーションでは、オブジェクトの作成が多すぎると手頃な価格ではありません。この記事をご覧くださいhttp://blogs.microsoft.co.il/gilf/2009/11/22/applying-strategy-pattern-instead-of-using-switch-statements/
注釈付きの反射
私はこのアプローチが本当に好きです。メソッドによって実行されるタスクを表すカスタムアノテーションを作成しました。タスクは単一のクラスによって実行されるため、Strategyパターンメソッドのようにオブジェクト作成のオーバーヘッドはありません。
注釈
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TaskAnnotation {
public String value();
}
以下に示すコードは、クライアント要求キーをタスクを処理するメソッドにマップします。ここでは、マップがインスタンス化され、1回だけ入力されます。
public static final HashMap<String, Method> taskList = new HashMap<>();
public static void main(String[] args) throws Exception {
// Retrieves declared methods from ClientMessageHandler class
Method[] classMethods = ClientMessageHandler.class.getDeclaredMethods();
for (Method method : classMethods) {
// We will iterate through the declared methods and look for
// the methods annotated with our TaskAnnotation
TaskAnnotation annot = method.getAnnotation(TaskAnnotation.class);
if (annot != null) {
// if a method with TaskAnnotation is found, its annotation
// value is mapped to that method.
taskList.put(annot.value(), method);
}
}
// Start server
}
最後に、ClientMessageHandlerクラスは次のようになります。
public class ClientMessageHandler {
public void onMessageReceived(JSONObject clientRequest) throws JSONException {
// Retrieve the Method corresponding to the task from map
Method method = taskList.get(clientRequest.getString("task"));
if (method == null)
return;
try {
// Invoke the Method for this object, if Method corresponding
// to client request is found
method.invoke(this);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
logger.error(e);
}
}
@TaskAnnotation("task1")
public void processTaskOne() {
}
@TaskAnnotation("task2")
public void processTaskTwo() {
}
// Methods for different tasks, annotated with the corresponding
// clientRequest code
}
このアプローチの主な欠点は、パフォーマンスの低下です。このアプローチは、直接メソッド呼び出しアプローチと比較して低速です。さらに、動的計画法を扱っていない限り、多くの記事がReflectionから離れることを提案しています。
これらの回答を読んで、リフレクションについて詳しく知るリフレクションとは何ですか。なぜそれが役立つのですか。
リフレクションパフォーマンス関連記事
https://dzone.com/articles/the-performance-cost-of-reflection
最終結果
パフォーマンスへの影響を避けるために、アプリケーションでは引き続きswitchステートメントを使用しています。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加