如何在 Spring MVC 中执行自定义验证?

2024-04-25

我有以下代码,允许用户更新姓名和年份。

Model

@Entity
public class Person implements Serializable{

    private static final long serialVersionUID = 1L;

    private String name;

    private int year;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

}

控制器

@RequestMapping(value = "/person", method = RequestMethod.POST)
public ModelAndView editPerson(@ModelAttribute Person person)
{
    //perform operations

    //display results
    ModelAndView modelAndView = new ModelAndView("Person.html");
    modelAndView.addObject("personBind", person);
        
    return modelAndView;

}

View

<form action="person" method="post" th:object="${personBind}">
Name:
<input type="text" th:field="*{name}" />

Year:   
<input type="text" th:field="*{year}" />

现在我想在年份字段中进行一些验证。 例如,如果用户在该字段上输入字符串而不是数字,则当前代码将引发异常,因为它不允许在整数属性中设置字符串。

那么如何验证输入呢? 不想使用@Valid。想做一些自定义验证。

我发现执行此操作的方法是在模型中创建年份字段的字符串版本(getter/setter)。然后在视图中使用该 strYear 并在控制器中进行验证。就像下面更新的代码一样。这是正确的方法还是有更好的方法来做到这一点?我问这个问题是因为不确定为每个需要验证的数字属性创建字符串版本的 getter/setter 是否正确。看起来有很多重复。

Model

@Entity
public class Person implements Serializable{

    private static final long serialVersionUID = 1L;

    private String name;

    private int year;

    @Transient
    private String strYear;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }    
    
    public String getStrYear() {
        return strYear;
    }

    public void setStrYear(String strYear) {
        this.strYear = strYear;
    }

}

控制器

@RequestMapping(value = "/person", method = RequestMethod.POST)
public ModelAndView editPerson(@ModelAttribute Person person)
{
    //validate
    boolean valid = Validate(Person.getStrYear());

    if(valid==true)
    {
      Person.setYear(Integer.ParseInt(Person.getStrYear()));
      //save edit
    }
    else
    {//display validation messages}

    //display results
    ModelAndView modelAndView = new ModelAndView("Person.html");
    modelAndView.addObject("personBind", person);
        
    return modelAndView;

}

View

<form action="person" method="post" th:object="${personBind}">
Name:
<input type="text" th:field="*{name}" />

Year:   
<input type="text" th:field="*{strYear}" />

验证可能很棘手且困难,在进行验证时需要考虑一些事情......

验证注意事项

  • MVC 中的模型(模型、视图、控制器)与域模型不同,通常也不应该相同。请参阅@wim-deblauwe 的评论和下面的问答部分。

    • 通常,用户界面中显示的内容与域模型中可用的内容不同。
    • 将 @Valid 注释放入域模型中意味着在使用域模型的每种形式中,都将应用相同的 @Valid 规则。这并非总是如此。旁注:这可能不适用于超级简单的 CRUD(创建、读取、更新、删除)应用程序,但一般来说,大多数应用程序比纯粹的 CRUD 更复杂。
  • 由于 Spring 在表单提交期间自动设置值的方式,使用真实的域模型对象作为表单支持对象存在严重的安全问题。例如,如果我们使用带有密码字段的 User 对象作为表单支持对象,则浏览器开发人员工具可以操纵表单来发送密码字段的新值,现在该新值将被保留。

  • 通过 html 表单输入的所有数据实际上都是字符串数据,稍后需要将其转置为其实际数据类型(整数、双精度、枚举等)。

  • 在我看来,有不同类型的验证需要以不同的时间顺序进行。

    • 必需的检查发生在类型检查(整数、双精度、枚举等)、有效值范围之前,最后进行持久性检查(唯一性、先前持久值等)
    • If there are any errors in a temporal level, then don't check anything later.
      • 这可以防止最终用户在同一错误消息中收到诸如需要电话号码、电话号码不是数字、电话号码格式不正确等错误。
  • 验证器之间不应该存在任何时间耦合。这意味着如果字段是可选的,那么如果值不存在,“数据类型”验证器不应验证失败。请参阅下面的验证器。

Example

领域对象/业务对象:

@Entity
public class Person {

    private String identifier;
    private String name;
    private int year;

    public String getIdentifier() {
        return identifier;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }    
}

要通过 Spring MVC 控制器填充 html 表单,我们将创建一个表示该表单的特定对象。这还包括所有验证规则:

@GroupSequence({Required.class, Type.class, Data.class, Persistence.class, CreateOrUpdatePersonForm.class})
public class CreateOrUpdatePersonForm {

    @NotBlank(groups = Required.class, message = "Name is required.")
    private String name;

    @NotBlank(groups = Required.class, message = "Year is required.")
    @ValidInteger(groups = Type.class, message = "Year must be a number.")
    @ValidDate(groups = Data.class, message = "Year must be formatted yyyy.")
    private String year;

    public CreateOrUpdatePersonForm(Person person) {
        this.name = person.getName();
        this.year = Integer.valueOf(person.getYear);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getYearStr() {
        this.year;
    }

    public void setYearStr(String year) {
        this.year = year;
    }

    public int getYear() {
        return Integer.valueOf(this.year);
    }
}

然后在您的控制器中使用新的 CreateOrUpdatePersonForm 对象:

@Controller
public class PersonController {
    ...
    @ModelAttribute("command")
    public CreateOrUpdatePersonForm setupCommand(@RequestParam("identifier") Person person) {

        return new CreateOrUpdatePersonForm(person);
    }

    //@PreAuthorize("hasRole('ADMIN')")
    @RequestMapping(value = "/person/{person}/form.html", method = RequestMethod.GET)
    public ModelAndView getForm(@RequestParam("person") Person person) {

        return new ModelAndView("/form/person");
    }

    //@PreAuthorize("hasRole('ADMIN')")
    @RequestMapping(value = "/person/{person}/form.html", method = RequestMethod.POST)
    public ModelAndView postForm(@RequestParam("person") Person person, @ModelAttribute("command") @Valid CreateOrUpdatePersonForm form,
                                 BindingResult bindingResult, RedirectAttributes redirectAttributes) {

        ModelAndView modelAndView;

        if (bindingResult.hasErrors()) {

            modelAndView = new ModelAndView("/form/person");

        } else {

            this.personService.updatePerson(person.getIdentifier(), form);

            redirectAttributes.addFlashAttribute("successMessage", "Person updated.");

            modelAndView = new ModelAndView("redirect:/person/" + person.getIdentifier() + ".html");
        }

        return modelAndView;
    }
}

@ValidInteger 和 @ValidDate 是我们自己编写的验证器。

@有效整数:

public class ValidIntegerValidator implements ConstraintValidator<ValidInteger, String> {

    @Override
    public void initialize(ValidInteger annotation) {

    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {

        boolean valid = true;

        if (StringUtils.hasText(value)) {

            try {
                Integer.parseInteger
(value);

            } catch (NumberFormatException e) {

                valid = false;
            }
        }

        return valid;
    }
}

@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = ValidIntegerValidator.class)
@Documented
public @interface ValidInteger {

    String message() default "{package.valid.integer}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

@有效日期

public class ValidDateValidator implements ConstraintValidator<ValidDate, String> {

    private String format;

    @Override
    public void initialize(ValidDate annotation) {
        this.format = annotation.format();
    }

    @Override
    public boolean isValid(String inputDate, ConstraintValidatorContext constraintValidatorContext) {

        boolean valid = true;

        if (StringUtils.hasText(inputDate)) {

            SimpleDateFormat dateFormat = new SimpleDateFormat(format);

            dateFormat.setLenient(false);

            try {

                dateFormat.parse(inputDate);

            } catch (ParseException e) {

                valid = false;
            }
        }

        return valid;
    }
}

@Target({METHOD, FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = ValidDateValidator.class)
@Documented
public @interface ValidDate {

    String message() default "{package.dateformat}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String format();
}

然后在您的视图 jsp 或模板中,您需要显示错误(如果有):

<html>
    ...
    <body>
        <common:form-errors modelAttribute="command"/>
        ...
    </body>
</html>

验证还有很多事情需要处理,例如比较两个字段,或者访问持久层来验证人名是否唯一,但这需要更多解释。

问答

问:您能否提供链接来解释不使用领域模型作为 MVC 模型背后的想法?

答: 当然,实体 VS 领域模型 VS 视图模型 https://stackoverflow.com/questions/24588838/entities-vs-domain-models-vs-view-models and 实体 vs 模型 vs 视图模型 https://stackoverflow.com/questions/26563444/entity-vs-model-vs-view-model

TL;DR:为域模型和 MVC 模型使用不同的对象是因为它减少了应用程序层之间的耦合,并保护我们的 UI 和域模型免受任一层中的更改的影响。

其他考虑因素

数据验证需要在应用程序的所有入口点进行:UI、API 以及读入的任何外部系统或文件。

API 只是计算机的 UI,需要遵循与人类 UI 相同的规则。

接受来自互联网的数据充满危险。限制较多比限制较少要好。这还包括确保没有任何奇怪的字符cough微软的1252字符编码 https://en.wikipedia.org/wiki/Windows-1252cough、Sql 注入、JavaScript 注入,确保您的数据库设置为 unicode,并了解设置为 512 个字符的列(具体取决于语言)由于代码点而实际上只能处理 256 个字符。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Spring MVC 中执行自定义验证? 的相关文章

  • Hibernate 自定义架构创建

  • 使用 Gson 序列化时如何公开类名

    我的场景非常复杂 但总结如下 我试图了解编译器的源代码 并了解每个 AST 节点代表什么 我正在生成不同程序的 AST 的 JSON 序列化 然后检查可视化的 JSON 输出 它工作得很好 除了一个问题是在 Gson 中生成的 JSON 数
  • 无法从 TemporalAccessor 获取 OffsetDateTime

    当我这样做时 String datum 20130419233512 DateTimeFormatter formatter DateTimeFormatter ofPattern yyyyMMddHHmmss withZone ZoneI
  • CSS 未在 Spring Boot 中加载

    我是 spring 框架工作和 spring boot 的新手 我正在尝试使用 CSS javascript js 添加静态 html 文件 文件结构是 我的 html 文件头看起来像这样
  • RSA 加密-解密:BadPaddingException:数据必须以零开头

    对于一个被问了很多次的问题 我很抱歉向您询问您的技能 我有一个关于 RSA 加密的问题 我已经检查过有关此问题的其他主题 但没有找到任何有用的答案 我希望你能帮助我 我想读取一个文件 加密其内容 然后解密它并将这些解密的字节放入一个新文件中
  • 按对象值分组,统计后按最大对象属性设置组键

    我设法使用 Java 8 Streams API 编写了一个解决方案 该解决方案首先按对象 Route 的值对列表进行分组 然后计算每组中的对象数量 它返回一个映射 Route gt Long 这是代码 Map
  • 如何在 Java 中安装附加包?

    我对 Java 很陌生 我想使用名为的包中的一些功能daj 教程代码有以下几行 import daj import java util import java lang Math import Msg 但第一行和第四行会产生红色下划线 导致
  • 文件保存在文件系统中 VS 保存在数据库中

    我正在设计一个 servlet 或 Struts2 中的操作 用于文件 图像 文档等 下载 但我想知道哪种更好的方法可以将文件保留在文件系统和数据库中 只需保留文件的路径或将文件保留在数据库中 如 BLOB 我知道当我查询数据库时 哪里的
  • 可以混合使用 JVM 语言吗?即:Groovy 和 Clojure

    我知道你可以轻松地混合groovy java clojure java 无论什么JvmLang java 这是否也意味着我也可以让 clojure 和 groovy 代码进行交互 如果我使用 Grails 或 jRoR 我也可以在该环境中使
  • Keycloak 社交登录 REST API

    我已经为我的 keycloak 实例启用了谷歌社交登录 但我需要将其用作休息服务 是否有可用于执行此操作的端点 Keycloak 中没有 Google 身份验证 API 但您可以使用以下方法解决它代币交换 https www keycloa
  • Spring @Value 添加验证小于

    我使用以下属性值注入 我如何向此操作添加小于验证 我的意思是我想设置一个验证user maxpassiveday可以说 财产价值不得低于 100 Value user maxpassiveday int maxpassiveday 使用Sp
  • JSP 作为电子邮件模板

    有没有办法发送 MIME 电子邮件 其中电子邮件正文源自 JSP 我需要使用 Javamail 发送一封电子邮件 其中包含一个表格 我认为如果我可以使用 JSP 来完成所有格式设置和布局 将会很方便 在这个线程中 Java 电子邮件模板的建
  • 如何获取队列中的第 n 个项目?

    我的应用程序中有许多队列和优先级队列 我想轻松访问这些队列中的第 n 个项目 但没有看到使用 API 实现此目的的简单方法 我想我可以创建一个Iterator并迭代到第 n 个元素或使用toArray index 但似乎应该有一个更简单的方
  • EclipseLink 2.7.0 和 JPA API 2.2.0 - 签名不匹配

    当运行由maven构建的具有以下依赖项的项目时
  • 为什么jdk中没有ConcurrentLinkedHashMap类?

    这个问题直接接着问从我之前的问题来看 https stackoverflow com q 12299731 1527084 我想我的第二个问题的答案是否定的 所以我想了解为什么 java util concurrent 包中没有 Concu
  • 相当于 C# 中 Java 的“ByteBuffer.putType()”

    我正在尝试通过从 Java 移植代码来格式化 C 中的字节数组 在 Java 中 使用方法 buf putInt value buf putShort buf putDouble 等等 但我不知道如何将其移植到 C 我尝试过 MemoryS
  • RecyclerView 适配器的 Kotlin 泛型

    我正在尝试编写一个通用的 recyclerview 适配器 我找到了几个例子 然而 仍然无法弄清楚如何实现通用适配器 我写的代码是 open abstract class BaseAdapter
  • 如何在SpringBootTest中向Autowired testRestTemplate添加基本身份验证;春季启动 1.4

    我在 Spring Boot 1 4 之前的 OAuth 集成测试如下 更新只是为了不使用已弃用的功能 RunWith SpringRunner class SpringBootTest classes ApplicationConfigu
  • 根据 Java 环境变量中的值创建使用 @JsonIgnore 的自定义注释

    我需要创建一个新的注释 用于在环境变量设置时忽略输出 JSON 文件中的字段var false 我尝试使用JsonAnnotationIntrospector 但无法获得预期的输出 public class Vehicle String v
  • 在Java的System.out中以表格格式输出

    我正在从数据库获取结果 并希望将数据作为 Java 标准输出中的表输出 我尝试过使用 t 但我想要的第一列的长度变化很大 有没有办法将其显示在类似输出的漂亮表格中 Use System out format http java sun co

随机推荐

  • 获取 `TypeError: jest.fn 不是一个函数`

    我正在尝试使用 Jest 创建以下单元测试 jest dontMock pointsAwardingActions js describe points awarding actions gt describe award points g
  • 获取个人资料的 Facebook 创建日期[重复]

    这个问题在这里已经有答案了 是否可以通过使用 Graph API 获取 Facebook 个人资料的创建日期 时间 我需要它来建立一个身份验证机制 该机制不允许刚刚创建的 Facebook 用户 调用 Facebook API 是一个 相对
  • 找不到主要出口来加载“XYZComponent”

    我在 Net MVC 组件中加载了 ng2 代码 但控制台中显示以下错误 异常 未捕获 承诺 错误 找不到加载 UsersComponent 的主要出口 错误 找不到加载 UsersComponent 的主要出口 知道可能是什么问题吗 我正
  • Ruby On Rails 更新 Heroku 动态路由

    我有一个使用应用程序范围的 slugs 的应用程序 使用懒惰的宝石 https github com RISCfuture slugalicious与 Sluggable 表 并使用以下代码路由到这些 slugs Slugs begin S
  • 在 Fluent nHibernate 中使用派生类

    我有两个共享公共字段的表 我不想重新映射所有这些 而是 希望拥有一个具有公共字段的基类 对于 POCO 来说这很简单 class Base public string commonField get set class Derived Ba
  • 如何在 C# 中读取文本文件并将数据添加到 int 数组中?

    我正在尝试读取一个文本文件 其中包含以逗号分隔的数字 当我阅读时使用File Readline 我把它拿到string 我需要将其转换为 int 数组 但它给出了错误 文本文件的内容 146429 143689 144380 141523
  • 将 unicode 对象转换为带有实体的拉丁字符串

    我有一个 unicode 对象 例如 x u a 日本語 en i hall le 并希望将其转换为带有 h tml entities 的 latin 1 字符串 例如 a amp 26085 26412 35486 en 269 i ha
  • C/C++ stdlib 命名约定的名称?

    我想知道 C C 标准库中使用的命名约定是否有一个名称 或者至少有一个可以查找规则的备忘单 例如 push back underscore used setstate but not used here string npos when t
  • 没有 id 或名称的 Spring bean

    我正在审查一些 Spring 代码 并且看到一些没有 id 或名称的 bean def 做这件事的人不在场 无法询问 该应用程序运行良好 我不知道这必然意味着什么 有人知道这是否有什么特别的含义吗 某些 Bean 不需要由上下文文件中的其他
  • Spring SAML 扩展和 Spring Security CSRF 保护冲突

    我们有一个带有 Spring Security 3 2 4 的 Spring MVC 4 0 5 应用程序 其中包括运行良好的 CSRF 保护 我们现在添加 SAML 安全扩展 spring security saml2 core 1 0
  • 如何在 JSTL 中设置 cookie

    我正在尝试实现以下目标 但不确定是否可能以及语法应该是什么
  • Python:运行所有子目录中的脚本

    我是Python新手 我正在用它来做一些数据分析 我的问题如下 我有一个包含许多子目录的目录 每个子目录都包含大量数据文件 我已经编写了一个 Python 脚本 当在这些子目录之一中执行时 该脚本会执行数据分析并将其写入输出文件 该脚本包含
  • 在 Postgres 中将数组转换为行

    如果我在 SQL 语句中有类似的内容 A B C 如何将其转换为具有多行的列 如下所示 col A B C 我无法更改该字符串的创建方式 因为它是从外部程序注入到 SQL 查询中的 例如 我不能将其作为 A B C 用方括号替换 我可以把任
  • 无法通过 WSO2 API Manager 中的自定义中介流中的呼叫中介器

    在 WSO2 AM 1 10 中创建自定义中介流以实现 API 链接 作为第一步 我创建了此流程作为测试 它调用 REST 服务以生成令牌并将响应返回给客户端
  • Java Swing 组件中的 HTML 标签

    向 Swing 组件 例如 JLabels 添加 HTML 标签是否会使 JFrame 的渲染速度变慢 我的意思是 Swing 组件中 HTML 渲染的性能如何 是的 这取决于你的 html 的复杂程度 实际上 当你的 jlabel 中有
  • jquery 自动完成不响应动态插入的元素

    我们正在研究使用 jquery 动态插入元素的自动完成 之前对静态元素进行过自动完成 下面是插入一个自动完成添加元素的 html 源代码 同一页面上可能插入多个元素 插入元素的 id 在插入之前是未知的 id 因元素而异
  • 在 jupyter 中 for 循环播放音频

    我有大量需要注释的训练数据 为了做到这一点 我需要听一堆声音片段并记下我听到的内容 我在笔记本上为此写了一个小脚本 我的主要问题是 IPython 显示不循环显示 举个例子 import numpy import IPython displ
  • 在没有活动的情况下启动应用程序,我的广播接收器不工作

    在我的应用程序中 我有一个广播接收器 用于捕获发送到手机的消息
  • Observer() 的结果似乎没有考虑 PyEphem 中的海拔影响

    我对 PyEphem 模块给出的与 Observer 查询相关的结果以及海拔的影响进行了查询 我从几个来源了解到 例如http curious astro cornell edu question php number 388 http c
  • 如何在 Spring MVC 中执行自定义验证?

    我有以下代码 允许用户更新姓名和年份 Model Entity public class Person implements Serializable private static final long serialVersionUID 1