§处理表单提交
§启用/禁用表单模块
默认情况下,Play在启用PlayJava
sbt插件时包含Java表单模块(play-java-forms
),因此如果您已经在项目中启用了enablePlugins(PlayJava)
,则无需启用任何内容。
表单模块也以javaForms
的形式在PlayImport
中可用,可以在您的build.sbt
中使用libraryDependencies += javaForms
。
注意:如果您没有使用表单,则可以使用
PlayMinimalJava
sbt插件而不是PlayJava
来删除表单依赖项。这还允许您删除表单模块仅使用的几个传递依赖项,包括几个Spring模块和Hibernate验证器。
§定义表单
play.data
包包含几个助手来处理HTTP表单数据提交和验证。处理表单提交的最简单方法是定义一个包装现有类的play.data.Form
import play.libs.Files.TemporaryFile;
import play.mvc.Http.MultipartFormData.FilePart;
public class User {
protected String email;
protected String password;
protected FilePart<TemporaryFile> profilePicture;
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
public FilePart<TemporaryFile> getProfilePicture() {
return profilePicture;
}
public void setProfilePicture(FilePart<TemporaryFile> pic) {
this.profilePicture = pic;
}
}
上面的表单定义了email
和password
文本字段以及profilePicture
文件输入字段,这意味着相应的HTML表单必须使用multipart/form-data
编码才能上传文件。
如您所见,默认情况下,您必须定义getter和setter方法,以便Play能够访问表单字段。但是,您也可以通过在conf/application.conf
中设置play.forms.binding.directFieldAccess = true
来启用“直接字段访问”(对于所有表单)。在这种模式下,Play将忽略getter和setter方法,并尝试直接访问字段
import play.libs.Files.TemporaryFile;
import play.mvc.Http.MultipartFormData.FilePart;
public class User {
public String email;
public String password;
public FilePart<TemporaryFile> profilePicture;
}
注意: 当使用“直接字段访问”且 Play 在表单绑定期间无法访问字段(例如,如果字段或包含该字段的类未定义为
public
),Play 将尝试通过调用field.setAccessible(true)
来通过反射使字段可访问。根据 Java 版本(8+)、JVM 和安全管理器设置,这可能会导致有关非法反射访问的警告,或者在最坏的情况下,抛出SecurityException
要包装一个类,您必须将play.data.FormFactory
注入您的控制器,然后才能创建表单
Form<User> userForm = formFactory.form(User.class);
您可以只为特定表单启用“直接字段访问”,而不是为所有表单启用它
Form<User> userForm = formFactory.form(User.class).withDirectFieldAccess(true);
注意: 底层绑定是使用Spring 数据绑定器完成的。
此表单可以从HashMap<String,String>
(用于文本数据)和Map<String, FilePart<?>>
(用于文件数据)生成User
结果值
Map<String, String> textData = new HashMap<>();
textData.put("email", "[email protected]");
textData.put("password", "secret");
Map<String, FilePart<?>> files = new HashMap<>();
files.put("profilePicture", myProfilePicture);
User user = userForm.bind(lang, attrs, textData, files).get();
如果您在作用域中有一个请求,您可以直接从请求内容进行绑定
User user = userForm.bindFromRequest(request).get();
§定义约束
您可以使用JSR-380
(Bean Validation 2.0)注释定义在绑定阶段将检查的其他约束
public class User {
@Required protected String email;
protected String password;
public void setEmail(String email) {
this.email = email;
}
public String getEmail() {
return email;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
}
提示:
play.data.validation.Constraints
类包含多个内置验证注释。所有这些约束注释都定义为@Repeatable
。这使您可以例如在同一个元素上多次重复使用相同的注释,但每次使用不同的groups
。但是,对于某些约束,让它们重复本身是有意义的,例如@ValidateWith
/@ValidatePayloadWith
。
在下面进一步的高级验证部分,您将学习如何处理跨字段验证、部分表单验证或如何在验证期间使用注入的组件(例如,访问数据库)。
§处理绑定失败
当然,如果您能够定义约束,那么您需要能够处理绑定错误。
if (userForm.hasErrors()) {
return badRequest(views.html.form.render(userForm));
} else {
User user = userForm.get();
return ok("Got user " + user);
}
通常,如上所示,表单只是传递给模板。全局错误可以通过以下方式呈现
@if(form.hasGlobalErrors) {
<p class="error">
@for(error <- form.globalErrors) {
<p>@error.format(messages)</p>
}
</p>
}
可以使用error.format
以以下方式呈现特定字段的错误
@for(error <- form("email").errors) {
<p>@error.format(messages)</p>
}
请注意,error.format
接受 messages()
作为参数 - 这是一个在 play.18n.Messages
中定义的 JavaI18N 实例。
§使用初始默认值填充表单
有时您需要使用现有值填充表单,通常用于编辑。
userForm = userForm.fill(new User("[email protected]", "secret"));
提示:
Form
对象是不可变的 - 对bind()
和fill()
等方法的调用将返回一个包含新数据的新的对象。
§处理具有动态字段的表单
如果您需要从具有动态字段的 html 表单中检索数据,可以使用 DynamicForm
。
public Result hello(Http.Request request) {
DynamicForm requestData = formFactory.form().bindFromRequest(request);
String firstname = requestData.get("firstname");
String lastname = requestData.get("lastname");
return ok("Hello " + firstname + " " + lastname);
}
§注册自定义 DataBinder
如果您想定义从自定义对象到表单字段字符串的映射,反之亦然,您需要为该对象注册一个新的 Formatter。
您可以通过为 Formatters
注册一个提供程序来实现这一点,该提供程序将执行正确的初始化。
对于像 JavaTime 的 LocalTime
这样的对象,它可能看起来像这样
import java.text.ParseException;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import play.data.format.Formatters;
import play.data.format.Formatters.SimpleFormatter;
import play.i18n.MessagesApi;
@Singleton
public class FormattersProvider implements Provider<Formatters> {
private final MessagesApi messagesApi;
@Inject
public FormattersProvider(MessagesApi messagesApi) {
this.messagesApi = messagesApi;
}
@Override
public Formatters get() {
Formatters formatters = new Formatters(messagesApi);
formatters.register(
LocalTime.class,
new SimpleFormatter<LocalTime>() {
private Pattern timePattern = Pattern.compile("([012]?\\d)(?:[\\s:\\._\\-]+([0-5]\\d))?");
@Override
public LocalTime parse(String input, Locale l) throws ParseException {
Matcher m = timePattern.matcher(input);
if (!m.find()) throw new ParseException("No valid Input", 0);
int hour = Integer.valueOf(m.group(1));
int min = m.group(2) == null ? 0 : Integer.valueOf(m.group(2));
return LocalTime.of(hour, min);
}
@Override
public String print(LocalTime localTime, Locale l) {
return localTime.format(DateTimeFormatter.ofPattern("HH:mm"));
}
});
return formatters;
}
}
定义提供程序后,您必须绑定它
import com.google.inject.AbstractModule;
import play.data.format.Formatters;
public class FormattersModule extends AbstractModule {
@Override
protected void configure() {
bind(Formatters.class).toProvider(FormattersProvider.class);
}
}
最后,您必须在 application.conf
中禁用 Play 的默认 FormattersModule
,并改为启用您的模块
play.modules.enabled += "com.example.FormattersModule"
play.modules.disabled += "play.data.format.FormattersModule"
当绑定失败时,会创建一个错误键数组,消息文件中定义的第一个键将被使用。此数组通常包含
["error.invalid.<fieldName>", "error.invalid.<type>", "error.invalid"]
错误键由 Spring DefaultMessageCodesResolver 创建,根“typeMismatch”被替换为“error.invalid”。
§高级验证
Play 的内置验证模块在幕后使用 Hibernate Validator。这意味着我们可以利用在 JSR-380
(Bean Validation 2.0) 中定义的功能。Hibernate Validator 文档可以在这里找到 here。
§跨字段验证
为了验证整个对象的 state,我们可以使用 类级约束。
为了让您免于实现自己的类级约束的负担,Play 开箱即用地提供了这种约束的通用实现,它应该至少涵盖最常见的用例。
现在让我们看看它是如何工作的:要定义一个临时验证,您需要做的就是使用 Play 提供的类级约束 (@Validate
) 对您的表单类进行注释,并实现相应的接口(在本例中为 Validatable<String>
) - 这会强制您覆盖 validate
方法
import play.data.validation.Constraints;
import play.data.validation.Constraints.Validate;
import play.data.validation.Constraints.Validatable;
@Validate
public class User implements Validatable<String> {
@Constraints.Required protected String email;
protected String password;
@Override
public String validate() {
if (authenticate(email, password) == null) {
// You could also return a key defined in conf/messages
return "Invalid email or password";
}
return null;
}
// getters and setters
上面示例中返回的消息将成为全局错误。错误被定义为 play.data.validation.ValidationError
。
还要注意,在这个例子中,validate
方法和 @Constraints.Required
约束会同时被调用 - 所以无论 @Constraints.Required
是否成功,validate
方法都会被调用(反之亦然)。你将在后面学习如何引入顺序。
如你所见,Validatable<T>
接口接受一个类型参数,该参数决定 validate()
方法的返回值类型。
因此,根据你是否希望通过 validate()
方法向表单添加单个全局错误、一个错误(也可能是全局错误)或多个(可能是全局)错误,你必须使用 String
、ValidationError
或 List<ValidationError>
作为类型参数。Play 会忽略 validate 方法的任何其他返回值类型。
如果验证在 validate()
方法内部通过,你必须返回 null
或一个空的 List
。返回任何其他非 null
值(包括空字符串)都被视为验证失败。
当你对特定字段有额外的验证时,返回 ValidationError
对象可能会有用。
import play.data.validation.Constraints.Validate;
import play.data.validation.Constraints.Validatable;
import play.data.validation.ValidationError;
@Validate
public static class LoginForm implements Validatable<ValidationError> {
// fields, getters, setters, etc.
@Override
public ValidationError validate() {
if (authenticate(email, password) == null) {
// Error will be displayed for the email field:
return new ValidationError("email", "Invalid credentials");
}
return null;
}
}
你可以通过返回 List<ValidationError>
来添加多个验证错误。这可以用来为特定字段添加验证错误、全局错误,甚至混合使用这些选项。
import play.data.validation.Constraints.Validate;
import play.data.validation.Constraints.Validatable;
import play.data.validation.ValidationError;
import java.util.List;
import java.util.ArrayList;
@Validate
public static class SignUpForm implements Validatable<List<ValidationError>> {
// fields, getters, setters, etc.
@Override
public List<ValidationError> validate() {
final List<ValidationError> errors = new ArrayList<>();
if (authenticate(email, password) == null) {
// Add an error which will be displayed for the email field:
errors.add(new ValidationError("email", "Access denied"));
// Also add a global error:
errors.add(new ValidationError("", "Form could not be submitted"));
}
return errors;
}
}
如你所见,当使用空字符串作为 ValidationError
的键时,它会变成一个全局错误。
还有一点:你可以使用在 conf/messages
中定义的消息键,而不是写出错误消息,并向它们传递参数。当在模板中显示验证错误时,消息键及其参数将由 Play 自动解析。
// Global error without internationalization:
new ValidationError("", "Errors occurred. Please check your input!");
// Global error; "validationFailed" should be defined in `conf/messages` - taking two arguments:
new ValidationError("", "validationFailed", Arrays.asList(arg1, arg2));
// Error for the email field; "emailUsedAlready" should be defined in `conf/messages` - taking
// the email as argument:
new ValidationError("email", "emailUsedAlready", Arrays.asList(email));
§通过组进行部分表单验证
当用户提交表单时,可能存在你不想一次性验证所有约束,而只想验证其中一些的情况。例如,考虑一个 UI 向导,在每个步骤中,只有指定的约束子集应该被验证。
或者考虑一个 Web 应用程序的注册和登录流程。通常,对于这两个流程,你都希望用户输入电子邮件地址和密码。因此,这些流程将需要几乎相同的表单,除了注册流程,用户还需要输入密码确认。为了使事情更有趣,让我们假设用户也可以在已经登录的情况下,在设置页面上更改他的用户数据 - 这将需要第三个表单。
在这种情况下使用三个不同的表单并不是一个好主意,因为你无论如何都会对大多数表单字段使用相同的约束注释。如果你为 name
字段定义了 255 的最大长度约束,然后想将其更改为仅 100 的限制,该怎么办?你必须为每个表单更改它。可以想象,如果你忘记更新其中一个表单,这将很容易出错。
幸运的是,我们可以简单地 分组约束
import javax.validation.groups.Default;
import play.data.validation.Constraints;
import play.data.validation.Constraints.Validatable;
import play.data.validation.Constraints.Validate;
import play.data.validation.ValidationError;
@Validate(groups = {SignUpCheck.class})
public class PartialUserForm implements Validatable<ValidationError> {
@Constraints.Required(groups = {Default.class, SignUpCheck.class, LoginCheck.class})
@Constraints.Email(groups = {Default.class, SignUpCheck.class})
private String email;
@Constraints.Required private String firstName;
@Constraints.Required private String lastName;
@Constraints.Required(groups = {SignUpCheck.class, LoginCheck.class})
private String password;
@Constraints.Required(groups = {SignUpCheck.class})
private String repeatPassword;
@Override
public ValidationError validate() {
if (!checkPasswords(password, repeatPassword)) {
return new ValidationError("repeatPassword", "Passwords do not match");
}
return null;
}
// getters and setters
SignUpCheck
和 LoginCheck
组被定义为两个接口。
public interface SignUpCheck {}
public interface LoginCheck {}
对于注册流程,我们只需将 SignUpCheck
组传递给 form(...)
方法。
Form<PartialUserForm> form =
formFactory().form(PartialUserForm.class, SignUpCheck.class).bindFromRequest(request);
在这种情况下,电子邮件地址是必需的,并且必须是有效的电子邮件地址,密码和密码确认都是必需的,并且两个密码必须相等(因为@Validate
注释调用了validate
方法)。但是我们不关心名字和姓氏 - 它们可以为空,或者我们甚至可以从注册页面中排除这些输入字段。
对于登录过程,我们只传递LoginCheck
组。
Form<PartialUserForm> form =
formFactory().form(PartialUserForm.class, LoginCheck.class).bindFromRequest(request);
现在我们只需要输入电子邮件地址和密码 - 仅此而已。我们甚至不关心电子邮件是否有效。你可能不会向用户显示任何其他表单字段,因为我们无论如何都不会验证它们。
想象一下,我们还有一个页面,用户可以在其中更改用户数据(但不能更改密码)。
Form<PartialUserForm> form =
formFactory().form(PartialUserForm.class, Default.class).bindFromRequest(request);
这与以下内容完全相同。
Form<PartialUserForm> form =
formFactory().form(PartialUserForm.class).bindFromRequest(request);
在这种情况下,将验证以下约束:电子邮件地址是必需的,并且必须有效,并且名字和姓氏也是必需的 - 这是因为如果约束注释没有明确定义group
,则将使用Default
组。
请注意,我们没有检查任何密码约束:因为它们明确定义了group
属性,但没有包含Default
组,因此它们在这里不会被考虑。
如最后一个示例所示,当仅传递组javax.validation.groups.Default
时,你可以省略它 - 因为它本来就是默认的。
但是,一旦你传递了任何其他组,如果你希望在验证过程中考虑其任何字段,你也必须明确传递Default
组。
提示:你可以向
form(...)
方法传递任意多个组(不仅仅是一个)。为了明确起见:这些组将同时被验证 - 而不是一个接一个地验证。
对于高级用法,一组约束可以包含另一个组。你可以使用组继承来实现这一点。
§定义约束组的顺序
你可以按序列验证组。这意味着组将一个接一个地验证 - 但只有在前面的组成功验证后才会验证下一个组。(但是,现在无法确定组内约束验证的顺序 - 这将是Bean Validation 未来版本的一部分)。
基于上面的例子,让我们定义一个组序列
import javax.validation.GroupSequence;
import javax.validation.groups.Default;
@GroupSequence({Default.class, SignUpCheck.class, LoginCheck.class})
public interface OrderedChecks {}
现在我们可以使用它了
Form<PartialUserForm> form =
formFactory().form(PartialUserForm.class, OrderedChecks.class).bindFromRequest(request);
使用这个组序列将首先验证属于Default
组的所有字段(同样也包括没有定义组的字段)。只有当属于Default
组的所有字段验证成功后,才会验证属于SignUpCheck
组的字段,以此类推。
使用组序列尤其是在你有一个validate
方法需要查询数据库或执行其他阻塞操作时是一个好习惯:如果验证在基本级别失败(电子邮件无效、数字是字符串等),执行该方法就没有意义了。在这种情况下,你可能希望validate
方法只在检查所有其他基于注解的约束之前,并且只有在它们通过的情况下才被调用。例如,一个注册的用户应该输入一个有效的电子邮件地址,并且只有在它有效的情况下,才应该在之后进行数据库查找以检查电子邮件地址是否存在。
§将有效载荷传递给验证器
如果需要,你也可以将一个ValidationPayload
对象(它包含有时在验证过程中需要的有用信息)传递给validate
方法。
要传递这样的有效载荷,只需用@ValidateWithPayload
(而不是@Validate
)注解你的表单,并实现ValidatableWithPayload
(而不是Validatable
)
import java.util.Map;
import com.typesafe.config.Config;
import play.data.validation.Constraints.ValidatableWithPayload;
import play.data.validation.Constraints.ValidateWithPayload;
import play.data.validation.ValidationError;
import play.data.validation.ValidationPayload;
import play.i18n.Lang;
import play.i18n.Messages;
@ValidateWithPayload
public class ChangePasswordForm implements ValidatableWithPayload<ValidationError>
public static class ChangePasswordForm implements ValidatableWithPayload<ValidationError> {
// fields, getters, setters, etc.
@Override
public ValidationError validate(ValidationPayload payload) {
Lang lang = payload.getLang();
Messages messages = payload.getMessages();
TypedMap attrs = payload.getAttrs();
Config config = payload.getConfig();
// ...
}
}
§支持 DI 的自定义类级别约束
有时你需要更复杂的验证过程。例如,当用户注册时,你想检查他的电子邮件地址是否已存在于数据库中,如果存在,则验证应该失败。
因为约束支持运行时依赖注入和,我们可以轻松地创建自己的自定义(类级别)约束,它可以注入一个Database
对象,我们可以在以后的验证过程中使用它。当然,你也可以注入其他组件,如MessagesApi
、JPAApi
等。
注意: 每个跨关注点只需要创建一个类级别约束。例如,我们将在本节中创建的约束是可重用的,并且可以用于所有需要访问数据库的验证过程。Play 不提供任何带有依赖注入组件的通用类级别约束的原因是,Play 不知道您的项目中可能启用了哪些组件。
首先,让我们设置一个接口,其中包含我们将在表单中实现的 validate
方法。您可以看到该方法传递了一个 Database
对象(查看 数据库文档)。
- 无负载
-
import play.db.Database; public interface ValidatableWithDB<T> { public T validate(final Database db); }
- 有负载
-
import play.data.validation.Constraints.ValidationPayload; import play.db.Database; public interface ValidatableWithDB<T> { public T validate(final Database db, final ValidationPayload payload); }
我们还需要在表单类上添加的类级别注释
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Repeatable(ValidateWithDB.List.class)
@Constraint(validatedBy = ValidateWithDBValidator.class)
public @interface ValidateWithDB {
String message() default "error.invalid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/** Defines several {@code @ValidateWithDB} annotations on the same element. */
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
public @interface List {
ValidateWithDB[] value();
}
}
最后,这是我们的约束实现的样子
- 无负载
-
import javax.inject.Inject; import javax.validation.ConstraintValidatorContext; import play.data.validation.Constraints.PlayConstraintValidator; import play.db.Database; public class ValidateWithDBValidator implements PlayConstraintValidator<ValidateWithDB, ValidatableWithDB<?>> { private final Database db; @Inject public ValidateWithDBValidator(final Database db) { this.db = db; } @Override public void initialize(final ValidateWithDB constraintAnnotation) {} @Override public boolean isValid( final ValidatableWithDB<?> value, final ConstraintValidatorContext constraintValidatorContext) { return reportValidationStatus(value.validate(this.db), constraintValidatorContext); } }
- 有负载
-
import javax.inject.Inject; import javax.validation.ConstraintValidatorContext; import play.data.validation.Constraints.PlayConstraintValidatorWithPayload; import play.data.validation.Constraints.ValidationPayload; import play.db.Database; public class ValidateWithDBValidator implements PlayConstraintValidatorWithPayload<ValidateWithDB, ValidatableWithDB<?>> { private final Database db; @Inject public ValidateWithDBValidator(final Database db) { this.db = db; } @Override public void initialize(final ValidateWithDB constraintAnnotation) {} @Override public boolean isValid( final ValidatableWithDB<?> value, final ValidationPayload payload, final ConstraintValidatorContext constraintValidatorContext) { return reportValidationStatus(value.validate(this.db, payload), constraintValidatorContext); } }
注意: 不要将
ValidationPayload
和ConstraintValidatorContext
混淆:前者由 Play 提供,是您在处理 Play 中的表单时日常工作中使用的。后者由 Bean Validation 规范 定义,仅在 Play 内部使用 - 只有一个例外:当您编写自己的自定义类级别约束时,此类会出现,您只需要将其传递给reportValidationStatus
方法,无论如何。
如您所见,我们将 Database
对象注入到约束的构造函数中,并在稍后调用 validate
时使用它。使用运行时依赖注入时,Guice 会自动注入 Database
对象,但对于编译时依赖注入,您需要自己完成。
import play.ApplicationLoader;
import play.BuiltInComponentsFromContext;
import play.data.FormFactoryComponents;
import play.data.validation.MappedConstraintValidatorFactory;
import play.db.DBComponents;
import play.db.HikariCPComponents;
import play.filters.components.NoHttpFiltersComponents;
import play.routing.Router;
public class ValidateWithDBComponents extends BuiltInComponentsFromContext
implements FormFactoryComponents, DBComponents, HikariCPComponents, NoHttpFiltersComponents {
public ValidateWithDBComponents(ApplicationLoader.Context context) {
super(context);
}
@Override
public Router router() {
return Router.empty();
}
@Override
public MappedConstraintValidatorFactory constraintValidatorFactory() {
return new MappedConstraintValidatorFactory()
.addConstraintValidator(
ValidateWithDBValidator.class, new ValidateWithDBValidator(database("default")));
}
}
注意:您不需要自己创建
database
实例,它已经在实现的接口中定义了。
这样,您的验证器将在需要时可用。
在编写自己的类级别约束时,您可以将以下对象传递给 reportValidationStatus
方法:ValidationError
、List<ValidationError>
或 String
(作为全局错误处理)。Play 会忽略任何其他对象。
最后,我们可以使用自定义类级别约束来验证表单
- 无负载
-
import play.data.validation.Constraints; import play.data.validation.ValidationError; import play.db.Database; @ValidateWithDB public class DBAccessForm implements ValidatableWithDB<ValidationError> { @Constraints.Required @Constraints.Email private String email; @Constraints.Required private String firstName; @Constraints.Required private String lastName; @Constraints.Required private String password; @Constraints.Required private String repeatPassword; @Override public ValidationError validate(final Database db) { // Access the database to check if the email already exists if (User.byEmail(email, db) != null) { return new ValidationError("email", "This e-mail is already registered."); } return null; } // getters and setters
- 有负载
-
import play.data.validation.Constraints; import play.data.validation.Constraints.ValidationPayload; import play.data.validation.ValidationError; import play.db.Database; @ValidateWithDB public class DBAccessForm implements ValidatableWithDB<ValidationError> { @Constraints.Required @Constraints.Email private String email; @Constraints.Required private String firstName; @Constraints.Required private String lastName; @Constraints.Required private String password; @Constraints.Required private String repeatPassword; @Override public ValidationError validate(final Database db, final ValidationPayload payload) { // Access the database to check if the email already exists if (User.byEmail(email, db) != null) { return new ValidationError("email", "This e-mail is already registered."); } return null; } // getters and setters
提示:您可能已经注意到,您甚至可以实现多个接口,从而在您的表单类上添加多个类级别约束注释。通过验证组,您就可以只调用所需的验证方法(甚至在一次验证过程中调用多个方法)。
下一步:防止 CSRF 攻击
发现此文档中的错误?此页面的源代码可以在 这里 找到。在阅读 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区交流。