Apache CXF 3.0:JAX-RS 2.0和Bean验证1.1最终一起
即將發布的出色的Apache CXF框架3.0版 (當前處于里程碑2階段)帶來了許多有趣且有用的功能,越來越接近提供完整的JAX-RS 2.0支持。 Bean Validation 1.1的支持是我們中許多人期盼已久的功能之一:簡單而簡潔的模型可為您的REST服務層添加驗證功能。
在這篇博客中,我們將研究如何在Apache CXF項目中配置Bean Validation 1.1 ,并討論一些有趣的用例。 為了使本篇文章簡短而集中,我們將不討論Bean Validation 1.1本身,而將更多的精力放在與JAX-RS 2.0資源的集成上(我們已經在較早的文章中介紹了一些Bean驗證基礎知識)。
目前, Hibernate Validator是Bean Validation 1.1規范的實際參考實現,最新版本為5.1.0.Final ,因此它將是我們選擇的驗證提供程序(目前Apache BVal項目僅支持Bean驗證1.0 )。 值得一提的是, Apache CXF與實現無關,并且與Hibernate Validator或Apache BVal一經發布便可以很好地兼容 。
我們將構建一個非常簡單的應用程序來管理人員。 我們的模型由一個名為Person的單個類組成。
package com.example.model;import javax.validation.constraints.NotNull;import org.hibernate.validator.constraints.Email;public class Person {@NotNull @Email private String email;@NotNull private String firstName;@NotNull private String lastName;public Person() {}public Person( final String email ) {this.email = email;}public String getEmail() {return email;}public void setEmail( final String email ) {this.email = email;}public String getFirstName() {return firstName;}public String getLastName() {return lastName;}public void setFirstName( final String firstName ) {this.firstName = firstName;}public void setLastName( final String lastName ) {this.lastName = lastName;} }從上面的代碼片段中,我們可以看到Person類對其屬性施加了一些限制:它們都不應該為null 。 此外, 電子郵件屬性應包含有效的電子郵件地址(將由Hibernate Validator特定的約束@Email進行驗證)。 很簡單
現在,讓我們看一下具有驗證約束的JAX-RS 2.0資源。 PeopleRestService類的框架綁定到/ people URL路徑,如下所示。
package com.example.rs;import java.util.Collection;import javax.inject.Inject; import javax.validation.Valid; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo;import org.hibernate.validator.constraints.Length;import com.example.model.Person; import com.example.services.PeopleService;@Path( "/people" ) public class PeopleRestService {@Inject private PeopleService peopleService;// REST methods here }它看起來應該很熟悉,沒有什么新意。 我們將使用驗證約束添加和裝飾的第一種方法是getPerson ,它將通過其電子郵件地址查找一個人。
@Produces( { MediaType.APPLICATION_JSON } ) @Path( "/{email}" ) @GET public @Valid Person getPerson( @Length( min = 5, max = 255 ) @PathParam( "email" ) final String email ) {return peopleService.getByEmail( email ); }與傳統的JAX-RS 2.0方法聲明有幾個區別。 首先,我們希望電子郵件地址( 電子郵件路徑參數)的長度至少為5個字符(但不超過255個字符),這由@Length(min = 5,max = 255)注釋強加。 其次,我們要確保此方法僅返回有效人,因此我們使用@Valid注釋對方法的返回值進行注釋。 @Valid的作用非常有趣:將根據其類( Person )聲明的所有驗證約束檢查該人員的實例。
目前,在您的Apache CXF項目中,默認情況下Bean Validation 1.1不處于活動狀態,因此,如果您運行應用程序并調用此REST端點,則所有驗證約束都將被忽略。 好消息是,激活Bean Validation 1.1非常容易,因為它只需將三個組件添加到您的常規配置中(請查看此功能文檔以獲取更多詳細信息和高級配置):
- JAXRSBeanValidationInInterceptor in-inteceptor:對JAX-RS 2.0資源方法的輸入參數進行驗證
- JAXRSBeanValidationOutInterceptor外接收器:執行JAX-RS 2.0資源方法返回值的驗證
- ValidationExceptionMapper異常映射器:將驗證沖突映射到HTTP狀態碼 。 根據規范,所有輸入參數驗證沖突都將導致400 Bad Request錯誤。 分別,所有返回值驗證沖突導致500內部服務器錯誤錯誤。 目前, ValidationExceptionMapper尚未在響應中包括其他信息(因為它可能違反應用程序協議),但是可以輕松地對其進行擴展以提供有關驗證錯誤的更多詳細信息。
AppConfig類展示了使用RuntimeDelegate和JAXRSServerFactoryBean將所有必需的組件連接在一起的方法之一 (也支持基于XML的配置)。
package com.example.config;import java.util.Arrays;import javax.ws.rs.ext.RuntimeDelegate;import org.apache.cxf.bus.spring.SpringBus; import org.apache.cxf.endpoint.Server; import org.apache.cxf.interceptor.Interceptor; import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; import org.apache.cxf.jaxrs.validation.JAXRSBeanValidationInInterceptor; import org.apache.cxf.jaxrs.validation.JAXRSBeanValidationOutInterceptor; import org.apache.cxf.jaxrs.validation.ValidationExceptionMapper; import org.apache.cxf.message.Message; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn;import com.example.rs.JaxRsApiApplication; import com.example.rs.PeopleRestService; import com.example.services.PeopleService; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;@Configuration public class AppConfig { @Bean( destroyMethod = "shutdown" )public SpringBus cxf() {return new SpringBus();}@Bean @DependsOn( "cxf" )public Server jaxRsServer() {final JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint( jaxRsApiApplication(), JAXRSServerFactoryBean.class );factory.setServiceBeans( Arrays.< Object >asList( peopleRestService() ) );factory.setAddress( factory.getAddress() );factory.setInInterceptors( Arrays.< Interceptor< ? extends Message > >asList( new JAXRSBeanValidationInInterceptor()) );factory.setOutInterceptors( Arrays.< Interceptor< ? extends Message > >asList( new JAXRSBeanValidationOutInterceptor() ) );factory.setProviders( Arrays.asList( new ValidationExceptionMapper(), new JacksonJsonProvider() ) );return factory.create();}@Bean public JaxRsApiApplication jaxRsApiApplication() {return new JaxRsApiApplication();}@Bean public PeopleRestService peopleRestService() {return new PeopleRestService();}@Bean public PeopleService peopleService() {return new PeopleService();} }注入了所有輸入/輸出攔截器和異常映射器。 太好了,讓我們構建項目并運行服務器以驗證Bean Validation 1.1是否處于活動狀態并按預期工作。
mvn clean package java -jar target/jaxrs-2.0-validation-0.0.1-SNAPSHOT.jar現在,如果我們使用短(或無效)電子郵件地址a @ b發出REST請求,則服務器應返回400 Bad Request 。 讓我們驗證一下。
> curl http://localhost:8080/rest/api/people/a@b -iHTTP/1.1 400 Bad Request Date: Wed, 26 Mar 2014 00:11:59 GMT Content-Length: 0 Server: Jetty(9.1.z-SNAPSHOT)優秀的! 完全可以肯定的是,我們可以檢查服務器控制臺的輸出,并在其中找到ConstraintViolationException類型的驗證異常及其堆棧跟蹤。 另外,最后一行提供了發生問題的詳細信息: PeopleRestService.getPerson.arg0:長度必須在5到255之間 (請注意,因為參數名稱在編譯后當前在JVM上不可用,所以將它們替換為占位符,例如arg0 , arg1) ,…)。
WARNING: Interceptor for {http://rs.example.com/}PeopleRestService has thrown exception, unwinding now javax.validation.ConstraintViolationExceptionat org.apache.cxf.validation.BeanValidationProvider.validateParameters(BeanValidationProvider.java:119)at org.apache.cxf.validation.BeanValidationInInterceptor.handleValidation(BeanValidationInInterceptor.java:59)at org.apache.cxf.validation.AbstractValidationInterceptor.handleMessage(AbstractValidationInterceptor.java:73)at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:307)at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121)at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:240)at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:223)at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:197)at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:149)at org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:167)at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:286)at org.apache.cxf.transport.servlet.AbstractHTTPServlet.doGet(AbstractHTTPServlet.java:211)at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)at org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:262)at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:711)at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:552)at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1112)at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:479)at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1046)at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)at org.eclipse.jetty.server.Server.handle(Server.java:462)at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:281)at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:232)at org.eclipse.jetty.io.AbstractConnection$1.run(AbstractConnection.java:505)at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:607)at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:536)at java.lang.Thread.run(Unknown Source)Mar 25, 2014 8:11:59 PM org.apache.cxf.jaxrs.validation.ValidationExceptionMapper toResponse WARNING: PeopleRestService.getPerson.arg0: length must be between 5 and 255接下來,我們將添加另外兩種REST方法來演示集合和實際的Response驗證。
@Produces( { MediaType.APPLICATION_JSON } ) @GET public @Valid Collection< Person > getPeople( @Min( 1 ) @QueryParam( "count" ) @DefaultValue( "1" ) final int count ) {return peopleService.getPeople( count ); }對象集合上的@Valid批注將確保集合中的每個對象都是有效的。 @Min(1)注釋還將count參數限制為最小值1 (如果未指定查詢參數, 則將@DefaultValue考慮在內)。 讓我們故意添加沒有設置名字和姓氏的人員,這樣結果集合將包含至少一個不通過驗證過程的人員實例。
> curl http://localhost:8080/rest/api/people -X POST -id "email=a@b3.com"這樣,對getPeople REST方法的調用應返回500 Internal Server Error 。 讓我們檢查情況是否如此。
> curl -i http://localhost:8080/rest/api/people?count=10HTTP/1.1 500 Server Error Date: Wed, 26 Mar 2014 01:28:58 GMT Content-Length: 0 Server: Jetty(9.1.z-SNAPSHOT)查看服務器控制臺輸出,就在這里提示錯誤。
Mar 25, 2014 9:28:58 PM org.apache.cxf.jaxrs.validation.ValidationExceptionMapper toResponse WARNING: PeopleRestService.getPeople.[0].firstName: may not be null Mar 25, 2014 9:28:58 PM org.apache.cxf.jaxrs.validation.ValidationExceptionMapper toResponse WARNING: PeopleRestService.getPeople.[0].lastName: may not be null最后,還有另一個例子,這次是通用的Response對象。
@Valid @Produces( { MediaType.APPLICATION_JSON } ) @POST public Response addPerson( @Context final UriInfo uriInfo,@NotNull @Length( min = 5, max = 255 ) @FormParam( "email" ) final String email, @FormParam( "firstName" ) final String firstName, @FormParam( "lastName" ) final String lastName ) { final Person person = peopleService.addPerson( email, firstName, lastName );return Response.created( uriInfo.getRequestUriBuilder().path( email ).build() ).entity( person ).build(); }最后一個示例有些棘手: Response類是JAX-RS 2.0 API的一部分,并且沒有定義驗證約束。 因此,在此類的實例上強加任何驗證規則都不會觸發任何違規行為。 但是Apache CXF會盡力而為,并執行一個簡單但有用的技巧:代替響應實例,將對響應的實體進行驗證。 我們可以通過嘗試創建一個沒有設置姓氏和名字的人來輕松驗證這一點:預期結果應為500 Internal Server Error 。
> curl http://localhost:8080/rest/api/people -X POST -id "email=a@b3.com"HTTP/1.1 500 Server Error Date: Wed, 26 Mar 2014 01:13:06 GMT Content-Length: 0 Server: Jetty(9.1.z-SNAPSHOT)服務器控制臺輸出更加詳細:
Mar 25, 2014 9:13:06 PM org.apache.cxf.jaxrs.validation.ValidationExceptionMapper toResponse WARNING: PeopleRestService.addPerson.<return value>.firstName: may not be null Mar 25, 2014 9:13:06 PM org.apache.cxf.jaxrs.validation.ValidationExceptionMapper toResponse WARNING: PeopleRestService.addPerson.<return value>.lastName: may not be null真好! 在本博文中,我們談到了Bean驗證1.1如何通過提供如此豐富和可擴展的聲明式驗證支持來使您的Apache CXF項目更好的話題。 絕對可以試試看!
- 完整項目可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2014/04/apache-cxf-3-0-jax-rs-2-0-and-bean-validation-1-1-finally-together.html
總結
以上是生活随笔為你收集整理的Apache CXF 3.0:JAX-RS 2.0和Bean验证1.1最终一起的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大秦赋台词彩是什么意思 大秦赋台词彩含义
- 下一篇: Lambda表达式和流API:基本示例