Apache CXF 3.0:JAX-RS 2.0和Bean验证1.1最终一起
即將發(fā)布的出色的Apache CXF框架3.0版 (當(dāng)前處于里程碑2階段)帶來了許多有趣且有用的功能,越來越接近提供完整的JAX-RS 2.0支持。 Bean Validation 1.1的支持是我們中許多人期盼已久的功能之一:簡單而簡潔的模型可為您的REST服務(wù)層添加驗(yàn)證功能。
在這篇博客中,我們將研究如何在Apache CXF項(xiàng)目中配置Bean Validation 1.1 ,并討論一些有趣的用例。 為了使本篇文章簡短而集中,我們將不討論Bean Validation 1.1本身,而將更多的精力放在與JAX-RS 2.0資源的集成上(我們已經(jīng)在較早的文章中介紹了一些Bean驗(yàn)證基礎(chǔ)知識(shí))。
目前, Hibernate Validator是Bean Validation 1.1規(guī)范的實(shí)際參考實(shí)現(xiàn),最新版本為5.1.0.Final ,因此它將是我們選擇的驗(yàn)證提供程序(目前Apache BVal項(xiàng)目僅支持Bean驗(yàn)證1.0 )。 值得一提的是, Apache CXF與實(shí)現(xiàn)無關(guān),并且與Hibernate Validator或Apache BVal一經(jīng)發(fā)布便可以很好地兼容 。
我們將構(gòu)建一個(gè)非常簡單的應(yīng)用程序來管理人員。 我們的模型由一個(gè)名為Person的單個(gè)類組成。
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類對其屬性施加了一些限制:它們都不應(yīng)該為null 。 此外, 電子郵件屬性應(yīng)包含有效的電子郵件地址(將由Hibernate Validator特定的約束@Email進(jìn)行驗(yàn)證)。 很簡單
現(xiàn)在,讓我們看一下具有驗(yàn)證約束的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 }它看起來應(yīng)該很熟悉,沒有什么新意。 我們將使用驗(yàn)證約束添加和裝飾的第一種方法是getPerson ,它將通過其電子郵件地址查找一個(gè)人。
@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 ); }與傳統(tǒng)的JAX-RS 2.0方法聲明有幾個(gè)區(qū)別。 首先,我們希望電子郵件地址( 電子郵件路徑參數(shù))的長度至少為5個(gè)字符(但不超過255個(gè)字符),這由@Length(min = 5,max = 255)注釋強(qiáng)加。 其次,我們要確保此方法僅返回有效人,因此我們使用@Valid注釋對方法的返回值進(jìn)行注釋。 @Valid的作用非常有趣:將根據(jù)其類( Person )聲明的所有驗(yàn)證約束檢查該人員的實(shí)例。
目前,在您的Apache CXF項(xiàng)目中,默認(rèn)情況下Bean Validation 1.1不處于活動(dòng)狀態(tài),因此,如果您運(yùn)行應(yīng)用程序并調(diào)用此REST端點(diǎn),則所有驗(yàn)證約束都將被忽略。 好消息是,激活Bean Validation 1.1非常容易,因?yàn)樗恍鑼⑷齻€(gè)組件添加到您的常規(guī)配置中(請查看此功能文檔以獲取更多詳細(xì)信息和高級配置):
- JAXRSBeanValidationInInterceptor in-inteceptor:對JAX-RS 2.0資源方法的輸入?yún)?shù)進(jìn)行驗(yàn)證
- JAXRSBeanValidationOutInterceptor外接收器:執(zhí)行JAX-RS 2.0資源方法返回值的驗(yàn)證
- ValidationExceptionMapper異常映射器:將驗(yàn)證沖突映射到HTTP狀態(tài)碼 。 根據(jù)規(guī)范,所有輸入?yún)?shù)驗(yàn)證沖突都將導(dǎo)致400 Bad Request錯(cuò)誤。 分別,所有返回值驗(yàn)證沖突導(dǎo)致500內(nèi)部服務(wù)器錯(cuò)誤錯(cuò)誤。 目前, ValidationExceptionMapper尚未在響應(yīng)中包括其他信息(因?yàn)樗赡苓`反應(yīng)用程序協(xié)議),但是可以輕松地對其進(jìn)行擴(kuò)展以提供有關(guān)驗(yàn)證錯(cuò)誤的更多詳細(xì)信息。
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();} }注入了所有輸入/輸出攔截器和異常映射器。 太好了,讓我們構(gòu)建項(xiàng)目并運(yùn)行服務(wù)器以驗(yàn)證Bean Validation 1.1是否處于活動(dòng)狀態(tài)并按預(yù)期工作。
mvn clean package java -jar target/jaxrs-2.0-validation-0.0.1-SNAPSHOT.jar現(xiàn)在,如果我們使用短(或無效)電子郵件地址a @ b發(fā)出REST請求,則服務(wù)器應(yīng)返回400 Bad Request 。 讓我們驗(yàn)證一下。
> 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)優(yōu)秀的! 完全可以肯定的是,我們可以檢查服務(wù)器控制臺(tái)的輸出,并在其中找到ConstraintViolationException類型的驗(yàn)證異常及其堆棧跟蹤。 另外,最后一行提供了發(fā)生問題的詳細(xì)信息: PeopleRestService.getPerson.arg0:長度必須在5到255之間 (請注意,因?yàn)閰?shù)名稱在編譯后當(dāng)前在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方法來演示集合和實(shí)際的Response驗(yàn)證。
@Produces( { MediaType.APPLICATION_JSON } ) @GET public @Valid Collection< Person > getPeople( @Min( 1 ) @QueryParam( "count" ) @DefaultValue( "1" ) final int count ) {return peopleService.getPeople( count ); }對象集合上的@Valid批注將確保集合中的每個(gè)對象都是有效的。 @Min(1)注釋還將count參數(shù)限制為最小值1 (如果未指定查詢參數(shù), 則將@DefaultValue考慮在內(nèi))。 讓我們故意添加沒有設(shè)置名字和姓氏的人員,這樣結(jié)果集合將包含至少一個(gè)不通過驗(yàn)證過程的人員實(shí)例。
> curl http://localhost:8080/rest/api/people -X POST -id "email=a@b3.com"這樣,對getPeople REST方法的調(diào)用應(yīng)返回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)查看服務(wù)器控制臺(tái)輸出,就在這里提示錯(cuò)誤。
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最后,還有另一個(gè)例子,這次是通用的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(); }最后一個(gè)示例有些棘手: Response類是JAX-RS 2.0 API的一部分,并且沒有定義驗(yàn)證約束。 因此,在此類的實(shí)例上強(qiáng)加任何驗(yàn)證規(guī)則都不會(huì)觸發(fā)任何違規(guī)行為。 但是Apache CXF會(huì)盡力而為,并執(zhí)行一個(gè)簡單但有用的技巧:代替響應(yīng)實(shí)例,將對響應(yīng)的實(shí)體進(jìn)行驗(yàn)證。 我們可以通過嘗試創(chuàng)建一個(gè)沒有設(shè)置姓氏和名字的人來輕松驗(yàn)證這一點(diǎn):預(yù)期結(jié)果應(yīng)為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)服務(wù)器控制臺(tái)輸出更加詳細(xì):
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驗(yàn)證1.1如何通過提供如此豐富和可擴(kuò)展的聲明式驗(yàn)證支持來使您的Apache CXF項(xiàng)目更好的話題。 絕對可以試試看!
- 完整項(xiàng)目可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2014/04/apache-cxf-3-0-jax-rs-2-0-and-bean-validation-1-1-finally-together.html
總結(jié)
以上是生活随笔為你收集整理的Apache CXF 3.0:JAX-RS 2.0和Bean验证1.1最终一起的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大秦赋台词彩是什么意思 大秦赋台词彩含义
- 下一篇: Lambda表达式和流API:基本示例