在Java EE 7和WildFly中使用Bean验证来验证JAX-RS资源数据
我過(guò)去已經(jīng)兩次接觸過(guò)這個(gè)主題。 首先,在我的文章《 在Java EE 6中將Bean驗(yàn)證與JAX-RS集成》中 ,介紹了甚至在Java EE平臺(tái)規(guī)范中未定義之前,如何在JBoss AS 7中將Bean驗(yàn)證與JAX-RS結(jié)合使用的方法。 后來(lái),在一篇為《 JAX Magazine 》撰寫(xiě)并隨后發(fā)表在《 JAXenter 》上的文章中,使用了帶有Glassfish 4服務(wù)器(第一臺(tái)經(jīng)過(guò)Java EE 7認(rèn)證的服務(wù)器)的Java EE 7中定義的新標(biāo)準(zhǔn)方式。
現(xiàn)在,以前稱(chēng)為JBoss Application Server的WildFly 8終于達(dá)到了最終版本,并加入了Java EE 7認(rèn)證的服務(wù)器俱樂(lè)部,現(xiàn)在該發(fā)表新文章了,重點(diǎn)介紹了這兩個(gè)應(yīng)用服務(wù)器GlassFish 4和WildFly之間的特性和差異。 8。
規(guī)格和API
Java EE 7是期待已久的Java EE 6的重大改進(jìn)。隨著Java EE的每個(gè)發(fā)行版,都添加了新功能并增強(qiáng)了現(xiàn)有規(guī)范。 Java EE 7以Java EE 6的成功為基礎(chǔ),并繼續(xù)致力于提高開(kāi)發(fā)人員的生產(chǎn)力。
JAX-RS是RESTful Web服務(wù)的Java API,是Java EE領(lǐng)域中發(fā)展最快的API之一。 當(dāng)然,這是由于基于REST的Web服務(wù)的大量采用以及使用這些服務(wù)的應(yīng)用程序數(shù)量的增加。
這篇文章將介紹配置REST端點(diǎn)以支持JavaScript客戶(hù)端以及處理驗(yàn)證異常以將本地化錯(cuò)誤消息發(fā)送給客戶(hù)端(除了HTTP錯(cuò)誤狀態(tài)代碼)所需的步驟。
源代碼
本文隨附的源代碼可在GitHub上找到 。
Bean驗(yàn)證簡(jiǎn)介
JavaBeans Validation( Bean驗(yàn)證 )是一種新的驗(yàn)證模型,可作為Java EE 6平臺(tái)的一部分使用。 約束條件支持Bean驗(yàn)證模型,該約束以注釋的形式出現(xiàn)在JavaBeans組件(例如托管Bean)的字段,方法或類(lèi)上。
javax.validation.constraints包中提供了一些內(nèi)置約束。 Java EE 7教程包含具有所有這些約束的列表。
Bean驗(yàn)證中的約束通過(guò)Java注釋表示:
public class Person {@NotNull@Size(min = 2, max = 50)private String name;// ... }Bean驗(yàn)證和RESTful Web服務(wù)
JAX-RS為提取請(qǐng)求值并將其綁定到Java字段,屬性和參數(shù)(使用@HeaderParam , @QueryParam等注釋)提供了強(qiáng)大的支持。它還支持通過(guò)非注釋參數(shù)將請(qǐng)求實(shí)體主體綁定到Java對(duì)象(即,未使用任何JAX-RS批注進(jìn)行批注的參數(shù))。 但是,在JAX-RS 2.0之前,必須以編程方式對(duì)資源類(lèi)中的這些值進(jìn)行任何其他驗(yàn)證。
最新版本的JAX-RS 2.0提供了一種解決方案,使驗(yàn)證批注可以與JAX-RS批注結(jié)合使用。
以下示例顯示了如何使用@Pattern驗(yàn)證批注來(lái)驗(yàn)證路徑參數(shù):
除了驗(yàn)證單個(gè)字段外,您還可以使用@Valid批注驗(yàn)證整個(gè)實(shí)體。
例如,下面的方法接收一個(gè)Person對(duì)象并對(duì)其進(jìn)行驗(yàn)證:
國(guó)際化
在前面的示例中,我們使用了默認(rèn)或硬編碼的錯(cuò)誤消息,但這既是一種不好的做法,又一點(diǎn)也不靈活。 I18n是Bean驗(yàn)證規(guī)范的一部分,它使我們能夠使用資源屬性文件來(lái)指定自定義錯(cuò)誤消息。 默認(rèn)資源文件名稱(chēng)為ValidationMessages.properties并且必須包含屬性/值對(duì),例如:
person.id.notnull=The person id must not be null person.id.pattern=The person id must be a valid number person.name.size=The person name must be between {min} and {max} chars long注意: {min}和{max}是指與消息相關(guān)聯(lián)的約束的屬性。
一旦定義,這些消息就可以注入到驗(yàn)證約束中,例如:
@POST @Path("create") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response createPerson(@FormParam("id")@NotNull(message = "{person.id.notnull}")@Pattern(regexp = "[0-9]+", message = "{person.id.pattern}")String id,@FormParam("name")@Size(min = 2, max = 50, message = "{person.name.size}")String name) {Person person = new Person();person.setId(Integer.valueOf(id));person.setName(name);persons.put(id, person);return Response.status(Response.Status.CREATED).entity(person).build(); }要提供其他語(yǔ)言的翻譯,必須使用翻譯后的消息創(chuàng)建一個(gè)新文件ValidationMessages_XX.properties ,其中XX是所提供語(yǔ)言的代碼。
不幸的是,對(duì)于某些應(yīng)用程序服務(wù)器,默認(rèn)的Validator提供程序不基于特定的HTTP請(qǐng)求支持i18n。 他們不考慮Accept-Language HTTP標(biāo)頭,并且始終使用Locale.getDefault()提供的默認(rèn)Locale 。 為了能夠使用Accept-Language HTTP標(biāo)頭(映射到瀏覽器選項(xiàng)中配置的語(yǔ)言)來(lái)更改Locale ,您必須提供一個(gè)自定義實(shí)現(xiàn)。
自定義驗(yàn)證器提供程序
盡管WildFly 8正確使用Accept-Language HTTP標(biāo)頭來(lái)選擇正確的資源包,但其他服務(wù)器(例如GlassFish 4)卻不使用此標(biāo)頭。 因此,為了完整性和與GlassFish代碼的比較(在同一個(gè)GitHub項(xiàng)目下提供 ),我還為WildFly實(shí)現(xiàn)了自定義Validator提供程序。
如果要查看GlassFish示例,請(qǐng)?jiān)L問(wèn)JAXenter上的Bean驗(yàn)證與JAX-RS集成。
WildFly使用RESTEasy ,這是JAX-RS規(guī)范的JBoss實(shí)現(xiàn)。
驗(yàn)證程序提供程序和Exception Mapper必需具有RESTEasy依賴(lài)關(guān)系,本文稍后將對(duì)此進(jìn)行討論。 讓我們將其添加到Maven:
ThreadLocal變量與普通變量不同,每個(gè)訪(fǎng)問(wèn)線(xiàn)程的線(xiàn)程都有其自己的,獨(dú)立初始化的變量副本。
/*** {@link ThreadLocal} to store the Locale to be used in the message interpolator.*/ public class LocaleThreadLocal {public static final ThreadLocal<Locale> THREAD_LOCAL = new ThreadLocal<Locale>();public static Locale get() {return (THREAD_LOCAL.get() == null) ? Locale.getDefault() : THREAD_LOCAL.get();}public static void set(Locale locale) {THREAD_LOCAL.set(locale);}public static void unset() {THREAD_LOCAL.remove();} }
請(qǐng)求過(guò)濾器負(fù)責(zé)讀取客戶(hù)端在Accept-Language HTTP標(biāo)頭中發(fā)送的第一語(yǔ)言并將Accept-Language Locale存儲(chǔ)在我們的ThreadLocal :
/*** Checks whether the {@code Accept-Language} HTTP header exists and creates a {@link ThreadLocal} to store the* corresponding Locale.*/ @Provider public class AcceptLanguageRequestFilter implements ContainerRequestFilter {@Contextprivate HttpHeaders headers;@Overridepublic void filter(ContainerRequestContext requestContext) throws IOException {if (!headers.getAcceptableLanguages().isEmpty()) {LocaleThreadLocal.set(headers.getAcceptableLanguages().get(0));}} }
接下來(lái),創(chuàng)建一個(gè)自定義消息插值器,以通過(guò)繞過(guò)或覆蓋默認(rèn)的Locale策略來(lái)強(qiáng)制執(zhí)行特定的Locale值:
/*** Delegates to a MessageInterpolator implementation but enforces a given Locale.*/ public class LocaleSpecificMessageInterpolator implements MessageInterpolator {private final MessageInterpolator defaultInterpolator;public LocaleSpecificMessageInterpolator(MessageInterpolator interpolator) {this.defaultInterpolator = interpolator;}@Overridepublic String interpolate(String message, Context context) {return defaultInterpolator.interpolate(message, context, LocaleThreadLocal.get());}@Overridepublic String interpolate(String message, Context context, Locale locale) {return defaultInterpolator.interpolate(message, context, locale);} }
RESTEasy通過(guò)查找實(shí)現(xiàn)ContextResolver<GeneralValidator>的提供程序來(lái)獲得Bean驗(yàn)證實(shí)現(xiàn)。
要配置新的驗(yàn)證服務(wù)提供者以使用我們的自定義消息插值器,請(qǐng)?zhí)砑右韵聝?nèi)容:
映射異常
默認(rèn)情況下,當(dāng)驗(yàn)證失敗時(shí),容器將引發(fā)異常,并將HTTP錯(cuò)誤返回給客戶(hù)端。
Bean驗(yàn)證規(guī)范定義了小的異常層次結(jié)構(gòu)(它們都繼承自ValidationException ),可以在驗(yàn)證引擎初始化期間或(在我們的情況下更重要)在輸入/輸出值驗(yàn)證期間拋出異常( ConstraintViolationException )。 如果拋出的異常是ValidationException的子類(lèi)( ConstraintViolationException除外),則此異常將映射到狀態(tài)碼為500(內(nèi)部服務(wù)器錯(cuò)誤)的HTTP響應(yīng)。 另一方面,當(dāng)拋出ConstraintViolationException時(shí),將返回兩個(gè)不同的狀態(tài)代碼:
- 500內(nèi)部服務(wù)器錯(cuò)誤)
如果在驗(yàn)證方法返回類(lèi)型時(shí)引發(fā)了異常。 - 400(錯(cuò)誤請(qǐng)求)
除此以外。
不幸的是,WildFly并沒(méi)有拋出ConstraintViolationException異常以獲取無(wú)效的輸入值, ResteasyViolationException拋出了ResteasyViolationException ,該異常實(shí)現(xiàn)了ValidationException接口。
可以自定義此行為,以允許我們將錯(cuò)誤消息添加到返回給客戶(hù)端的響應(yīng)中:
上面的示例是ExceptionMapper接口的實(shí)現(xiàn),該接口映射ValidationException類(lèi)型的異常。 驗(yàn)證失敗時(shí),Validator實(shí)現(xiàn)將引發(fā)此異常。 如果該異常是ResteasyViolationException的實(shí)例, ResteasyViolationException除了HTTP 400/500狀態(tài)代碼外,我們ResteasyViolationException在響應(yīng)中發(fā)送ViolationReport 。 這樣可以確保客戶(hù)端收到格式化的響應(yīng),而不僅僅是從資源傳播的異常。
產(chǎn)生的輸出類(lèi)似于以下內(nèi)容(JSON格式):
{"exception": null,"fieldViolations": [],"propertyViolations": [],"classViolations": [],"parameterViolations": [{"constraintType": "PARAMETER","path": "getPerson.id","message": "The id must be a valid number","value": "test"}],"returnValueViolations": [] }運(yùn)行和測(cè)試
要運(yùn)行本文使用的應(yīng)用程序,請(qǐng)使用Maven構(gòu)建項(xiàng)目,將其部署到WildFly 8應(yīng)用程序服務(wù)器中,然后將瀏覽器指向http:// localhost:8080 / jaxrs-beanvalidation-javaee7 / 。
另外,您也可以運(yùn)行在類(lèi)中的測(cè)試PersonsIT其內(nèi)置的Arquillian和JUnit的 。 Arquillian將自動(dòng)啟動(dòng)嵌入式WildFly 8容器,因此請(qǐng)確保您沒(méi)有在同一端口上運(yùn)行其他服務(wù)器。
建議和改進(jìn)
而對(duì)于Glassfish,您只需要定義正確的依賴(lài)項(xiàng)(org.glassfish.main.extras:glassfish-embedded-all)。
這是RESTEasy錯(cuò)誤嗎?
翻譯自: https://www.javacodegeeks.com/2014/04/validating-jax-rs-resource-data-with-bean-validation-in-java-ee-7-and-wildfly.html
總結(jié)
以上是生活随笔為你收集整理的在Java EE 7和WildFly中使用Bean验证来验证JAX-RS资源数据的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 宏碁传奇 Edge 笔记本降至 4999
- 下一篇: 借助Java 8和lambdas,可以一