このエントリで実装を見ていたとき、思いついた。 jyukutyo.hatenablog.com
Spring Expression Language(SpEL)でもバリデーションできそうだな〜と。Spring、Hibernate Validatorの利用が前提になってしまうけど。
@SpringELAssertアノテーションというのを作り、expressionの値にバリデーション処理を記述する感じ。
import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; @Target({ CONSTRUCTOR, METHOD }) @Retention(RUNTIME) @Constraint(validatedBy = SpringELAssertValidator.class) public @interface SpringELAssert { String message() default "{}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; String expression(); /** * Defines several {@link SpringELAssert} annotations on the same executable. */ @Target({ CONSTRUCTOR, METHOD }) @Retention(RUNTIME) @Documented public @interface List { SpringELAssert[] value(); } }
このアノテーションは@ParameterScriptAssertと内容は同じです。で、バリデーション処理はSpringELAssertValidatorクラスに記述する。
import java.util.List; import java.util.Map; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.constraintvalidation.SupportedValidationTarget; import javax.validation.constraintvalidation.ValidationTarget; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorContextImpl; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; @SupportedValidationTarget(ValidationTarget.PARAMETERS) public class SpringELAssertValidator implements ConstraintValidator<SpringELAssert, Object[]> { private Expression expression; @Override public void initialize(SpringELAssert constraintAnnotation) { ExpressionParser parser = new SpelExpressionParser(); this.expression = parser.parseExpression(constraintAnnotation.expression()); } @Override public boolean isValid(Object[] arguments, ConstraintValidatorContext context) { List<String> parameterNames = ((ConstraintValidatorContextImpl)context).getMethodParameterNames(); Map<String, Object> bindings = getBindings(arguments, parameterNames ); StandardEvaluationContext evaluationContext = new StandardEvaluationContext(); evaluationContext.setVariables(bindings); Boolean result = this.expression.getValue(evaluationContext, Boolean.class); return result == null ? true : result.booleanValue(); } private Map<String, Object> getBindings(Object[] arguments, List<String> parameterNames) { Map<String, Object> bindings = new HashMap()<>; for ( int i = 0; i < arguments.length; i++ ) { bindings.put( parameterNames.get( i ), arguments[i] ); } return bindings; } }
これもParameterScriptAssertValidatorクラスの内容を少し書き換えた。引数名とバリデーションする値をMapにできるので、このMapをSpELのコンテキストにvariableとしてセットする。expressionではvariableは"#name"で参照できる。
Variables can be referenced in the expression using the syntax #variableName. Variables are set using the method setVariable on the StandardEvaluationContext.
これで完成。コントローラはこうなる。
import java.util.Date; import jp.furyu.voyager.newsp.spring.validation.SpringELAssert; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.stereotype.Controller; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller @Validated public class HogeController { @RequestMapping(path = "hoge") @ResponseBody @SpringELAssert(expression = "#start.before(#end)") public String index(@RequestParam @DateTimeFormat(pattern="yyyyMMdd") Date start, @RequestParam @DateTimeFormat(pattern="yyyyMMdd") Date end) { return "hoge"; } }
@ParameterScriptAssertとほぼ変わらないけど、SpELで書けるのでここでいきなりJavaScriptを持ち出してくるよりはいいかな?