BeanValidationの結果をExceptionHandlerでメッセージ出力させようと思って色々やっていた足跡みたいなものです。 とりあえず、あとから振り返りが出来るようにするためのメモ。 (独自の実装クラスとか ありますが そのあたりの説明は省略します)
事象
全く同じ事象を経験されていた方がいました。
対処
そこで、ふと思いました。
生成するタイミング(つまりFactory)だったらInjectionできるんじゃないかな?と。
結論
ExceptionHandlerFactory にInjectしたインスタンスを
ExceptionHandlerに渡せば できました。
実装
injectionしたいインターフェース
public interface ViewMessage { public void appendMessage(Set<ConstraintViolation<Object>> validatedResults); }
injectionしたいクラス
public class JsfViewMessage implements ViewMessage { @Override public void appendMessage(Set<ConstraintViolation<Object>> validatedResults) { FacesContext facesContext = FacesContext.getCurrentInstance(); MessageInterpolatorFactory interpolatorFactory = MessageInterpolatorFactory.of("Messages", "FormMessages", "FormLabels"); MessageInterpolator interpolator = interpolatorFactory.create(); validatedResults.stream() .map(result -> { return interpolator.toMessage(result); }) .distinct() .forEach(message -> { FacesMessage facemsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, message, null); facesContext.addMessage(null, facemsg); }); // リダイレクトしてもFacesMessageが消えないように設定 facesContext.getExternalContext().getFlash().setKeepMessages(true); } }
Producer
@Named @Dependent public class ViewMessageProducer { @Produces public ViewMessage getViewMessage() { return new JsfViewMessage(); } }
ExceptionHandlerFactory
public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory { private final ExceptionHandlerFactory parent; public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) { this.parent = parent; } @Inject ViewMessage message; @Override public ExceptionHandler getExceptionHandler() { ExceptionHandler handler = new CustomExceptionHandler(parent.getExceptionHandler(), message); return handler; } }
ExceptionHandler
メッセージ出力の実装とか ちょっとしていますが、ポイントはコンストラクタで Factoryで取得したインスタンスを使うところ。
public class CustomExceptionHandler extends ExceptionHandlerWrapper { private final ExceptionHandler wrapped; private final ViewMessage viewMessage; CustomExceptionHandler(ExceptionHandler exception, ViewMessage viewMessage) { this.wrapped = exception; this.viewMessage = viewMessage; } @Override public ExceptionHandler getWrapped() { return this.wrapped; } @Override public void handle() { final Iterator<ExceptionQueuedEvent> it = getUnhandledExceptionQueuedEvents().iterator(); while (it.hasNext()) { ExceptionQueuedEventContext eventContext = it.next().getContext(); try { // ハンドリング対象のアプリケーション例外を取得 Throwable th = getRootCause(eventContext.getException()).getCause(); // 任意の例外毎に処理を行う this.handleBeanValidationException(th); } catch (IOException ex) { System.out.println(Arrays.toString(ex.getStackTrace())); } finally { // 未ハンドリングキューから削除する it.remove(); } } getWrapped().handle(); } void handleBeanValidationException(Throwable th) throws IOException { if (th instanceof BeanValidationException == false) { return; } BeanValidationException ex = (BeanValidationException) th; Set<ConstraintViolation<Object>> results = ex.getValidatedResults(); viewMessage.appendMessage(results); FacesContext context = FacesContext.getCurrentInstance(); String contextPath = context.getExternalContext().getRequestContextPath(); String currentPage = context.getViewRoot().getViewId(); context.getExternalContext().redirect(contextPath + currentPage); } }
さいごに
という感じで Exceptionを扱うことは出来るには出来たのですが
警告: #{userRegistrationAction.confirm()}: ddd.domain.validation.BeanValidationException javax.faces.FacesException: #{userRegistrationAction.confirm()}: ddd.domain.validation.BeanValidationException (以下、略)
標準出力に上述のログが出力される。。 (#{userRegistrationAction.confirm()} はトリガーとなったアクション)
このログは本来出力不要なものなので困った。。
Interceptorを使えば、当然 このようなログを出力することなく処理は可能。
デバッグで確認した限りでは、このログはExceptionHandlerの制御外で出力されているみたいなので、はてさてどうしたものか。。
結局、Interceptorでやるのが無難なのかなぁ。
追記
Loggerとか調整したら出来るかも、と思ったりもしたけど 個別実装でそういうことをするのは良くないな。
— vermeer (@_vermeer_) 2018年7月11日
Interceptor「と」ExceptionHandlerで対応することにしよう。
InterceptorマーカーのAnnotationを付与し忘れてしても安心な基盤、という感じ。
業務例外がログにあったら注釈漏れってことで改修。