第24章. Web服务
Seam 集成了JBossWS,允许标准JEE web服务充分利用Seam的上下文框架的优势, 包括支持对话web服务。本章通过必要的步骤允许web服务运行在一个Seam 环境。
24.1. 配置和打包
为了允许Seam拦截web服务请求,以便于可以为请求创建必要Seam上下文,一个特殊的SOAP(简单对象存取协议)处理器必须被配置; org.jboss.seam.webservice.SOAPRequestHandler是一个 SOAPHandler 实现,管理在一个web服务请求作用域期间的Seam的生命周期。
一个特殊的配置文件, standard-jaxws-endpoint-config.xml应该被放在包含web服务类jar文件的META-INF的目录下。这个文件包含下面的SOAP处理器配置:
<jaxws-config xmlns="urn:jboss:jaxws-config:2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="urn:jboss:jaxws-config:2.0 jaxws-config_2_0.xsd">
<endpoint-config>
<config-name>Seam WebService Endpoint</config-name>
<pre-handler-chains>
<javaee:handler-chain>
<javaee:protocol-bindings>##SOAP11_HTTP</javaee:protocol-bindings>
<javaee:handler>
<javaee:handler-name>SOAP Request Handler</javaee:handler-name>
<javaee:handler-class>org.jboss.seam.webservice.SOAPRequestHandler</javaee:handler-class>
</javaee:handler>
</javaee:handler-chain>
</pre-handler-chains>
</endpoint-config>
</jaxws-config>
24.2. 对话的Web服务
那么对话是如何在web服务请求之间传播呢? Seam 使用一个SOAP头元素表示SOAP请求和响应消息,携带着对话ID在消费者与服务器之间传递。这里是一个包含对话ID的web服务的例子:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:seam="http://seambay.example.seam.jboss.org/">
<soapenv:Header>
<seam:conversationId xmlns:seam='http://www.jboss.org/seam/webservice'>2</seam:conversationId>
</soapenv:Header>
<soapenv:Body>
<seam:confirmAuction/>
</soapenv:Body>
</soapenv:Envelope>
正如你在上面SOAP消息中看见的,在SOAP头内有一个conversationId 元素,包含了一个请求的对话ID,在这里是2。不幸的是,因为web服务由各种语言编写的各种web服务客户端消费,这由开发者实现对话ID在单一对话作用域内使用的单独web服务之间的传播。
注意,一个重要的事情是conversationId头元素必须合乎http://www.jboss.org/seam/webservice的一个命名空间的条件,否则Seam不能读取请求的对话ID 。这里是一个响应上述请求消息的例子:
<env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'>
<env:Header>
<seam:conversationId xmlns:seam='http://www.jboss.org/seam/webservice'>2</seam:conversationId>
</env:Header>
<env:Body>
<confirmAuctionResponse xmlns="http://seambay.example.seam.jboss.org/"/>
</env:Body>
</env:Envelope>
正如你看见的,响应消息包含与请求一样的conversationId元素。
24.2.1. 推荐策略
因为web服务必须由无状态会话bean或POJO实现,所以,对于对话web 服务,推荐web服务作为对话Seam组件的一个正面(facade)。
如果web服务作为无状态会话被编写,那么也可以通过@Name注释使其成为Seam组件。这样做便于Seam双向注入 (或其他)功能用于web服务类自身。
24.3. web服务例子
让我们浏览一个web服务例子。这节的所有代码来自Seam的 /examples 目录下的seamBay例子应用程序, 并遵循前节所述的推荐策略。 首先让我们看一看web服务类和它的一个web服务方法:
@Stateless
@WebService(name = "AuctionService", serviceName = "AuctionService")
public class AuctionService implements AuctionServiceRemote
{
@WebMethod
public boolean login(String username, String password)
{
Identity.instance().setUsername(username);
Identity.instance().setPassword(password);
Identity.instance().login();
return Identity.instance().isLoggedIn();
}
……
}
正如你看见的,我们的web服务是一个无状态会话bean,并用javax.jws包的JWS注释注释了它,如JSR-181定义一样。 @WebService注释告诉容器该类实现了web服务,在login()方法上的@WebMethod注释指明该方法是一个web服务方法。@WebService 注释的Name和 serviceName属性是可选的。
如规范要求的一样,被暴露来作为web服务方法的每个方法必须在web服务类的远程接口中也被声明(当web服务是一个无状态会话bean时)。在上面的例子中, AuctionServiceRemote接口声明了与用@WebMethod注释一样的login()方法。
如你在上面代码中所见的, web服务实现了一个代理Seam内建的Identity组件的login()方法。按照我们推荐的策略,web服务作为一个简单的正面(facade)被编写,假冒Seam组件的真正工作。这样可以最大程度的重用web服务和客户端之间的业务逻辑。
让我看另外一个例子。这个web服务方法通过委托AuctionAction.createAuction()方法开始了一个新的对话:
@WebMethod
public void createAuction(String title, String description, int categoryId)
{
AuctionAction action = (AuctionAction) Component.getInstance(AuctionAction.class, true);
action.createAuction();
action.setDetails(title, description, categoryId);
}
下面是来自AuctionAction的代码:
@Begin
public void createAuction()
{
auction = new Auction();
auction.setAccount(authenticatedAccount);
auction.setStatus(Auction.STATUS_UNLISTED);
durationDays = DEFAULT_AUCTION_DURATION;
}
从这里我们可以看见,web服务如何通过作为一个正面(facade)并代理对话Seam组件的真正工作参与到长期运行对话。
24.4. 使用RESTEasy的RESTful HTTP web服务
Seam集成了实现了JAX-RS规范(JSR 311)的RESTEasy。 你可以决定要多么“深”地集成到你的Seam应用程序:
24.4.1. RESTEasy配置和请求服务
首先,得到RESTEasy库和jaxrs-api.jar包,与你的应用程序带的其他库一起部署。 还要部署集成的库jboss-seam-resteasy.jar 。
在启动时,所有注释了@javax.ws.rs.Path的类会被自动发现并注册成HTTP资源。 Seam用它内建的SeamResourceServlet自动访问并服务于HTTP请求。资源的URI(统一资源标识符)象下面这样被构建:
举一个例子,下面的资源定义会对使用URI http://your.hostname/seam/resource/rest/customer/123的任何GET请求返回一个纯文本表示:
@Path("/customer")
public class MyCustomerResource {
@GET
@Path("/{customerId}")
@ProduceMime("text/plain")
public String getCustomer(@PathParam("customerId") int id) {
return ...;
}
}
如果这些默认是可以接受的,你不需要另外的配置,不必编辑web.xml 或任何其他设置。 然而,你可以在你的Seam 应用程序中配置RESTEasy 。 首先导入resteasy命名空间到你的XML配置文件头:
<components
xmlns="http://jboss.com/products/seam/components"
xmlns:resteasy="http://jboss.com/products/seam/resteasy"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
http://jboss.com/products/seam/resteasy
http://jboss.com/products/seam/resteasy-2.1.xsd
http://jboss.com/products/seam/components
http://jboss.com/products/seam/components-2.1.xsd">
然后,你可以象前面提及的改变/rest前缀:
<resteasy:application-config resource-path-prefix="/restv1"/>
现在你的资源的完整路径是/seam/resource/restv1/{resource} ——注意 ,你的@Path定义和映射并没有改变。这是一种应用广泛的通用开关,用于HTTP API版本。
如果你喜欢在你的资源内映射完整路径,你可以禁用断裂的基本路径:
<resteasy:application-config strip-seam-resource-path="false"/>
例如,现在资源的路径利用@Path("/seam/resource/rest/customer")来映射。然而,我们反对禁用这个功能,因为你的资源类映射是捆绑到一个特殊的部署情节。
对任何部署的@javax.ws.rs.Path 资源和任何@javax.ws.rs.ext.Provider类,Seam会扫描你的类路径。你可以禁用扫描,并手动配置这些类:
<resteasy:application-config
scan-providers="false"
scan-resources="false"
use-builtin-providers="true">
<resteasy:resource-class-names>
<value>org.foo.MyCustomerResource</value>
<value>org.foo.MyOrderResource</value>
</resteasy:resource-class-names>
<resteasy:provider-class-names>
<value>org.foo.MyFancyProvider</value>
</resteasy:provider-class-names>
</resteasy:application-config>
use-built-in-providers切换启用(默认)或禁用RESTEasy内建供应商。 我们建设你启用它们,因为它们提供纯文本、JSON和JAXB编组的开箱即用功能。
最后,你可以配置媒体类型和语言的URI扩展名:
<resteasy:application-config>
<resteasy:media-type-mappings>
<key>txt</key><value>text/plain</value>
</resteasy:media-type-mappings>
<resteasy:language-mappings>
<key>deutsch</key><value>de-DE</value>
</resteasy:language-mappings>
</resteasy:application-config>
这个定义将对附加的访问和访问语言头的值 text/plain和de-DE映射URI后缀为.txt和.deutsc。
24.4.2. 资源和供应商作为Seam组件
默认时RESTEasy管理任何资源和供应商实例。那意味着RESTEasy会实例化一个资源类并且服务于一个请求,然后摧毁它。这是默认的JAX-RS生命周期。供应商对整个应用程序实例化一次,实际上是单独的并且被假定是无状态的。
你可以编写资源和供应商作为Seam组件,得益于丰富的Seam管理的生命周期、双向注入拦截和安全等等。简单地让你的资源类成为一个Seam组件:
@Name("customerResource")
@Path("/customer")
public class MyCustomerResource {
@In
CustomerDAO customerDAO;
@GET
@Path("/{customerId}")
@ProduceMime("text/plain")
public String getCustomer(@PathParam("customerId") int id) {
return customerDAO.find(id).getName();
}
}
现在,当一个请求访问服务器时,Seam处理customerResource实例。这是一个事件作用域的Seam JavaBean组件,因此不同于默认的JAX-RS生命周期。然而,你完全得到了Seam注入支持和所有其它Seam组件以及你想用的上下文。目前也支持SESSION、 APPLICATION和STATELESS资源组件。请记住,为正确地处理服务边会话上下文,所有的HTTP 请求必须转换成一个有效会话标识符 (cookie, URI路径参数)。
对话作用域(Conversation-scoped)资源组件和对话映射目前还不支持,但不久会实现的。
供应商类也是Seam组件, 它们必须是应用作用域(APPLICATION-scoped)或无状态的。
资源和供应商可以是EJBs或JavaBeans, 象所有其它Seam组件一样。