最近,我惊讶于一个代码库在其所有域实体中具有公共默认构造函数(即零参数构造函数),并且所有字段具有getter和setter。 当我深入研究时,我发现域实体之所以如此,主要是因为该团队认为Web / MVC框架需要它。 我认为这是消除一些误解的好机会。

具体来说,我们将研究以下情况:

  1. 生成的ID字段没有设置器(即,生成的ID字段具有吸气剂但没有设置器)
  2. 没有默认的构造函数(例如,没有公共的零参数构造函数)
  3. 具有子实体的域实体(例如,子实体未显示为可修改列表)

绑定Web请求参数

首先,一些细节和背景。 让我们基于特定的Web / MVC框架-Spring MVC。 使用Spring MVC时,其数据绑定按名称绑定请求参数。 让我们举个例子。

@Controller
@RequestMapping("/accounts")
... class ... {...@PostMappingpublic ... save(@ModelAttribute Account account, ...) {...}...
}

给定上面的控制器映射到“ / accounts”,一个Account实例可以从哪里来?

根据文档 ,Spring MVC将使用以下选项获取实例:

  • 从模型(如果已通过Model添加(例如通过同一控制器中的@ModelAttribute方法 )。
  • 通过@SessionAttributes在HTTP会话中。
  • 来自通过Converter的URI路径变量。
  • 从默认构造函数的调用开始。
  • (仅适用于Kotlin)通过调用具有与Servlet请求参数匹配的参数的“主要构造函数”; 参数名称是通过JavaBeans @ConstructorProperties或字节码中运行时保留的参数名称确定的。

假设没有在会话中添加Account对象,并且没有@ModelAttribute方法 ,Spring MVC最终将使用其默认构造函数实例化一个实例,并按name绑定Web请求参数。 例如,请求包含“ id”和“ name”参数。 Spring MVC将尝试通过分别调用“ setId”和“ setName”方法将它们绑定到“ id”和“ name” bean属性。 这遵循JavaBean约定。

生成ID字段的无设置方法

让我们从简单的事情开始。 假设我们有一个Account域实体。 它具有由持久性存储生成的ID字段,并且仅提供getter方法(但不提供setter方法)。

@Entity
... class Account {@Id @GeneratedValue(...) private Long id;...public Account() { ... }public Long getId() { return id; }// but no setId() method
}

那么,我们如何让Spring MVC将请求参数绑定到Account域实体? 我们是否必须为生成的字段和只读字段提供公共设置方法?

在我们HTML表单中,我们不会将“ id”作为请求参数。 我们将其放置为路径变量。

我们使用@ModelAttribute方法。 在请求处理方法之前调用它。 它支持与常规请求处理方法几乎相同的参数。 在我们的例子中,我们使用它来检索具有给定唯一标识符的Account域实体,并将其用于进一步的绑定。 我们的控制器看起来像这样。

@Controller
@RequestMapping("/accounts")
... class ... {...@ModelAttributepublic Account populateModel(HttpMethod httpMethod,@PathVariable(required=false) Long id) {if (id != null) {return accountRepository.findById(id).orElseThrow(...);}if (httpMethod == HttpMethod.POST) {return new Account();}return null;}@PutMapping("/{id}")public ... update(...,@ModelAttribute @Valid Account account, ...) {...accountRepository.save(account);return ...;}@PostMappingpublic ... save(@ModelAttribute @Valid Account account, ...) {...accountRepository.save(account);return ...;}...
}

更新现有帐户时,请求将是对“ / accounts / {id}” URI的PUT 。 在这种情况下,我们的控制器需要检索具有给定唯一标识符的域实体,并向Spring MVC提供相同的域对象以进行进一步绑定(如果有)。 “ id”字段将不需要设置方法。

添加或保存新帐户时,请求将是“ / accounts”的POST 。 在这种情况下,我们的控制器需要使用一些请求参数创建一个新的域实体,并向Spring MVC提供相同的域对象以进行进一步绑定(如果有)。 对于新的域实体,“ id”字段保留为null 。 基础的持久性基础结构将在存储时生成一个值。 尽管如此,“ id”字段仍不需要设置方法。

在这两种情况下,@ @ModelAttribute方法populateModel均在映射的请求处理方法之前被调用。 因此,我们需要在populateModel使用参数来确定在哪种情况下使用它。

域对象中没有默认构造函数

假设我们的Account域实体没有提供默认构造函数(即,没有零参数构造函数)。

... class Account {public Account(String name) {...}...// no public default constructor// (i.e. no public zero-arguments constructor)
}

那么,我们如何让Spring MVC将请求参数绑定到Account域实体? 它不提供默认的构造函数。

我们可以使用@ModelAttribute方法。 在这种情况下,我们要创建一个带有请求参数的Account域实体,并将其用于进一步的绑定。 我们的控制器看起来像这样。

@Controller
@RequestMapping("/accounts")
... class ... {...@ModelAttributepublic Account populateModel(HttpMethod httpMethod,@PathVariable(required=false) Long id,@RequestParam(required=false) String name) {if (id != null) {return accountRepository.findById(id).orElseThrow(...);}if (httpMethod == HttpMethod.POST) {return new Account(name);}return null;}@PutMapping("/{id}")public ... update(...,@ModelAttribute @Valid Account account, ...) {...accountRepository.save(account);return ...;}@PostMappingpublic ... save(@ModelAttribute @Valid Account account, ...) {...accountRepository.save(account);return ...;}...
}

具有子实体的域实体

现在,让我们看一下具有子实体的域实体。 这样的东西。

... class Order {private Map<..., OrderItem> items;public Order() {...}public void addItem(int quantity, ...) {...}...public Collection<CartItem> getItems() {return Collections.unmodifiableCollection(items.values());}
}... class OrderItem {private int quantity;// no public default constructor...
}

请注意,订单中的项目不会显示为可修改列表。 Spring MVC支持索引属性,并将它们绑定到数组,列表或其他自然排序的集合。 但是,在这种情况下, getItems方法将返回无法修改的集合。 这意味着当对象尝试向其添加/删除项目时,将引发异常。 那么,如何让Spring MVC将请求参数绑定到Order域实体? 我们是否被迫将订单项公开为可变列表?

并不是的。 我们必须避免用表示层关注点来稀释域模型(例如Spring MVC)。 相反,我们使表示层成为域模型的客户端。 为了处理这种情况,我们创建了另一个符合Spring MVC的类型,并使我们的域实体与表示层无关。

... class OrderForm {public static OrderForm fromDomainEntity(Order order) {...}...// public default constructor// (i.e. public zero-arguments constructor)private List<OrderFormItem> items;public List<OrderFormItem> getItems() { return items; }public void setItems(List<OrderFormItem> items) { this.items = items; }public Order toDomainEntity() {...}
}... class OrderFormItem {...private int quantity;// public default constructor// (i.e. public zero-arguments constructor)// public getters and setters
}

请注意,完全可以创建一个了解域实体的表示层类型。 但是让域实体知道表示层对象并不是全部。 更具体地说,表示层OrderForm知道Order域实体。 但是Order不了解表示层OrderForm

这是我们的控制器的外观。

@Controller
@RequestMapping("/orders")
... class ... {...@ModelAttributepublic OrderForm populateModel(HttpMethod httpMethod,@PathVariable(required=false) Long id,@RequestParam(required=false) String name) {if (id != null) {return OrderForm.fromDomainEntity(orderRepository.findById(id).orElseThrow(...));}if (httpMethod == HttpMethod.POST) {return new OrderForm(); // new Order()}return null;}@PutMapping("/{id}")public ... update(...,@ModelAttribute @Valid OrderForm orderForm, ...) {...orderRepository.save(orderForm.toDomainEntity());return ...;}@PostMappingpublic ... save(@ModelAttribute @Valid OrderForm orderForm, ...) {...orderRepository.save(orderForm.toDomainEntity());return ...;}...
}

总结思想

正如我在之前的文章中提到的,可以让您的域对象看起来像具有公共默认零参数构造函数,getter和setter的JavaBean。 但是,如果域逻辑开始变得复杂,并且要求某些域对象失去其JavaBean风格(例如,不再有公共的零参数构造函数,没有更多的setter),则不必担心。 定义新的JavaBean类型以满足与表示相关的问题。 不要稀释域逻辑。

目前为止就这样了。 我希望这有帮助。

再次感谢Juno帮助我提供样品。 相关代码段可以在GitHub上找到 。

翻译自: https://www.javacodegeeks.com/2018/06/domain-objects-spring-mvc.html

在Spring MVC中处理域对象相关推荐

  1. Spring 2.5:Spring MVC中的新特性

    转载说明:infoQ就是牛人多,看人家去年就把Spring2.5注视驱动的MVC写出来了,还是这么详细,我真是自叹不如,今天偶尔看到这篇文章非常认真的拜读了2遍,简直是茅厕顿开啊....\(^o^)/ ...

  2. 在Spring MVC中使用Apache Shiro安全框架

    我们在这里将对一个集成了Spring MVC+Hibernate+Apache Shiro的项目进行了一个简单说明.这个项目将展示如何在Spring MVC 中使用Apache Shiro来构建我们的 ...

  3. Spring MVC中获取Request的方法及分析

    一.概述 在使用Spring MVC开发Web系统时,经常需要在处理请求时使用request对象,比如获取客户端IP地址.请求的URL.header中的属性(如cookie.授权信息).body中的数 ...

  4. spring MVC cors跨域实现源码解析

    spring MVC cors跨域实现源码解析 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议.IP.http方法任意一个不同就是跨域. sp ...

  5. Spring MVC中处理Request和Response的策略

    前沿技术早知道,弯道超车有希望 积累超车资本,从关注DD开始 作者:码农小胖哥, 图文编辑:xj 来源:https://mp.weixin.qq.com/s/3eFygsiVl8dC2nRy8_8n5 ...

  6. Spring MVC中的二三事

    HandlerMapping和HandlerAdapter 这个两个组件应该算是spring mvc中最重要的几个组件之一了,当一个请求到达DispatcherSerlvet后,spring mvc就 ...

  7. Spring MVC中Session的正确用法之我见02

    Spring MVC中Session的正确用法之我见 Spring MVC是个非常优秀的框架,其优秀之处继承自Spring本身依赖注入(Dependency Injection)的强大的模块化和可配置 ...

  8. Spring MVC中的视图解析ViewResolver

    http://blog.csdn.net/prince2270/article/details/5891085 在Spring MVC中,当Controller将请求处理结果放入到ModelAndVi ...

  9. Spring MVC 中的基于注解的 Controller

    为什么80%的码农都做不了架构师?>>>    Spring MVC 中的基于注解的 Controller @Controller 基于注解的 Controller   终于来到了基 ...

最新文章

  1. LeetCode简单题之数组异或操作
  2. OpenCV(十八)霍夫变换(直线、线段与圆检测)
  3. PHP爬取post网页数据,php curl发送post请求爬取webService接口数据简单实例
  4. 东芝打印机cd40故障_东芝小尺寸UV平板机介绍
  5. 可以接受失败,但不选择放弃
  6. Oracle数据库事务回滚和提交,数据库 事务提交和回滚
  7. 《C#与.net高级编程》——第一支柱:C#的封装
  8. 详解:JVM内存调优参数
  9. android跑分和ios,手机:苹果VS安卓 跑分不代表手机流畅度
  10. python画一条曲线有不同的形状_Python+pandas+matplotlib控制不同曲线的属性 !
  11. 你竟是这样的月饼-今年中秋节折腾的月饼2.0
  12. Visual Studio2017 配置OpenGL
  13. Linus Torvalds 回应,Debian 项目曾讨论永久禁止他出席会议!
  14. docker搭建swoole简易聊天室
  15. MyBatis 别名标签 sql的复用
  16. mysql截取前几个字符串_mysql中截取指定字符前后的字符串
  17. 初中生学计算机编程的好处,为什么初中生更加适合学习计算机编程?
  18. 女生选择了计算机专业能做什么工作?其实有这 5 种,前景很好
  19. [论文解读]微信看一看实时Look-alike推荐算法
  20. 唐朝乐队第三张专辑《浪漫骑士》6月上市.

热门文章

  1. 北方大学 ACM 多校训练赛 第十五场 蜘蛛牌A
  2. 浅谈流处理算法 (1) – 蓄水池采样
  3. Streaming的算法Reservoir Sampling
  4. Java码农必须掌握的循环删除List元素的正确方法
  5. 公众号一年能有多少收入?
  6. 有些事,父母一定不能依着孩子!
  7. SpringAOP之代理设计模式
  8. 从oracle里面取直,45个非常有用的 Oracle 查询语句小结
  9. 学院派 实践派 计算机科学与技术,饶旻现场为boss所在企业“挑错”
  10. mysql-on duplicate key update实现insertOrUpdate官方文档