站内搜索: 请输入搜索关键词
当前页面: 在线文档首页 > JBoss Application Server 4.0.1 release2 Guide 英文版指南文档

Chapter 12. Web Services - JBoss Application Server 4.0.1 release2 Guide 英文版指南文档

Chapter 12. Web Services

The biggest new feature of J2EE 1.4 is the ability of J2EE components to act both as web service providers and consumers. J2EE applications can expose a web service from the EJB tier using a stateless session bean or from the web tier using a plain Java object. Additionally, both server-side components and J2EE client applications have a standard way of declaring references to external web services.

12.1. JAX-RPC Service Endpoints

JAX-RPC service endpoints (JSEs) provide web services from the web tier. They take the form of a simple Java objects that masquerade as servlets. To show how simple they are, we'll jump right in with a trivial hello web service implementation class.

package org.jboss.chap12.hello;

public class HelloServlet
{
    public String hello(String name)
    {
        return "Hello " + name + "!";
    }
}

There is nothing remarkable about HelloServlet. It doesn't implement any special interfaces nor does it need any methods besides the business methods it decides to provide. The hello method is the operation we will expose as a web service, and it does nothing but respond with a friendly greeting to the person passed in.

That is our web service implementation. In addition to this, we need a service endpoint class that defines the interface of the web service. That is shown here as the Hello interface.

package org.jboss.chap12.hello;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello
    extends Remote
{
    public String hello(String name)
        throws RemoteException;
}

The service endpoint interface is declared Remote and the methods must throw RemoteException. Beyond this, it is a simple expression of the interface to our web service. This is all the code we need to write to expose a J2EE web service. Deploying it, however, does require a few additional deployment descriptors.

Although a JSE doesn't bears any direct resemblance to a servlet, it is nonetheless deployed as a servlet in the web.xml file. We'll need to declare the web service implementation class as a servlet and provide a servlet mapping that will respond to the web service invocations. Here is the definition required to deploy the hello web service.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">


    <servlet>
        <servlet-name>HelloWorldServlet</servlet-name>
        <servlet-class>org.jboss.chap12.hello.HelloServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>HelloWorldServlet</servlet-name>
        <url-pattern>/Hello</url-pattern>
    </servlet-mapping>
    
</web-app>

The URL pattern in the servlet mapping is the only externally visible configuration element. It controls what URL the web service lives at. This will be primarily noticed as the location of the WSDL file for this service.

The web.xml file doesn't contain any web service related configuration. A new deployment descriptor, webservices.xml, is needed to instruct JBoss to treat this servlet as a web service and not as a normal servlet. But before we see that, we'll need two additional configuration files, a WSDL file and a JAX-RPC mapping file. Both of these files can be generated using the wscompile tool that ships as part of the Java Web Services Developer Pack (WSDP).

wscompile -classpath <classpath> -gen:server -f:rpcliteral -mapping mapping.xml config.xml

This generates a WSDL file and a JAX-RPC mapping file based on the supplied config.xml and the corresponding classes found on the classpath. The config.xml file for the hello web service is shown below.

<?xml version="1.0" encoding="UTF-8"?>

<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
    <service name="HelloService"
             targetNamespace="http://hello.chap12.jboss.org/"
             typeNamespace="http://hello.chap12.jboss.org/types"
             packageName="org.jboss.chap12.hello">
        <interface name="org.jboss.chap12.hello.Hello"/>
    </service>
</configuration> 

The service element defines the interface our web service provides. The following attributes are required:

  • name: This is the name of the web service.

  • targetNamespace: Web services require namespaces just like Java classes do. It's a common practice to use a URL namespace that corresponds to the Java namespace given.

  • typeNamespace: This specifies the namespace to use for custom types.

  • packageName: This is the base package name that your web services classes live under.

Additionally, we need an interface element that tells wscompile what the Java interface for the webservice is. This interface declares the operations that the web service provides.

The WSDL file that wscompile generated for our config.xml file is shown below. Note that the SOAP address isn't provided in the WSDL file. JBoss will insert the correct URL for the WSDL when it deploys the web service.

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="HelloService"
    targetNamespace="http://hello.chap12.jboss.org/"
    xmlns:tns="http://hello.chap12.jboss.org/"
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
    <types/>
    <message name="Hello_hello">
        <part name="String_1" type="xsd:string"/>
    </message>
    <message name="Hello_helloResponse">
        <part name="result" type="xsd:string"/>
    </message>
    <portType name="Hello">
        <operation name="hello" parameterOrder="String_1">
            <input message="tns:Hello_hello"/>
            <output message="tns:Hello_helloResponse"/>
        </operation>
    </portType>
    <binding name="HelloBinding" type="tns:Hello">
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"/>
        <operation name="hello">
            <soap:operation soapAction=""/>
            <input>
                <soap:body use="literal" namespace="http://hello.chap12.jboss.org/"/>
            </input>
            <output>
                <soap:body use="literal" namespace="http://hello.chap12.jboss.org/"/>
            </output>
        </operation>
    </binding>
    <service name="HelloService">
        <port name="HelloPort" binding="tns:HelloBinding">
            <soap:address location="REPLACE_WITH_ACTUAL_URL"/>
        </port>
    </service>
</definitions>

We also asked wscompile to generate a JAX-RPC mapping file. This is shown below.

<?xml version="1.0" encoding="UTF-8"?>
<java-wsdl-mapping version="1.1" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_jaxrpc_mapping_1_1.xsd">
    <package-mapping>
        <package-type>org.jboss.chap12.hello</package-type>
        <namespaceURI>http://hello.chap12.jboss.org/types</namespaceURI>
    </package-mapping>
    <package-mapping>
        <package-type>org.jboss.chap12.hello</package-type>
        <namespaceURI>http://hello.chap12.jboss.org/</namespaceURI>
    </package-mapping>
    <service-interface-mapping>
        <service-interface>org.jboss.chap12.hello.HelloService</service-interface>
        <wsdl-service-name xmlns:serviceNS="http://hello.chap12.jboss.org/">serviceNS:HelloService</wsdl-service-name>
        <port-mapping>
            <port-name>HelloPort</port-name>
            <java-port-name>HelloPort</java-port-name>
        </port-mapping>
    </service-interface-mapping>
    <service-endpoint-interface-mapping>
        <service-endpoint-interface>org.jboss.chap12.hello.Hello</service-endpoint-interface>
        <wsdl-port-type xmlns:portTypeNS="http://hello.chap12.jboss.org/">portTypeNS:Hello</wsdl-port-type>
        <wsdl-binding xmlns:bindingNS="http://hello.chap12.jboss.org/">bindingNS:HelloBinding</wsdl-binding>
        <service-endpoint-method-mapping>
            <java-method-name>hello</java-method-name>
            <wsdl-operation>hello</wsdl-operation>
            <method-param-parts-mapping>
                <param-position>0</param-position>
                <param-type>java.lang.String</param-type>
                <wsdl-message-mapping>
                    <wsdl-message xmlns:wsdlMsgNS="http://hello.chap12.jboss.org/">wsdlMsgNS:Hello_hello</wsdl-message>
                    <wsdl-message-part-name>String_1</wsdl-message-part-name>
                    <parameter-mode>IN</parameter-mode>
                </wsdl-message-mapping>
            </method-param-parts-mapping>
            <wsdl-return-value-mapping>
                <method-return-value>java.lang.String</method-return-value>
                <wsdl-message xmlns:wsdlMsgNS="http://hello.chap12.jboss.org/">wsdlMsgNS:Hello_helloResponse</wsdl-message>
                <wsdl-message-part-name>result</wsdl-message-part-name>
            </wsdl-return-value-mapping>
        </service-endpoint-method-mapping>
    </service-endpoint-interface-mapping>
        </java-wsdl-mapping> 

Once the extra files are generated, we need to bundle them up in a webservices.xml file. This file links to our WSDL file with the wsdl-file element and to the mapping file using the jaxrpc-mapping-file element.

In addition to this, a port-component element is needed that maps a port in the WSDL file to a particular service implementation. For our JSE, this is done with a servlet-link inside the service-impl-bean element. The servlet link must be the same as the name of the pseudo-servlet we declared in the web.xml file.

<webservices xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_web_services_1_1.xsd" version="1.1">
    <webservice-description>
        <webservice-description-name>HelloService</webservice-description-name>
        <wsdl-file>WEB-INF/wsdl/HelloService.wsdl</wsdl-file>
        <jaxrpc-mapping-file>WEB-INF/mapping.xml</jaxrpc-mapping-file>
        <port-component>
            <port-component-name>Hello</port-component-name>
            <wsdl-port>HelloPort</wsdl-port>
            <service-endpoint-interface>org.jboss.chap12.hello.Hello</service-endpoint-interface>
            <service-impl-bean>
                <servlet-link>HelloWorldServlet</servlet-link>
            </service-impl-bean>
        </port-component>
    </webservice-description>
</webservices>

With these completed we can deploy the WAR file containing our web service. All the deployment descriptors go in the WEB-INF directory, as shown in Figure 12.1, “The structure of hello-servlet.war”. It's important to note that the WSDL file is required to be in the wsdl subdirectory.

The structure of hello-servlet.war

Figure 12.1. The structure of hello-servlet.war

To deploy and test the hello web service, run the following from the examples directory:

[examples]$ ant -Dchap=chap12 -Dex=1 run-example
...
run-example1:
     [echo] Waiting for 5 seconds for deploy...
     [java] Contacting webservice at http://localhost:8080/hello-servlet/Hello?wsdl
     [java] hello.hello(JBoss user)
     [java] output:Hello JBoss user!

Note the URL the JBoss publishes the WSDL file at. Our web application name is hello-servlet and we mapped the servlet to /Hello in the web.xml file so the web service is mappend to /hello-servlet/Hello. The ?wsdl query returns the WSDL file.

If you aren't sure what the URL of the WSDL file will be, JBoss provides a way to list the web services available on the system at /ws4ee/services. Figure 12.2, “The web services list” shows a view of the services list.

The web services list

Figure 12.2. The web services list

The services list shows all of the deployed web services along with the name of the deployment unit and a link to the WSDL file for that service.

12.2. EJB Endpoints

Web services can also be provided from the EJB tier. Any stateless session bean can serve as the endpoint for a web service in almost the same way as the JAX-RPC endpoints. To see how this works, we will adapt the HelloServlet example into a session bean. Here is the code:

package org.jboss.chap12.hello;

import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public class HelloBean
    implements SessionBean
{
    public String hello(String name)
    {
        return "Hello " + name + "!";
    }

    public void ejbCreate() {};
    public void ejbRemove() {};

    public void ejbActivate() {}
    public void ejbPassivate() {}

    public void setSessionContext(SessionContext ctx) {}
}

This is a very trivial session bean. Session beans normally require a home interface and either a local or remote interface. However, it is possible to omit them if the session bean is only serving as a web services endpoint. However, we do still need the Hello service endpoint interface that we used in the JSE example.

The ejb-jar.xml file is very standard for a session bean. The normal session bean parameters are explained in Chapter 5, EJBs on JBoss. The only new element is the service-endpoint element, which declares the service endpoint interface for the web service.

<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" version="2.1"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd">
    <display-name>chapter 12 EJB JAR</display-name>
    <enterprise-beans>
        <session>
            <ejb-name>HelloBean</ejb-name>
            <service-endpoint>org.jboss.chap12.hello.Hello</service-endpoint>
            <ejb-class>org.jboss.chap12.hello.HelloBean</ejb-class>
            <session-type>Stateless</session-type>
            <transaction-type>Container</transaction-type>
        </session>
    </enterprise-beans>
    <assembly-descriptor>
        <method-permission>
            <unchecked/>
            <method>
                <ejb-name>HelloBean</ejb-name>
                <method-name>*</method-name>
            </method>
        </method-permission>
        <container-transaction>
            <method>
                <ejb-name>HelloBean</ejb-name>
                <method-name>*</method-name>
            </method>
            <trans-attribute>Required</trans-attribute>
        </container-transaction>
    </assembly-descriptor>
</ejb-jar>

Accompanying this there needs to be a supporting webservices.xml. The file, shown below, looks almost identical to the webservices.xml for the WAR file.

<webservices xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_web_services_1_1\.xsd" version="1.1">
    <webservice-description>
        <webservice-description-name>HelloService</webservice-description-name>

        <wsdl-file>META-INF/wsdl/HelloService.wsdl</wsdl-file>
        <jaxrpc-mapping-file>META-INF/mapping.xml</jaxrpc-mapping-file>

        <port-component>
            <port-component-name>Hello</port-component-name>
            <wsdl-port>HelloPort</wsdl-port>
            <service-endpoint-interface>org.jboss.chap12.hello.Hello</service-endpoint-interface>
            <service-impl-bean>
                <ejb-link>HelloBean</ejb-link>
            </service-impl-bean>
        </port-component>
    </webservice-description>
</webservices>

The first difference is that the WSDL file should be in the META-INF/wsdl directory instead of the WEB-INF/wsdl directory. The second difference is that the service-impl-bean element contains an ejb-link that refers to the ejb-name of the session bean. The WSDL file and JAX-RPC mapping files remain unchanged from the previous example.

To package and deploy the application, run the following command in the examples directory:

[examples]$ ant -Dchap=chap12 -Dex=2 run-example
...
run-example2:
     [copy] Copying 1 file to /tmp/jboss-4.0.1/server/default/deploy
     [echo] Waiting for 5 seconds for deploy...
     [java] Contacting webservice at http://localhost:8080/hello-ejb/Hello?wsdl
     [java] hello.hello(JBoss user)
     [java] output:Hello JBoss user!

The test program run here is the same as with the servlet example, except that we use a different URL for the WSDL. JBoss composes the WSDL using the base name of the EJB JAR file and the name of the service interface. However, as with all web services in JBoss, you can use the http://localhost:8080/ws4ee/services service view shown in Figure 12.2, “The web services list” to verify the deployed URL of the WSDL.

12.3. Web Services Clients

We will now turn our attention from providing web services to consuming them.

12.3.1. A JAX-RPC client

The full JAX-RPC programming model is available to J2EE applications and clients. We won't cover the full range of client programming techniques, but we swill look briefly at the client we've used so far to test the web services we've deployed. The client, shown in the following listing, illustrates the dynamic proxy invocation mechanism.

package org.jboss.chap12.client;

import org.jboss.chap12.hello.Hello;

import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;

import javax.xml.namespace.QName;

import java.net.URL;

public class HelloClient
{
    public static void main(String[] args)
        throws Exception
    {
        String urlstr   = args[0];
        String argument = args[1];

        System.out.println("Contacting webservice at " + urlstr);

        URL url =  new URL(urlstr);

        QName qname = new QName("http://hello.chap12.jboss.org/",
                                "HelloService");

        ServiceFactory factory = ServiceFactory.newInstance();
        Service        service = factory.createService(url, qname);

        Hello          hello   = (Hello) service.getPort(Hello.class);

        System.out.println("hello.hello(" + argument + ")");
        System.out.println("output:" + hello.hello(argument));
    }
}

This JAX-RPC client uses the Hello service endpoint interface and creates a dynamic proxy to speak to the service advertised by the WSDL at the URL that is passed in as a command line argument. For illustrative purposes, we'll show another variation of web services invocation that doesn't use the service endpoint interface. This is known as the Dynamic Invocation Interface (DII). Using DII, it is possible to refer to a specific port and operation by name. Think of it as reflection for web services. The client code is shown in the following listing.

package org.jboss.chap12.client;

import org.jboss.chap12.hello.Hello;

import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import javax.xml.rpc.Call;

import javax.xml.namespace.QName;

import java.net.URL;

public class HelloClientDII
{
    public static void main(String[] args)
        throws Exception
    {
        String urlstr   = args[0];
        String argument = args[1];

        System.out.println("Contacting webservice at " + urlstr);

        URL url =  new URL(urlstr);

        String ns        = "http://hello.chap12.jboss.org/";
        QName  qname     = new QName(ns, "HelloService");
        QName  port      = new QName(ns, "HelloPort");
        QName  operation = new QName(ns, "hello");

        ServiceFactory factory = ServiceFactory.newInstance();
        Service        service = factory.createService(url, qname);
        Call           call    = service.createCall(port, operation);

        System.out.println("hello.hello(" + argument + ")");
        System.out.println("output:" + call.invoke(new Object[] {argument}));
    }
}

The following two commands can be used to run DII client against both the JSE and EJB web services we have created.

[examples]$ ant -Dchap=chap12 -Dex=1b run-example
[examples]$ ant -Dchap=chap12 -Dex=2b run-example

12.3.2. Service references

The JAX-RPC examples in Section 12.3.1, “A JAX-RPC client” all required manual configuration of the WSDL URL and knowledge of the XML nature of the web services in question. This can be a configuration nightmare, but if your code is a J2EE component there is another option. J2EE components can declare service references and look up JAX-RPC Service objects in JNDI without needing to hardcode any web service references in the code.

To show how this works, let's first look at a session bean that needs to make a call to the hello web service:

package org.jboss.chap12.example;

import javax.ejb.*;
import javax.naming.*;
import java.rmi.RemoteException;           
                
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceException;

import org.jboss.chap12.hello.Hello;

public class ExampleBean
    implements SessionBean
{
    public String doWork()
    {
        try {
            Context ctx     = new InitialContext();

            Service service = (Service) ctx.lookup("java:comp/env/services/hello");
            Hello   hello   = (Hello)   service.getPort(Hello.class);
            
            return hello.hello("example bean");
        } catch (NamingException e) {
            throw new EJBException(e);
            
        } catch (ServiceException e) {
            throw new EJBException(e);
            
        } catch (RemoteException e) {
            throw new EJBException(e);
        }
    }

    public void ejbCreate() {};
    public void ejbRemove() {};

    public void ejbActivate() {}
    public void ejbPassivate() {}

    public void setSessionContext(SessionContext ctx) {}
}

ExampleBean invokes the hello web service in its doWork method. We've used the dynamic proxy invocation method here, but any of the JAX-RPC supported invocation methods would be fine. The interesting point here is that the bean has obtained the Service reference from a JNDI lookup in its ENC.

Web service references are declared using a service-ref element in inside an ejb-jar.xml file.

The service-ref content model

Figure 12.3. The service-ref content model

The following elements are supported by the service-ref:

  • service-ref-name: This is the JNDI name that the service object will be bound under in the bean's ENC. It is relative to java:comp/env/.

  • service-interface: This is the name of JAX-RPC service interface the client will use. Normally this is javax.xml.rpc.Service, but it's possible to provide your own service class.

  • wsdl-file: This is the location of the WSDL file. The WSDL file should be under META-INF/wsdl.

  • jaxrpc-mapping-file: This is the location of the JAX-RPC mapping file.

  • service-qname: This element specifies the name of the service in the web services file. It is only mandatory if the WSDL file defines multiple services. The value must by a QName, which means it needs to be a namespace qualified value such as ns:ServiceName where ns is an XML namespace valid at the scope of the service-qname element.

  • port-component-ref: This element provides the mapping between a service endpoint interface and a port in a web service.

  • handler: This allows the specification of handlers, which act like filters or interceptors on the current request.

The following service-ref declares a reference to the hello web service for the Example session bean.

<session>
    <ejb-name>Example</ejb-name>
    <home>org.jboss.chap12.example.ExampleHome</home>
    <remote>org.jboss.chap12.example.Example</remote>
    <ejb-class>org.jboss.chap12.example.ExampleBean</ejb-class>
    <session-type>Stateless</session-type>
    <transaction-type>Container</transaction-type>
    <service-ref>
        <service-ref-name>services/hello</service-ref-name>
        <service-interface>javax.xml.rpc.Service</service-interface>
        <wsdl-file>META-INF/wsdl/hello.wsdl</wsdl-file>
        <jaxrpc-mapping-file>META-INF/mapping.xml</jaxrpc-mapping-file>
        <service-qname xmlns:hello="http://hello.chap12.jboss.org">hello:HelloService</service-qname>
    </service-ref>
</session>

This instructs the EJB deployer to make a Service object available for the bean in JNDI under the name java:comp/env/services/hello that talks to our hello web service. The session bean can then invoke normal web services operations on the service.

Since most of the web services configuration options are completely standard, there's little need to go into great depths here. However, JBoss does provide several additional web services configuration options through the service-ref element in the jboss.xml deployment descriptor. The content model for the service-ref element is shown in Figure 12.4, “The jboss.xml service-ref content model”.

The jboss.xml service-ref content model

Figure 12.4. The jboss.xml service-ref content model

The configurable elements are:

  • service-ref-name: This element should match the service-ref-name in the ejb-jar.xml file that is being configured.

  • port-component-ref: The port-component-ref element provides additional information for a specific port. This includes properties that should be associated with the JAX-RPC stub for the port.

  • wsdl-override: This provides an alternate location for the WSDL file. The value can be any valid URL. This can be used in co-ordination with the wsdl-publish-location to get the final WSDL file for a locally published web service. It could also be the URL of a remotely published WSDL that you don't want duplicated in the deployment file.

  • call-property: This sets properties on the JAX-RPC stub.

Since the WSDL file generated by wscompile doesn't contain the SOAP address of our web service, we'll use the WSDL override feature to dynamically download the correct WSDL file from the server. While this might not be the best technique to use in a production application, it does illustrate the WSDL override functionality very well. The following jboss.xml file links the published URL for the hello-servlet version of the hello web service..

<!DOCTYPE jboss PUBLIC
          "-//JBoss//DTD JBOSS 4.0//EN"
          "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd">
<jboss>
    <enterprise-beans>
        <session>
            <ejb-name>Example</ejb-name>
            <service-ref>
                <service-ref-name>services/hello</service-ref-name>
                <wsdl-override>http://localhost:8080/hello-servlet/Hello?wsdl</wsdl-override>
            </service-ref>
        </session>
    </enterprise-beans>
</jboss>

This example can be run as shown below:

[examples]$ ant -Dchap=chap12 -Dex=2 run-example
...
run-example3:
     [echo] Waiting for 5 seconds for deploy...
     [copy] Copying 1 file to /tmp/jboss-4.0.1/server/default/deploy
     [echo] Waiting for 5 seconds for deploy...
     [java] output:Hello example bean!

The service-ref element is not limited to the ejb-jar.xml file. It's available to any J2EE component. A service reference can be placed in the web.xml file for use by web tier components or in the application-client.xml file for use by J2EE client applications.