javascript
在Spring MVC中处理域对象
最近,我驚訝于一個代碼庫在其所有域實體中都具有公共默認構造函數(即零參數構造函數),并且所有字段都具有getter和setter。 當我深入研究時,我發現域實體之所以如此,主要是因為該團隊認為Web / MVC框架需要它。 我認為這是消除一些誤解的好機會。
具體來說,我們將研究以下情況:
綁定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中处理域对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Data JPA教程
- 下一篇: 计算机网络dce是什么意思,DTE与DC