Java:例外の原因を知り、ユーザにわかりやすく通知する
問題
Javaの例外には若干のメッセージが含まれるのだが、それをそのままユーザに表示するわけにはいかない。また、トップの例外メッセージには途中の無駄な例外クラスが含まれている。
例えばこうだ。
javax.ws.rs.ProcessingException: java.net.SocketTimeoutException: Read timed out
at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:260)
at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:254)
(略)
Caused by: java.net.SocketTimeoutException: Read timed out
at java.base/java.net.SocketInputStream.socketRead0(Native Method)
あるいは
javax.ws.rs.ProcessingException: java.net.ConnectException: Connection refused: connect
at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:260)
(略)
Caused by: java.net.ConnectException: Connection refused: connect
at java.base/java.net.PlainSocketImpl.connect0(Native Method)
(略)
いずれにしても、例外が多段になるに連れて途中の例外クラス名がメッセージに含まれてしまう。これを除去し、既知の例外であれば、ユーザに日本語で通知するようにする。
例外の根本原因を保持するクラス
例外クラスをExceptionCause.getに入れれば、根本原因をExceptionCauseオブジェクトとして戻す。
public class ExceptionCause {
public final Class<?>clazz;
public final String message;
public ExceptionCause(Class<?>clazz, String message) {
this.clazz = clazz;
this.message = message;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ExceptionCause)) return false;
ExceptionCause that = (ExceptionCause)o;
return this.clazz.equals(that.clazz) && this.message.equals(that.message);
}
@Override
public int hashCode() {
return clazz.hashCode() + message.hashCode() * 7;
}
@Override
public String toString() {
return clazz + "," + message;
}
public static ExceptionCause get(Throwable e) {
while (e.getCause() != null) e = e.getCause();
return new ExceptionCause(e.getClass(), e.getMessage());
}
}
あとは、既知のパターンについて、マップを通じて日本語メッセージを取得する。
private static final Map<ExceptionCause, String>PROCESSING_EXCEPTION_MAP =
new HashMap<ExceptionCause, String>() {{
put(new ExceptionCause(java.net.ConnectException.class, "Connection refused: connect"),
"サーバに接続できません"
);
put(new ExceptionCause(java.net.SocketTimeoutException.class, "Read timed out"),
"サーバからの応答がありません");
}};
public static SomeException processingException(ProcessingException ex) {
ExceptionCause cause = ExceptionCause.get(ex);
String message = PROCESSING_EXCEPTION_MAP.get(cause);
if (message != null) return new SomeException(ex, message);
return new SomeException(ex, cause.message);
}