即将发布的出色的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