Java应用程序中的验证
我經常看到的項目幾乎沒有任何有意識的數據驗證策略。 他們的團隊在截止日期,明確要求的巨大壓力下工作,只是沒有足夠的時間以適當且一致的方式進行驗證。 因此,數據驗證代碼隨處可見:JavaScript片段,Java屏幕控制器,業務邏輯bean,域模型實體,數據庫約束和觸發器。 這段代碼充滿了if-else語句,引發了各種未經檢查的異常,很難找到可以驗證該死數據的正確位置……因此,過了一段時間,當項目長大到足夠長的時候,它就變得非常困難保持此驗證的一致性和后續要求非常昂貴,正如我所說,這些要求通常很模糊。
有沒有辦法以一種優雅,標準和簡潔的方式進行數據驗證? 不會成為無法閱讀的罪過的方法,是幫助我們將大多數數據驗證邏輯保持在一起的方法,以及流行的Java框架的開發人員已經為我們完成了大部分代碼的方法?
就在這里。
對于我們CUBA Platform的開發人員而言,讓我們的用戶遵循最佳實踐非常重要。 我們認為驗證代碼應為:
在本文中,我將針對所有示例使用基于CUBA平臺的應用程序。 但是,由于CUBA基于Spring和EclipseLink,因此大多數示例都可用于支持JPA和bean驗證標準的任何其他Java框架。
數據庫約束驗證
也許,最常見,最直接的數據驗證方法是使用數據庫級別的約束,例如必需的標志(“非空”字段),字符串長度,唯一索引等。 對于企業應用程序來說,這種方式非常自然,因為此類軟件通常以數據為中心。 但是,即使在這里,開發人員也經常犯錯,分別為應用程序的每一層定義約束。 此問題通常是由開發人員之間的職責分散引起的。
讓我們舉一個例子,你們大多數人面對甚至參與:)。 如果某個規范說護照字段的數字應該有10位數字,則很可能會在所有地方對其進行檢查:由DDL中的DB架構師,由相應Entity和REST服務中的后端開發人員,最后由UI開發人員直接在客戶端源中進行檢查-碼。 后來,此要求發生了變化,并且字段的大小最多增長了15位。 技術支持更改了數據庫約束,但是對于用戶而言,這沒有任何意義,因為無論如何都不會通過客戶端檢查……
每個人都知道避免這種問題的方法,驗證必須集中! 在CUBA中,這種驗證的中心點是實體上的JPA批注。 基于此元信息,CUBA Studio生成正確的DDL腳本并在客戶端應用相應的驗證器。
如果更改了JPA批注,則CUBA將更新DDL腳本并生成遷移腳本,因此,下次您部署項目時,基于JPA的新限制將應用于應用程序的UI和數據庫。
盡管涉及到DB級別的簡單性和實現方式是完全防彈的,但是JPA注釋受到可以以DDL標準表示的最簡單情況的限制,而無需涉及特定于DB的觸發器或存儲過程。 因此,基于JPA的約束可以確保實體字段是唯一的或必填的,或者可以為varchar列定義最大長度。 另外,您可以使用@UniqueConstraint批注為列的組合定義唯一約束。 但這差不多。
但是,在需要更復雜的驗證邏輯的情況下,例如檢查字段的最大值和最小值,使用表達式進行驗證或對您的應用程序進行特定的自定義檢查,我們需要使用眾所周知的方法“ Bean驗證” 。
Bean驗證
我們所知道的,遵循標準是一種好習慣,標準通常具有較長的生命周期,并且在數千個項目中得到了實踐證明。 Java Bean驗證是一種在JSR 380、349 和303及其實現中固定的方法: Hibernate Validator和Apache BVal 。
 盡管許多開發人員都熟悉這種方法,但是它的好處常常被低估了。 即使對于舊項目,這也是添加數據驗證的簡便方法,它使您能夠以清晰,直接和可靠的方式表達驗證,并盡可能接近業務邏輯。 
 使用Bean驗證方法可以為您的項目帶來很多好處: 
- 驗證邏輯集中在您的域模型附近:定義值,方法,bean約束以一種自然的方式完成,可以將OOP方法提升到一個新的水平。
 - Bean驗證標準為您提供了數十種開箱即用的驗證批注 ,例如:@ NotNull,@ Size,@ Min,@ Max,@ Pattern,@ Email,@ Past,次標準,例如@ URL,@ Length,強大的@ScriptAssert和很多其他的。
 - 您不受預定義約束的限制,可以定義自己的約束注釋。 您還可以通過結合其他方式進行新的注釋,或者創建一個全新的注釋并定義將用作驗證器的Java類。
 - 例如,在前面的示例中,我們可以定義一個類級別的批注@ValidPassportNumber,以檢查護照號碼是否遵循正確的格式,該格式取決于國家/地區字段的值。
 - 您不僅可以對字段和類施加約束,還可以對方法和方法參數施加約束。 這稱為“合同驗證”,是下一節的主題。
 
 CUBA平臺(和其他框架一樣)在用戶提交數據時自動調用這些Bean驗證,因此,如果驗證失敗,用戶將立即收到錯誤消息,而您不必擔心手動運行這些Bean驗證器。 
 讓我們再次看一下護照號碼示例,但是這次我們要在實體上添加幾個附加約束: 
- 人名的長度應為2個或更多,并且是格式正確的名稱。 Regexp非常復雜,但是Charles Ogier de Batz de Castelmore Comte d'Artagnan通過了檢查,R2D2沒有通過:);
 - 人的身高應在以下范圍內:0 <身高<= 300厘米;
 - 電子郵件字符串應為格式正確的電子郵件地址。
 
因此,通過所有這些檢查,Person類如下所示:
我認為使用@ NotNull,@ DecimalMin,@ Length,@ Pattern等標準注釋非常清楚,不需要太多注釋。 讓我們看看如何實現自定義@ValidPassportNumber批注。
我們全新的@ValidPassportNumber會檢查Person#passportNumber是否匹配特定于Person#country定義的每個國家的正則表達式模式。
首先,遵循文檔( CUBA或Hibernate文檔是很好的參考),我們需要使用此新注釋標記我們的實體類,并向其傳遞groups參數,其中UiCrossFieldChecks.class表示應在檢查了跨字段檢查階段和Default.class將約束保留在默認驗證組中。
批注定義如下所示:
@Target(ElementType.TYPE)定義此運行時批注的目標為一個類,@Constraint(validatedBy =…)聲明批注實現在ValidPassportNumberValidator類中,該類實現ConstraintValidator <…>接口,并在isValid( …)方法,該代碼以非常簡單的方式進行實際檢查:
而已。 使用CUBA平臺,我們無需編寫任何代碼即可使我們的自定義驗證工作并在用戶輸入錯誤時向用戶發送消息。 沒什么復雜的,你同意嗎?
現在,讓我們檢查一下所有這些東西是如何工作的。 CUBA還有一些額外的好處:它不僅向用戶顯示錯誤消息,而且還用紅色線條突出顯示尚未通過單字段Bean驗證的表單字段:
 這不是一件好事嗎? 在將幾個Java批注添加到域模型實體之后,您在用戶瀏覽器中獲得了不錯的錯誤UI反饋。 
 在本節結束時,讓我們再次簡要列出實體的bean驗證的優點: 
但是,如果我們需要對方法,構造函數或某些REST端點設置約束以驗證來自外部系統的數據,該怎么辦? 或者,如果我們想以聲明性的方式檢查方法參數的值,而不用在每個方法中編寫充滿if-els的乏味代碼,我們需要進行這種檢查嗎?
答案很簡單:bean驗證也可以應用于方法!
合同確認
 有時,我們需要采取進一步的措施,而不僅僅是應用程序數據模型狀態驗證。 許多方法可能會受益于自動參數和返回值驗證。 這可能不僅在我們需要檢查到達REST或SOAP端點的數據時需要,而且在我們要表達方法調用的前提條件和后置條件以確保在執行方法主體之前已檢查輸入數據或返回值時可能需要處于預期范圍內,或者我們只想聲明性地表達參數邊界以提高可讀性。 
 使用bean驗證,可以將約束條件應用于任何Java類型的方法或構造函數的參數和返回值,以檢查其調用的前提條件和后置條件。 與傳統的檢查參數和返回值正確性的方法相比,此方法具有多個優點: 
作為“合同確認”方法的結果,我們擁有清晰的代碼,數量更少,更易于支持和理解。
讓我們看一下CUBA應用程序中REST控制器界面的外觀。 PersonApiService接口允許使用getPersons()方法從數據庫中獲取人員列表,并使用addNewPerson(…)調用將新人員添加到數據庫中。 請記住:bean驗證是可繼承的! 換句話說,如果用約束為某個類,字段或方法添加注釋,則所有擴展或實現該類或接口的后代都將受到相同的約束檢查的影響。
 這個代碼片段對您來說看起來很清楚并且可讀嗎? (除了@RequiredView(“ _ local”)批注,該批注專用于CUBA平臺,并檢查返回的Person對象是否具有從PASSPORTNUMBER_PERSON表中加載的所有字段)。 
 @Valid批注指定getPersons()方法返回的集合中的每個對象也必須針對Person類約束進行驗證。 
CUBA使這些方法可用于以下端點:
- / app / rest / v2 / services / passportnumber_PersonApiService / getPersons
 - / app / rest / v2 / services / passportnumber_PersonApiService / addNewPerson
 
讓我們打開Postman應用程序并確保驗證按預期進行:
您可能已經注意到,上面的示例未驗證護照號碼。 這是因為它需要對addNewPerson方法進行跨參數驗證,因為passportNumber驗證正則表達式模式取決于國家/地區值。 這樣的交叉參數檢查直接等效于實體的類級別約束!
JSR 349和380支持交叉參數驗證,您可以查閱hibernate文檔,以了解如何為類/接口方法實現自定義交叉參數驗證器。
超越Bean驗證
世界上沒有什么是完美的,并且bean驗證也有一些局限性:
CUBA平臺提供了兩種在提交之前驗證數據的機制,稱為實體偵聽器和事務偵聽器 。 讓我們更仔細地看看它們。
實體偵聽器
 CUBA中的實體偵聽 器 與 JPA提供給開發人員的PreInsertEvent,PreUpdateEvent 和PredDeleteEvent偵聽器非常相似。 兩種機制都允許在實體對象持久化到數據庫之前或之后檢查實體對象。 
 在CUBA中定義和連接實體偵聽器并不難,我們需要做兩件事: 
BeforeDeleteEntityListener,BeforeInsertEntityListener和
BeforeUpdateEntityListener
而已。
與JPA標準(JSR 338,第3.5章)相比,CUBA平臺的偵聽器接口是有類型的,因此您無需強制轉換Object參數即可開始使用實體。 CUBA平臺增加了與當前實體關聯的實體或調用EntityManager加載和更改任何其他實體的可能性。 所有這些更改也會調用適當的實體偵聽器調用。
CUBA平臺還支持軟刪除 ,這是將數據庫中的實體標記為已刪除而不從數據庫中刪除其記錄時的功能。 因此,對于軟刪除,CUBA平臺將調用BeforeDeleteEntityListener / AfterDeleteEntityListener偵聽器,而標準實現將調用PreUpdate / PostUpdate偵聽器。
讓我們來看一個例子。 事件偵聽器bean僅使用以下一行代碼連接到Entity類:注釋@Listeners接受實體偵聽器類的名稱:
實體偵聽器的實現可能如下所示:
當您執行以下操作時,實體偵聽器是不錯的選擇:
- 需要在實體對象持久化到數據庫之前在事務內部進行數據檢查;
 - 在驗證過程中需要檢查數據庫中的數據,例如,檢查我們是否有足夠的庫存貨物可以接受訂單;
 - 不僅需要遍歷給定的實體對象(例如Order),還需要遍歷與該實體關聯或組成的對象,例如Order實體的OrderItems對象;
 - 只想跟蹤某些實體類的插入/更新/刪除操作,例如,您只想跟蹤Order和OrderItem實體的此類事件,而無需在交易期間驗證其他實體類中的更改。
 
交易聽眾
 CUBA事務偵聽器也可以在事務上下文中工作,但是與實體偵聽器相比,它們針對每個數據庫事務都被調用。 
 這賦予了他們最終的力量: 
- 沒有什么可以吸引他們的注意力,但同樣也會給他們帶來弱點:
 - 他們很難寫,
 - 如果執行過多不必要的檢查,他們會大大降低性能,
 - 需要更加謹慎地編寫它們:事務偵聽器中的錯誤甚至可能阻止應用程序引導。
 
因此,當您需要使用相同的算法檢查許多不同類型的實體時,例如將數據饋送到為您的所有業務對象服務的自定義欺詐檢測器中,事務偵聽器是一個很好的解決方案。
讓我們看一下檢查實體是否使用@FraudDetectionFlag注釋進行注釋的示例,如果是,請運行欺詐檢測器對其進行驗證。 再次提醒您,此方法在每個數據庫事務提交到系統之前被調用,因此代碼必須嘗試盡可能快地檢查最少的對象。
要成為事務偵聽器,受管bean應該只實現BeforeCommitTransactionListener接口并實現beforeCommit方法。 應用程序啟動時,事務監聽器會自動連接。 CUBA將所有實現BeforeCommitTransactionListener或AfterCompleteTransactionListener的類注冊為事務偵聽器。
結論
Bean驗證(JPA 303、349和980)是可以作為企業項目中發生的95%數據驗證案例的具體基礎的一種方法。 這種方法的最大優點是,大多數驗證邏輯都集中在域模型類中。 因此,它易于發現,易于閱讀和支持。 Spring,CUBA和許多庫都了解這些標準,并會在UI輸入,經過驗證的方法調用或ORM持久性過程中自動調用驗證檢查,因此從開發人員的角度來看,驗證就像是一種魅力。
 一些軟件工程師將影響應用程序域模型的驗證視為具有一定侵入性和復雜性,他們說在UI級別進行數據檢查已足夠好。 但是,我認為在UI控件和控制器中具有多個驗證點是很成問題的方法。 此外,當我們將此處討論的驗證方法與可識別Bean驗證器,偵聽器并將其自動集成到客戶端級別的框架集成在一起時,它們不會被視為具有侵入性。 
 最后,讓我們制定一個經驗法則來選擇最佳的驗證方法: 
- JPA驗證的功能有限,但是如果可以將此類約束映射到DDL,則它是對實體類的最簡單約束的理想選擇。
 - Bean驗證是一種靈活,簡潔,聲明性,可重用和易讀的方式,可以涵蓋您可能在域模型類中進行的大多數檢查。 在大多數情況下,無需在事務內運行驗證時,這是最佳選擇。
 - 合同驗證是一個bean驗證,但用于方法調用。 當您需要檢查方法的輸入和輸出參數時,例如在REST調用處理程序中,請使用它。
 - 實體偵聽器:盡管它們不像Bean驗證注釋那樣聲明性,但它們是檢查大對象圖或進行需要在數據庫事務內完成的檢查的好地方。 例如,當您需要從數據庫讀取一些數據來做出決定時。 Hibernate具有此類偵聽器的類似物。
 - 事務偵聽器是危險的,但卻是在事務上下文中起作用的終極武器。 當您需要在運行時決定必須驗證哪些對象或何時需要根據同一驗證算法檢查許多不同類型的實體時,請使用它。
 
我希望本文能使您對有關Java企業應用程序中可用的不同驗證方法的記憶重新煥發,并為您提供一些有關如何改善正在處理的項目的體系結構的想法。
翻譯自: https://www.javacodegeeks.com/2018/10/validation-java-applications.html
總結
以上是生活随笔為你收集整理的Java应用程序中的验证的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: ddos如何防范(ddos怎么防范)
 - 下一篇: 可见性得以保障,并不意味着_战略模式并不