WebService交互

1.3 一个简单的WebService运行实例

WebService代码:

package stock.server;

@javax.jws.WebService

public class StockQuoteProvider {

   public StockQuoteProvider () {

   }

   public float getLastTradePrice (String tickerSymbol) {

      return "abc".equals(tickerSymbol)? 1234.0f : 0.0f;

    }

}

客户端代码:

import java.lang.annotation.Annotation;

import stock.server.*;

import javax.xml.ws.Service;

 

public class StockQuoteClient {

      public static void main(String[] args) throwsException {

      StockQuoteProviderService service = newStockQuoteProviderService();

      StockQuoteProvider port = service.getStockQuoteProviderPort();

           System.out.println(port.getLastTradePrice(args[0]));

      }

}

 

 

 

2. Web Service技术研究的环境准备

2.1 安装运行环境

   推荐安装GlassFish服务器,具体安装方法可以参见以下文章:

手把手教你 怎么 安装 GlassFish

http://blog.csdn.net/nanjingjiangbiao/archive/2010/01/28/5264913.aspx

2.2 下载WebService API(JAX-WS)的源代码

   具体的下载URL参见以下地址:

https://jax-ws-sources.dev.java.net/source/browse/jax-ws-sources/

2.3 部署服务器端

1.把1.3中的服务器端部署到GlassFish中

2.用wsimport命令生成stub中间程序

3.把1.3中的客户端代码和stub代码,以及2.2下载的WebService源代码部署到Eclipse工程中,就可以DEBUG了。

具体的可以参照以下文章:

SOA研究之 JAX-WS

http://blog.csdn.net/nanjingjiangbiao/archive/2010/02/10/5305049.aspx

 

3. Web Service的技术内幕

3.1 客户端是如何和WSDL建立关系的?

   1. 首先,我们看到客户端程序中有以下这样一行

   StockQuoteProviderService service = new

 StockQuoteProviderService();

   2. 这行代码,会调用到

public StockQuoteProviderService() {

        super(STOCKQUOTEPROVIDERSERVICE_WSDL_LOCATIONnewQName("http://server.stock/""StockQuoteProviderService"));

    }

3. 这行代码,会调用到com.sun.xml.ws.spi.ProviderImpl的

@Override

    public ServiceDelegate createServiceDelegate( URL wsdlDocumentLocation, QName serviceName, Class serviceClass) {

         return new WSServiceDelegate(wsdlDocumentLocation, serviceName, serviceClass);

    }

4. 这行代码,会调用到com.sun.xml.ws.clientWSServiceDelegate的

/**

     * @param serviceClass

     *      Either {@link Service}.class or other generatedservice-derived classes.

     */

    public WSServiceDelegate(@Nullable Source wsdl, @NotNullQName serviceName, @NotNull final Class<? extends Service> serviceClass) {

       ~省略~

    }

5. 而其中最关键的就是以下这段,解析阅读WSDL的代码,这段代码主要是用XMLStream来构建WSDL代码,不足为奇。

WSDLServiceImpl service=null;

        if (wsdl != null) {

            try {

                URL url = wsdl.getSystemId()==null ? null : newURL(wsdl.getSystemId());

                WSDLModelImpl model = parseWSDL(url, wsdl);

                service = model.getService(this.serviceName);

                if (service == null)

                    throw new WebServiceException(

                       ClientMessages.INVALID_SERVICE_NAME(this.serviceName,

                           buildNameList(model.getServices().keySet())));

                // fill in statically known ports

                for (WSDLPortImpl port : service.getPorts())

                    ports.put(port.getName(), new PortInfo(this, port));

            } catch (MalformedURLException e) {

                throw newWebServiceException(ClientMessages.INVALID_WSDL_URL(wsdl.getSystemId()), e);

            }

        }

        this.wsdlService = service;

3.2 客户端是如何和SEI建立关系的?

1. 首先,我们看到客户端程序中有以下这样一行

StockQuoteProvider port = service.getStockQuoteProviderPort();

2. 这行代码,会调用到com.sun.xml.ws.clientWSServiceDelegate的

private <T> T getPort(WSEndpointReference wsepr, QName portName, Class<T> portInterface,

                          WebServiceFeature... features) {

        SEIPortInfo spi = addSEI(portName, portInterface, features);

        returncreateEndpointIFBaseProxy(wsepr,portName,portInterface,features, spi);

    }

3. 这行代码,会调用到com.sun.xml.ws.clientWSServiceDelegate的

/**

     * Creates a new pipeline for the given port name.

     */

    private Tube createPipeline(PortInfo portInfo, WSBinding binding) {

        //Check all required WSDL extensions are understood

        checkAllWSDLExtensionsUnderstood(portInfo,binding);

        SEIModel seiModel = null;

        if(portInfo instanceof SEIPortInfo) {

            seiModel = ((SEIPortInfo)portInfo).model;

        }

        BindingID bindingId = portInfo.bindingId;

        TubelineAssembler assembler =TubelineAssemblerFactory.create(

                Thread.currentThread().getContextClassLoader(), bindingId);

        if (assembler == null)

            throw new WebServiceException("Unable to process bindingID=" + bindingId);    // TODO: i18n

        return assembler.createClient(

                new ClientTubeAssemblerContext(

                        portInfo.targetEndpoint,

                        portInfo.portModel,

                        this, binding, container,((BindingImpl)binding).createCodec(),seiModel));

    }

在这段代码中,使用了TUBE技术,把客户端和服务器之间建立了关系。

 

4. 这行代码,会调用到com.sun.xml.ws.util.pipe.StandaloneTubeAssembler的

@NotNull

    public Tube createClient(ClientTubeAssemblerContext context) {

        Tube head = context.createTransportTube();

        head = context.createSecurityTube(head);

        if (dump) {

            // for debugging inject a dump pipe. this is left in the production code,

            // as it would be very handy for a trouble-shooting at the production site.

            head = context.createDumpTube("client", System.out, head);

        }

        head = context.createWsaTube(head);

        head = context.createClientMUTube(head);

        head = context.createValidationTube(head);

        return context.createHandlerTube(head);       

    }

 

在以上代码中,开始和SOAP绑定,准备发送请求和调用函数等等。

 

3.3 客户端是如何发送请求的?

1. 首先,我们看到客户端程序中有以下这样一行

port.getLastTradePrice(args[0])

2. 这行代码,会调用到com.sun.xml.ws.client.sei.SEIStub的

public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {

        MethodHandler handler = methodHandlers.get(method);

        if (handler != null) {

            return handler.invoke(proxy, args);

        } else {

            // we handle the other method invocations by ourselves

            try {

                return method.invoke(this, args);

            } catch (IllegalAccessException e) {

                // impossible

                throw new AssertionError(e);

            } catch (IllegalArgumentException e) {

                throw new AssertionError(e);

            } catch (InvocationTargetException e) {

                throw e.getCause();

            }

        }

    }

3. 这行代码,会调用到com.sun.xml.ws.client.stub的(中间省去了2层不重要的代码)

/**

     * Passes a message to a pipe for processing.

     * <p>

     * Unlike {@link Tube} instances,

     * this method is thread-safe and can be invoked from

     * multiple threads concurrently.

     *

     * @param packet         The message to be sent to theserver

     * @param requestContext The {@link RequestContext} whenthis invocation is originally scheduled.

     *                       This must be the same object as{@link #requestContext} for synchronous

     *                       invocations, but for asynchronousinvocations, it needs to be a snapshot

     *                       captured at the point ofinvocation, to correctly satisfy the spec requirement.

     * @param receiver       Receives the {@link ResponseContext}. Since the spec requires

     *                       that the asynchronous invocationsmust not update response context,

     *                       depending on the mode of invocationthey have to go to different places.

     *                       So we take a setter that abstractsthat away.

     */

    protected final Packet process(Packet packet, RequestContext requestContext, ResponseContextReceiver receiver) {

        configureRequestPacket(packet, requestContext);

        Pool<Tube> pool = tubes;

        if (pool == null)

            throw new WebServiceException("close method has already been invoked"); // TODO: i18n

 

        Fiber fiber = engine.createFiber();

        // then send it away!

        Tube tube = pool.take();

 

        try {

            return fiber.runSync(tube, packet);

        } finally {

            // this allows us to capture the packet even when the call failed with an exception.

            // when the call fails with an exception it's no longer a 'reply' but it may provide some information

            // about what went wrong.

 

            // note that Packet can still be updated after

            // ResponseContext is created.

            Packet reply = (fiber.getPacket() == null) ? packet : fiber.getPacket();

            receiver.setResponseContext(newResponseContext(reply));

 

            pool.recycle(tube);

        }

    }

4. 这行代码,会调用到com.sun.xml.ws.api.pipe. __doRun的(中间省去了3层不重要的代码)

这段代码开始发送打成数据包的http请求

/**

     * To be invoked from {@link #doRun(Tube)}.

     *

     * @see #doRun(Tube)

     */

    private Tube __doRun(Tube next) {

        final Fiber old = CURRENT_FIBER.get();

        CURRENT_FIBER.set(this);

 

        // if true, lots of debug messages to show what's being executed

        final boolean traceEnabled =LOGGER.isLoggable(Level.FINER);

 

        try {

            while(!isBlocking() && !needsToReenter) {

                try {

                    NextAction na;

                    Tube last;

                    if(throwable!=null) {

                        if(contsSize==0) {

                            // nothing else to execute. we are done.

                            return null;

                        }

                        last = popCont();

                        if(traceEnabled)

                            LOGGER.finer(getName()+' '+last+".processException("+throwable+')');

                        na = last.processException(throwable);

                    } else {

                        if(next!=null) {

                            if(traceEnabled)

                                LOGGER.finer(getName()+' '+next+".processRequest("+packet+')');

                            na = next.processRequest(packet);

                            last = next;

                        } else {

                            if(contsSize==0) {

                                // nothing else to execute. we are done.

                                return null;

                            }

                            last = popCont();

                            if(traceEnabled)

                                LOGGER.finer(getName()+' '+last+".processResponse("+packet+')');

                            na = last.processResponse(packet);

                        }

                    }

 ~省略~

5. 这行代码,会调用到com.sun.xml.ws.encoding. StreamSOAPCodec的(中间省去了无数层不重要的代码)的

public ContentType encode(Packet packet, OutputStream out) {

        if (packet.getMessage() != null) {

            XMLStreamWriter writer = XMLStreamWriterFactory.create(out);

            try {

                packet.getMessage().writeTo(writer);

                writer.flush();

            } catch (XMLStreamException e) {

                throw new WebServiceException(e);

            }

            XMLStreamWriterFactory.recycle(writer);

        }

        return getContentType(packet.soapAction);

    }

大家可以看到,他发出请求了。

3.4 服务器端是如何接受请求的?

1. 首先,服务器端有一个名叫JAXWSServlet的Servlet常驻服务器,监听请求。所以,请求会首先被转发给com.sun.enterprise.webservice.JAXWSServlet的

    protected void doPost(HttpServletRequest request,

                          HttpServletResponse response)

        throws ServletException ,IOException{

         /**

         * This requirement came from the jbi team. If theWebServiceEndpoint

         * is a jbi endpoint which is private throw an errorwhenever a get

         * or a post request is made

         */

        Endpoint endpt =

 wsEngine_.getEndpoint(request.getServletPath());

~省略~

2.最后,代码会调转到以下com.sun.xml.ws.transport.http.HttpAdapter的

final class HttpToolkit extends Adapter.Toolkit {

        public void handle(WSHTTPConnection con) throws IOException {

            boolean invoke = false;

            try {

                Packet packet = new Packet();

                try {

                    packet = decodePacket(con, codec);

                    invoke = true;

                } catch(ExceptionHasMessage e) {

                    LOGGER.log(Level.SEVERE"JAXWS2015: An ExceptionHasMessage occurred. " + e.getMessage(), e);

                    packet.setMessage(e.getFaultMessage());

                } catch(UnsupportedMediaException e) {

                    LOGGER.log(Level.SEVERE"JAXWS2016: An UnsupportedMediaException occurred. " + e.getMessage(), e);

                    con.setStatus(WSHTTPConnection.UNSUPPORTED_MEDIA);

                } catch(Exception e) {

                    LOGGER.log(Level.SEVERE"JAXWS2017: A ServerRtException occurred. " + e.getMessage(), e);

                   con.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);

                }

                if (invoke) {

                    try {

                        packet = head.process(packet, con.getWebServiceContextDelegate(),

                                packet.transportBackChannel);

                    } catch(Exception e) {

                        LOGGER.log(Level.SEVERE"JAXWS2018: An Exception occurred. " + e.getMessage(), e);

                        if (!con.isClosed()) {

                            writeInternalServerError(con);

                        }

                        return;

                    }

                }

               encodePacket(packet, con, codec);

            } finally {

                if (!con.isClosed()) {

                    con.close();

                }

            }

        }

    }

以上代码分为三块大的处理,分别是

packet = decodePacket(con, codec);

packet = head.process(packet, con.getWebServiceContextDelegate(),

                                packet.transportBackChannel);

encodePacket(packet, con, codec);

用来解析数据包,调用服务,发送回复数据包。这里就不详细介绍了。

4. 总结

希望能够通过这样的文章,更清晰的,直白的把WebService的详细技术内幕公布给大家,为开源软件运动作一份自己的贡献。

 

你可能感兴趣的:(WebService交互)