Develop with pleasure!

福岡でCloudとかBlockchainとか。

SpringModulesでBeanValidator

Hibernateとか独自にBeanValidatorの実装を持ってるフレームワークはあるけど、Springには無いなーと思っていたら、SpringFrameworkにアドオンするツールセットとしてSpringModulesというコンポーネントが公開されている。
https://springmodules.dev.java.net/
で、この中にBeanValidationをサポートするBean Validation Frameworが存在する。これを利用すればSpringでもBeanValidatorが使用可能になる。ただ、まだ開発段階のようでメジャー化はしていない模様。

Web層のValidationについては、Struts2等のValidationの仕組みを使う方が判り易いが、Service層で引数として渡って来たモデルの値のValidationにServiceのメソッド実行前にAOPでBeanValidatorをかけるというのは有効じゃないかと思う。

てことで、早速SpringModules0.9をGetしてみる。ただ、サンプルコードではSpringMVCのControllerに対してBeanValidatorをかけてるのが全て。
なので、Interceptor自体は自作する必要がある。こんな感じで良いみたい。

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.validation.BindException;
import org.springframework.validation.Validator;
import org.springmodules.validation.bean.BeanValidator;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

public class ValidationInterceptor implements MethodInterceptor {

  // 使用するBeanValidatorをインジェクション
  @Resource(name = "beanValidator")
  private BeanValidator validator;

  public Object invoke(MethodInvocation invocation) throws Throwable {
    List<BindException> errors = new ArrayList<BindException>();

    Object[] args = invocation.getArguments();

    for (Object object : args) {
      if(object == null){
        continue;
      }
      if(validator.supports(object.getClass())) {
        BindException bindException = new BindException(object, object.getClass().getName());
        validator.validate(object, bindException);
        if (bindException.hasErrors()) {
          errors.add(bindException);
        }
      }
    }

    if (errors.size() > 0) {
      // エラー発生時はメッセージを保持するerrorsを呼び出し側に返せるように工夫が必要。
      throw new IllegalArgumentException();
    }

    return invocation.proceed();
  }
}

applicationContext.xml

<!--BeanValidatorを定義-->
<bean id="beanValidator" class="org.springmodules.validation.bean.BeanValidator">
  <property name="configurationLoader" ref="configurationLoader"/>
</bean>

<bean id="configurationLoader"
  class="org.springmodules.validation.bean.conf.loader.annotation.AnnotationBeanValidationConfigurationLoader"/>

<bean id="validationInterceptor" class="ValidationInterceptor "/>

<!--BeanValidatorの対象をAOPで設定-->
<aop:config>
  <aop:advisor advice-ref="validationInterceptor" pointcut="execution(public * org.hoge.service.*Impl.*(..))"/>
</aop:config>

な感じ。最後に、BeanValidatorをかけたいModelにアノテーションを付加する。

import java.io.Serializable;

import org.springmodules.validation.bean.conf.loader.annotation.handler.NotNull;

public class HogeModel implements Serializable {

  @NotNull
  private String hoge;
	
  public void setHoge(String hoge) {
    this.hoge = hoge;
  }

  public String getHoge() {
    return hoge;
  }
}

という形で実現可能。ただ、BeanValidatorをAOPでかけてるため、それがコードをメンテナンスする上で判り易いかと言えば、そうでは無いかと思う。ただ、冗長なバリデーションをSeviceクラス内で行うこともまた違うと思う。まぁ、この辺は、開発標準なり、スタンダードな実装方法として各方式が浸透しなければ有効な策は無いのかもしれない。

そういった意味では、BeanValidatorのJSR-303の標準化が早くリリースされてほしい。結局、SpringModulesやHibernateのBeanValidatorも、使用しているアノテーションが標準化されているものでは無いため、各モデルが特定のフレームワークに依存してしまうことになる。これが一番嫌だ…。