弱弱的问一句是什么意思 我下面15-16厘米 够用了没

  • 《真正男子汉》第二季·空军篇是由中国人民解放军空军政治工作部电视艺术中心和湖南卫视联合制作的原创大型国防教育特别节目。节目挑选了8位性格标签各异的男女明星,深入一线部队在空军的各个兵种经历5次3天2晚的真实军营体验,与年轻的部队战士同吃同住同训练看他们是如何历练成为一名真正嘚“空军战士”,腾讯视频联合独播

  我经常看见很多项目没有数據验证的策略和意识他们的团队在交付日期的重压下,面对不清楚的需求没有时间去考虑用合适并且统一的方法对数据进行验证。所鉯在这样的项目中到处能看见数据验证的代码:在前端JS中,在后端页面控制器中在业务逻辑的bean中,在数据模型实体中在数据库的约束和触发器中。这些代码都是一些 if-else 的语句抛出一些不同的未检查的异常,所以有时会很难找到这些该死的数据到底是在哪里做的验证洇此,一段时间之后当项目成长到足够大的时候就很难并且需要耗费很多精力来统一这些验证,并且后面的需求也一样模糊不清

  那有没有做数据验证比较标准、优雅而且还简洁的方法呢?这个方法不会导致代码的不可读这个方法能帮我们将大部分数据验证的代码維护在统一的地方,而且有没有可能一些流行框架的开发者已经替我们做了大部分的工作呢

  作为我们CUBA平台的开发者来说,让我们的鼡户也遵循最佳实践非常重要我们认为,数据验证的代码应该是:

    2. 用干净和自然的方式表达出来

    3. 放在开发人员期望看到的地方。

    4. 能对不同数据来源的数据进行检查:用户输入SOAP或者REST 调用等。

    6. 由应用程序隐式统一调用而不需要手动调用这些检查代码

    7. 能用简洁的弹窗为用户展示清晰,本地语言的消息

  这篇文章里,我将使用基于CUBA平台开发的应用程序来演示所有的例子由于CUBA是基於Spring和EclipseLink的,所以这些例子对于使用JPA和bean验证的其他Java框架也适用

  也许,最常用最直接的数据验证方法就是使用数据库级别的约束比如非涳,字符串长度唯一索引等。对于企业级应用来说这个方法很自然,因为这种类型的软件通常都是以数据为中心但是,即便是这种凊况开发者也经常出错,在应用程序的各个数据层级分别定义了约束这个问题主要是由于开发人员的不同责任分工引起的。

  我们看一个几乎大家都会面对的例子有的人甚至干过这样的事 :)。 假设有个规定要求护照号码字段需要有10个数字很可能到处都会做这个规则檢查:数据库设计者用DDL检查,后台开发人员在相应的实体和REST服务中检查最后前端工程师在客户端代码中检查。之后这个需求变了要求護照字段升到15个数字。技术支持人员可能只修改了数据库约束但是这样对于用户来说等于什么都没改,因为后台和前台的检查还没修改呢

  大家都知道避免这个问题的方法,验证需要中心化在CUBA,这种验证的中心点在是实体的JPA注解基于这个元数据信息,CUBA Studio可以生成正確的DDL脚本并且能在客户端采用相应的验证器

  此时,如果JPA注解改变的话CUBA会自动更新DDL脚本以及生成数据库迁移脚本,所以下次部署项目的时候新的基于JPA的限制将会在UI和DB生效。

  这种方式简单、也能实施到底层数据库级别因此能完全防破解。但是JPA注解的局限性在于只能使用在最简单、可以用标准的DDL表述、而不需要引入特定数据库的触发器或者存储过程的情况。所以基于JPA的约束可以用来保证实体字段是唯一的或者必须的,抑或也能定义varchar字段的最大长度还有,可以使用 @UniqueConstraint 注解来为一组字段定义唯一性约束但也就这些了。

  如果茬需要更加复杂的验证逻辑的的时候比如检查某个字段的最大最小值或者对一个字段使用正则表达式进行验证,此时我们就需要使用众所周知的叫做 “bean 验证” 的方法了

  我们知道,遵循标准是很好的实践通常这种方式有更长的生命周期而且有几千个项目实战证明过叻。Java 的 Bean验证是早就写在石头上的方案了:在, 也有些成熟的实现:和 

很多开发者都熟悉这个方法,但是这个方法的好处却总是被低估用這个方法甚至可以很容易在遗留项目中添加数据验证,并且还能以清晰、直接、可靠最贴近业务逻辑的方式表达需要做的验证

  使用Bean驗证能为项目带来很多好处:

  l  验证逻辑集中在数据模型附近:使用最自然的方法定义针对值、方法和bean的约束,因此可以将OOP推进到下一個级别(验证也可以OOP)

  l 不会受限于仅使用预定义的约束,还可以自定义约束注解可以定义一个注解来将其他几个注解绑定到一起,或者定义一个全新的注解然后定义一个相应的Java类作为验证器。比如之前那个例子中,可以定义一个类级别的注解 @ValidPassportNumber 用来检查护照号码昰否符合正确的格式号码也许还依赖 country 字段的值。

  l 不止可以在类和字段上加约束也可以添加到方法和方法参数上。这个叫做“合同驗证”后面会介绍。

  CUBA平台(以及一些其他平台)会在用户提交数据的时候自动调用这些验证所以一旦验证失败用户会马上看到错誤消息,不需要考虑手动执行这些bean验证

  我们一起再看看护照号码验证的例子,但是这次我们还需要在实体添加几个其他的验证:

  l 人物姓名(例子中是用的英文名)至少有2个单词或者可以更多必须是格式化很好的姓名。检查的正则表达式很复杂比如 Charles Ogier de Batz de Castelmore Comte d'Artagnan 能通过检查,但是 R2D2 却不能通过

  l 邮件地址需要是正确的邮件地址格式。

  因此带有所有这些检查,Person类看起来是这样:

  首先按照文档(戓 文档是很好的参考)的描述,我们需要使用新的注解来标记实体类以及将约束分组传递给这个注解。CUBA文档有说UiCrossFieldChecks.class 应当在所有单独的字段检查完之后,才执行跨字段的检查Default.class 能将约束添加到默认的验证组。

  注解的定义是这样的:

  好了足够了。使用CUBA平台不需要多寫任何代码来保证这个验证的运行也不需要添加代码在用户输入错误的时候给用户发送消息通知。很简单吧

  现在,我们看看这些東西都是怎么工作的CUBA还做了一些额外的事情:不但给用户展示错误消息,而且还将有问题的表单字段高亮出来这些漂亮的描红字段没囿通过单一字段的bean验证:

  是不是很简洁?在用户的浏览器显示漂亮的错误提醒只需要在实体中添加几个简单的注解就好了。

  作為本章节的总结我们再简单列举一下实体的Bean验证有什么好处:

  1. 可以直接在实体模型中定义值的约束
  2. 跟很多流行的ORM集成,检查都是在实体保存在数据库之前自动调用的
  3. 有些框架也能在用户从UI提交数据的时候自动运行bean验证(但是如果不支持的话很难手动调用 Validator 接口)
  4. Bean验证是众所周知的标准,网上能找到很多相关文档

  但是如果我们需要将验证放到方法、构造器上或者放到某个REST终端来验证从外部来的数据呢戓者我们想用声明式的方法验证方法参数而不是在每个方法内写很多if-else这种枯燥的检查参数的方法?

  答案很简单bean验证也可以作用在方法上!

  有时候,我们需要前进一步不只是做到应用的数据模型验证。如果能做到参数和返回值自动验证那么写方法的时候就会容噫很多。这个需求可能不只是用在检查REST或者SOAP接入的数据也会用在针对方法的输入参数和返回值上。用来做所谓的前置条件和后置条件检查确保在方法体执行前对输入参数的检查,以及在方法执行后对返回值范围的检查或者只是希望能声明式的用在参数上限定参数的范圍以达到代码更好的可读性。

  使用合同验证就可以在任何Java类型的方法、构造器的参数和返回值上使用验证。相对传统的检查参数和返回值的办法这个方案的优点是:

  l 不需要以极端的方式执行检查(比如,抛出类似 illegalArgumentException 这样的异常)我们会更愿意使用声明式的约束,这样会形成可读性表达性更强的代码

  l 约束都是可重用、可配置、可定制化的:不需要每次都写验证代码,更少的代码意味着更少嘚bug

  l 如果类、方法的返回值或参数使用了 @Validated 注解,平台会在每个方法调用的时候自动执行约束检查

  l 如果一个可执行程序使用了 @Documented 注解,那么它的前置条件和后置条件会自动包含在生成的JavaDoc中

  因此,使用合同验证方案会有清晰、相对少的代码,更易于维护和理解

  我们看看在CUBA应用的REST控制器中,使用合同验证的代码大概是什么样的通过 PersonApiService 接口的 getPerson() 方法可以从数据库获取用户的列表,使用 addNewPerson(…) 方法可鉯添加新用户需要注意的是,bean验证是可以继承的!也就是说如果用验证的注解标记了某些类,字段或者方法那么这个类的后代或者接口的实现类都会受到这些验证的影响。  

  @Valid 注解指定 getPerson() 方法的返回列表中的每个对象需要使用 Person 类的验证进行检查

  CUBA会自动生成下列路徑用来执行这些API:

  我们打开Postman试试这些验证是否都好用:

  你可能会注意到,上面的例子没有验证护照号码这是因为这个需要在 addNewPerson 做跨参数验证,passportNumber 的验证正则表达式依赖 country 的值这种跨参数的验证跟实体类级别约束是一样的。

  JSR 349 和 380 支持跨参数验证 可以查阅 了解如何为類/接口方法实施自定义的跨参数验证。

  世上没有什么是完美的bean验证也有局限性:

  l 有时候需要在保存更改之前检查复杂的对象关系图的状态。比如可能需要检查客户在你电商网站的订单中购买的所有东西是否能装到一个快递箱子中。这是个比较繁重的检查因此烸次客户订单的商品变更的时候都做这个检查不合适。所以这个检查应该只需要在Order对象和它的OrderItem对象保存到数据库之前做一次

  l 有些检查需要在数据库的事务中做。比如电商系统需要在订单保存到数据库之前检查是否有足够的库存。这些检查只能在事务级别因为系统昰并发的,库存的数量是实时变化的

  CUBA平台提供了两个在数据提交之前做验证的机制:和。我们仔细看看

  跟JPA提供的 非常相似。這两种机制都都可以在实体对象持久化到数据库之前或者之后做检查

  在CUBA中定义和组织实体监听器不难,只需要两步:

  跟JPA标准(JSR 338 3.5)不一样CUBA的监听器接口是带数据类型的,所以不需要在方法内做类型转换可以直接使用实体。CUBA平台还提供了跟当前实体关联的实体以忣通过EntityManager去加载或者更改其他任何实体的机制这些改动也会调用相应的实体监听器。

  另外CUBA平台支持“软删除()”,实体在数据库呮是标记为删除但是不会真正删除数据库记录。所以对于软删除CUBA平台会调用 BeforeDeleteEntityListener

  看看下面的例子吧。事件监听器的bean跟实体类连接只需要一行注解:@Listeners,注解使用的参数是监听器类的名称

  实体监听器的实现是这样的:

  实体监听器有时候很有用:

  l 在实体持久囮到数据库之前需要在事务内做检查

  l 需要在验证的过程中访问数据库信息,比如在保存订单之前先检查库存的数量

  l 需要遍历实体關联或者组合的实体比如Order里面的OrderItem实体

  l 需要跟踪某些实体的增/删/改操作,比如希望跟踪Order和OrderItem的变化情况

  也在事务的上下文环境中工莋但是跟实体监听器不一样的是,事务监听器是在事务级别被调用的

  因此,事务监听器是终极大杀器能监管到所有的数据库交互,但是这样也带来了弱点:

  l 不是很好编码

  l 如果做太多检查会显著的降低性能

  l 编码需要很小心一个bug可能会导致整个应用都啟动不了

  所以事务监听器在需要用同一算法检查很多不同类型的实体的时候是个好办法。比如需要给支持所有业务的“欺诈侦探器”填充数据的时候

  我们看看下面这个例子,检查是否有实体带有 @FraudDetectionFlag 注解如果有的话,调用欺诈侦探器来检查一下注意,这个方法会茬每次数据库提交的事务都调用所以代码需要尽可能少的检查数据对象,并且越快越好

  Bean 验证(JPA 303 349 980)基本能满足企业级应用中 95% 的数据驗证的情况。这个方案最大的优点是大部分验证的逻辑都集中到了数据模型类中。因此很容易找到代码可读性强还容易维护。SpringCUBA以及佷多类库都能知道这些标准并且在UI输入值的时候,调用方法的时候或者做ORM持久化的时候自动调用验证代码从开发者角度来说,这些验证僦像是小魔法

  有些软件工程师认为,在数据模型层面做的验证复杂且带有侵入性觉得在UI层做验证就够了。但是我个人觉得,在UI戓者UI控制器中写很多验证点是很容易出问题的另外,我们这里讨论的验证方法在跟平台集成的时候并不是侵入性的代码,因为平台会感知这些验证器、监听器然后将它们自动集成到客户端层

  最后,我们制定一个经验规则来选择最佳的验证方法:

  l JPA验证:功能有限但是在实体类上做最简单的约束是最好的选择。要求这些约束能映射成DDL

  l Bean验证:灵活、简洁、声明式、可重用而且易读基本上能覆盖模型中需要的所有验证,如果不需要在事务中进行验证的话这是最好的选择

  l 合同验证:也是一种bean验证,不过是应用在方法上洳果需要检查输入和输出参数,比如REST调用可以使用这个方法

  l 实体监听器:尽管不像bean验证那样是使用全部声明式的方式,但是可以在數据库事务中对比较复杂的对象关系图做验证比如需要从数据库加载一些信息来做决定。Hibernate也有类似的监听器

  l 事务监听器:危险但是這是事务级别的终极武器如果需要在运行时对实体进行验证或者需要对很多不同类型的实体使用同一种验证方法的时候可以选用

  我唏望这篇文章能刷新你对于Java企业级应用中验证方法的记忆,也希望在提升项目架构方面提供一点点参考

我要回帖

更多关于 弱弱的问一句是什么意思 的文章

 

随机推荐