日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

在Java EE 7和WildFly中使用Bean验证来验证JAX-RS资源数据

發布時間:2023/12/3 java 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 在Java EE 7和WildFly中使用Bean验证来验证JAX-RS资源数据 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我過去已經兩次接觸過這個主題。 首先,在我的文章《 在Java EE 6中將Bean驗證與JAX-RS集成》中 ,介紹了甚至在Java EE平臺規范中未定義之前,如何在JBoss AS 7中將Bean驗證與JAX-RS結合使用的方法。 后來,在一篇為《 JAX Magazine 》撰寫并隨后發表在《 JAXenter 》上的文章中,使用了帶有Glassfish 4服務器(第一臺經過Java EE 7認證的服務器)的Java EE 7中定義的新標準方式。
現在,以前稱為JBoss Application Server的WildFly 8終于達到了最終版本,并加入了Java EE 7認證的服務器俱樂部,現在該發表新文章了,重點介紹了這兩個應用服務器GlassFish 4和WildFly之間的特性和差異。 8。

規格和API

Java EE 7是期待已久的Java EE 6的重大改進。隨著Java EE的每個發行版,都添加了新功能并增強了現有規范。 Java EE 7以Java EE 6的成功為基礎,并繼續致力于提高開發人員的生產力。

JAX-RS是RESTful Web服務的Java API,是Java EE領域中發展最快的API之一。 當然,這是由于基于REST的Web服務的大量采用以及使用這些服務的應用程序數量的增加。

這篇文章將介紹配置REST端點以支持JavaScript客戶端以及處理驗證異常以將本地化錯誤消息發送給客戶端(除了HTTP錯誤狀態代碼)所需的步驟。

源代碼

本文隨附的源代碼可在GitHub上找到 。

Bean驗證簡介

JavaBeans Validation( Bean驗證 )是一種新的驗證模型,可作為Java EE 6平臺的一部分使用。 約束條件支持Bean驗證模型,該約束以注釋的形式出現在JavaBeans組件(例如托管Bean)的字段,方法或類上。

javax.validation.constraints包中提供了一些內置約束。 Java EE 7教程包含具有所有這些約束的列表。

Bean驗證中的約束通過Java注釋表示:

public class Person {@NotNull@Size(min = 2, max = 50)private String name;// ... }

Bean驗證和RESTful Web服務

JAX-RS為提取請求值并將其綁定到Java字段,屬性和參數(使用@HeaderParam , @QueryParam等注釋)提供了強大的支持。它還支持通過非注釋參數將請求實體主體綁定到Java對象(即,未使用任何JAX-RS批注進行批注的參數)。 但是,在JAX-RS 2.0之前,必須以編程方式對資源類中的這些值進行任何其他驗證。

最新版本的JAX-RS 2.0提供了一種解決方案,使驗證批注可以與JAX-RS批注結合使用。
以下示例顯示了如何使用@Pattern驗證批注來驗證路徑參數:

@GET @Path("{id}") public Person getPerson(@PathParam("id")@Pattern(regexp = "[0-9]+", message = "The id must be a valid number")String id) {return persons.get(id); }

除了驗證單個字段外,您還可以使用@Valid批注驗證整個實體。
例如,下面的方法接收一個Person對象并對其進行驗證:

@POST public Response validatePerson(@Valid Person person) {// ... }

國際化

在前面的示例中,我們使用了默認或硬編碼的錯誤消息,但這既是一種不好的做法,又一點也不靈活。 I18n是Bean驗證規范的一部分,它使我們能夠使用資源屬性文件來指定自定義錯誤消息。 默認資源文件名稱為ValidationMessages.properties并且必須包含屬性/值對,例如:

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}是指與消息相關聯的約束的屬性。

一旦定義,這些消息就可以注入到驗證約束中,例如:

@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(); }

要提供其他語言的翻譯,必須使用翻譯后的消息創建一個新文件ValidationMessages_XX.properties ,其中XX是所提供語言的代碼。

不幸的是,對于某些應用程序服務器,默認的Validator提供程序不基于特定的HTTP請求支持i18n。 他們不考慮Accept-Language HTTP標頭,并且始終使用Locale.getDefault()提供的默認Locale 。 為了能夠使用Accept-Language HTTP標頭(映射到瀏覽器選項中配置的語言)來更改Locale ,您必須提供一個自定義實現。

自定義驗證器提供程序

盡管WildFly 8正確使用Accept-Language HTTP標頭來選擇正確的資源包,但其他服務器(例如GlassFish 4)卻不使用此標頭。 因此,為了完整性和與GlassFish代碼的比較(在同一個GitHub項目下提供 ),我還為WildFly實現了自定義Validator提供程序。
如果要查看GlassFish示例,請訪問JAXenter上的Bean驗證與JAX-RS集成。

  • 將RESTEasy依賴項添加到Maven
  • WildFly使用RESTEasy ,這是JAX-RS規范的JBoss實現。
    驗證程序提供程序和Exception Mapper必需具有RESTEasy依賴關系,本文稍后將對此進行討論。 讓我們將其添加到Maven:

    <dependencyManagement><dependencies><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-bom</artifactId><version>3.0.6.Final</version><scope>import</scope><type>pom</type></dependency></dependencies> </dependencyManagement><dependencies><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-jaxrs</artifactId><scope>provided</scope></dependency><dependency><groupId>org.jboss.resteasy</groupId><artifactId>resteasy-validator-provider-11</artifactId><scope>provided</scope></dependency> </dependencies>

  • 創建一個ThreadLocal來存儲Locale從Accept-Language HTTP標頭
  • ThreadLocal變量與普通變量不同,每個訪問線程的線程都有其自己的,獨立初始化的變量副本。

    /*** {@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();} }

  • 創建一個請求過濾器以讀取Accept-Language HTTP標頭
  • 請求過濾器負責讀取客戶端在Accept-Language HTTP標頭中發送的第一語言并將Accept-Language Locale存儲在我們的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));}} }

  • 創建自定義消息插值器以強制執行特定的Locale
  • 接下來,創建一個自定義消息插值器,以通過繞過或覆蓋默認的Locale策略來強制執行特定的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通過查找實現ContextResolver<GeneralValidator>的提供程序來獲得Bean驗證實現。
    要配置新的驗證服務提供者以使用我們的自定義消息插值器,請添加以下內容:

    /*** Custom configuration of validation. This configuration can define custom:* <ul>* <li>MessageInterpolator - interpolates a given constraint violation message.</li>* <li>TraversableResolver - determines if a property can be accessed by the Bean Validation provider.</li>* <li>ConstraintValidatorFactory - instantiates a ConstraintValidator instance based off its class.* <li>ParameterNameProvider - provides names for method and constructor parameters.</li> ** </ul>*/ @Provider public class ValidationConfigurationContextResolver implements ContextResolver<GeneralValidator> {/*** Get a context of type {@code GeneralValidator} that is applicable to the supplied type.** @param type the class of object for which a context is desired* @return a context for the supplied type or {@code null} if a context for the supplied type is not available from* this provider.*/@Overridepublic GeneralValidator getContext(Class<?> type) {Configuration<?> config = Validation.byDefaultProvider().configure();BootstrapConfiguration bootstrapConfiguration = config.getBootstrapConfiguration();config.messageInterpolator(new LocaleSpecificMessageInterpolator(Validation.byDefaultProvider().configure().getDefaultMessageInterpolator()));return new GeneralValidatorImpl(config.buildValidatorFactory(),bootstrapConfiguration.isExecutableValidationEnabled(),bootstrapConfiguration.getDefaultValidatedExecutableTypes());} }
  • 映射異常

    默認情況下,當驗證失敗時,容器將引發異常,并將HTTP錯誤返回給客戶端。

    Bean驗證規范定義了小的異常層次結構(它們都繼承自ValidationException ),可以在驗證引擎初始化期間或(在我們的情況下更重要)在輸入/輸出值驗證期間拋出異常( ConstraintViolationException )。 如果拋出的異常是ValidationException的子類( ConstraintViolationException除外),則此異常將映射到狀態碼為500(內部服務器錯誤)的HTTP響應。 另一方面,當拋出ConstraintViolationException時,將返回兩個不同的狀態代碼:

    • 500內部服務器錯誤)
      如果在驗證方法返回類型時引發了異常。
    • 400(錯誤請求)
      除此以外。

    不幸的是,WildFly并沒有拋出ConstraintViolationException異常以獲取無效的輸入值, ResteasyViolationException拋出了ResteasyViolationException ,該異常實現了ValidationException接口。
    可以自定義此行為,以允許我們將錯誤消息添加到返回給客戶端的響應中:

    /*** {@link ExceptionMapper} for {@link ValidationException}.* <p>* Send a {@link ViolationReport} in {@link Response} in addition to HTTP 400/500 status code. Supported media types* are: {@code application/json} / {@code application/xml} (if appropriate provider is registered on server).* </p>** @see org.jboss.resteasy.api.validation.ResteasyViolationExceptionMapper The original WildFly class:* {@code org.jboss.resteasy.api.validation.ResteasyViolationExceptionMapper}*/ @Provider public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {@Overridepublic Response toResponse(ValidationException exception) {if (exception instanceof ConstraintDefinitionException) {return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);}if (exception instanceof ConstraintDeclarationException) {return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);}if (exception instanceof GroupDefinitionException) {return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);}if (exception instanceof ResteasyViolationException) {ResteasyViolationException resteasyViolationException = ResteasyViolationException.class.cast(exception);Exception e = resteasyViolationException.getException();if (e != null) {return buildResponse(unwrapException(e), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);} else if (resteasyViolationException.getReturnValueViolations().size() == 0) {return buildViolationReportResponse(resteasyViolationException, Status.BAD_REQUEST);} else {return buildViolationReportResponse(resteasyViolationException, Status.INTERNAL_SERVER_ERROR);}}return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR);}protected Response buildResponse(Object entity, String mediaType, Status status) {ResponseBuilder builder = Response.status(status).entity(entity);builder.type(MediaType.TEXT_PLAIN);builder.header(Validation.VALIDATION_HEADER, "true");return builder.build();}protected Response buildViolationReportResponse(ResteasyViolationException exception, Status status) {ResponseBuilder builder = Response.status(status);builder.header(Validation.VALIDATION_HEADER, "true");// Check standard media types.MediaType mediaType = getAcceptMediaType(exception.getAccept());if (mediaType != null) {builder.type(mediaType);builder.entity(new ViolationReport(exception));return builder.build();}// Default media type.builder.type(MediaType.TEXT_PLAIN);builder.entity(exception.toString());return builder.build();}protected String unwrapException(Throwable t) {StringBuffer sb = new StringBuffer();doUnwrapException(sb, t);return sb.toString();}private void doUnwrapException(StringBuffer sb, Throwable t) {if (t == null) {return;}sb.append(t.toString());if (t.getCause() != null && t != t.getCause()) {sb.append('[');doUnwrapException(sb, t.getCause());sb.append(']');}}private MediaType getAcceptMediaType(List<MediaType> accept) {Iterator<MediaType> it = accept.iterator();while (it.hasNext()) {MediaType mt = it.next();/** application/xml media type causes an exception:* org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response* object of type: org.jboss.resteasy.api.validation.ViolationReport of media type: application/xml*//*if (MediaType.APPLICATION_XML_TYPE.getType().equals(mt.getType())&& MediaType.APPLICATION_XML_TYPE.getSubtype().equals(mt.getSubtype())) {return MediaType.APPLICATION_XML_TYPE;}*/if (MediaType.APPLICATION_JSON_TYPE.getType().equals(mt.getType())&& MediaType.APPLICATION_JSON_TYPE.getSubtype().equals(mt.getSubtype())) {return MediaType.APPLICATION_JSON_TYPE;}}return null;} }

    上面的示例是ExceptionMapper接口的實現,該接口映射ValidationException類型的異常。 驗證失敗時,Validator實現將引發此異常。 如果該異常是ResteasyViolationException的實例, ResteasyViolationException除了HTTP 400/500狀態代碼外,我們ResteasyViolationException在響應中發送ViolationReport 。 這樣可以確保客戶端收到格式化的響應,而不僅僅是從資源傳播的異常。

    產生的輸出類似于以下內容(JSON格式):

    {"exception": null,"fieldViolations": [],"propertyViolations": [],"classViolations": [],"parameterViolations": [{"constraintType": "PARAMETER","path": "getPerson.id","message": "The id must be a valid number","value": "test"}],"returnValueViolations": [] }

    運行和測試

    要運行本文使用的應用程序,請使用Maven構建項目,將其部署到WildFly 8應用程序服務器中,然后將瀏覽器指向http:// localhost:8080 / jaxrs-beanvalidation-javaee7 / 。

    另外,您也可以運行在類中的測試PersonsIT其內置的Arquillian和JUnit的 。 Arquillian將自動啟動嵌入式WildFly 8容器,因此請確保您沒有在同一端口上運行其他服務器。

    建議和改進

  • 為了實現自定義的驗證程序提供程序,我們依賴于應用程序服務器代碼。 在GlassFish 4上,需要實現ContextResolver ContextResolver<ValidationConfig> ,而在WildFly 8上,我們需要實現ContextResolver<GeneralValidator> 。 為什么不在Java EE 7規范中定義ValidationConfig和GeneralValidator必須實現的接口,而不是依賴于應用程序服務器特定的代碼?
  • 使WildFly 8 Embedded易于使用和通過Maven進行配置。 當前,要使Arquillian可以使用它,需要下載WildFly發行版(org.wildfly:wildfly-dist),將其解壓縮到target文件夾中,并在Surefire / Failsafe Maven插件上配置系統屬性: <systemPropertyVariables><java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager><jboss.home>${wildfly.home}</jboss.home><module.path>${wildfly.home}/modules</module.path> </systemPropertyVariables>

    而對于Glassfish,您只需要定義正確的依賴項(org.glassfish.main.extras:glassfish-embedded-all)。

  • 使RESTEasy成為WildFly Embedded的可傳遞依賴項。 僅通過定義provided WildFly Embedded依賴項,在編譯時就可以使用所有WildFly模塊,這將是一個很好的生產力提升。
  • 當前無法在Eclipse上使用選項Run As >> JUnit Test ,因為必須存在名為jbossHome的系統屬性。 Eclipse不會從Surefire / Failsafe配置中讀取此屬性。 有沒有解決方法?
  • 當使用RESTEasy的ExceptionMapper<ValidationException>默認實現時,以application/xml媒體類型請求數據并發生驗證錯誤,將引發以下異常: org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure:Could not find MessageBodyWriter for response object of type:org.jboss.resteasy.api.validation.ViolationReport of media type:application/xml

    這是RESTEasy錯誤嗎?

  • 翻譯自: https://www.javacodegeeks.com/2014/04/validating-jax-rs-resource-data-with-bean-validation-in-java-ee-7-and-wildfly.html

    總結

    以上是生活随笔為你收集整理的在Java EE 7和WildFly中使用Bean验证来验证JAX-RS资源数据的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。