JaxWs基于消息编程

标签: jaxws 消息 编程 | 发表时间:2013-12-30 22:20 | 作者:234390216
出处:http://www.iteye.com

JaxWs 基于消息编程

1     两种消息模式

2     三种数据类型

2.1     Source

2.2     SOAPMessage

2.3     DataSource

3     服务端访问底层信息

4     客户端访问底层消息

4.1     Dispatch的三种请求方式

5     统一入口

6     注意

6.1     MESSAGE和PAYLOAD的区别

 

       通过SEI(Service Endpoint Interface)在服务端和客户端进行操作时,我们是直接使用的对应实现类或代理类对象。表面上看我们是使用的对象在服务端和客户端进行通讯,而实际上底层还是通过发送消息和解析消息进行的。有时候我们可能会希望或者需要直接访问这些消息,这个时候我们就可以通过Provider和Dispatch来实现了。Provider是应用在服务端的,而Dispatch是应用在客户端的。

 

1       两种消息模式

       在使用Provider和Dispatch时,我们可以使用两种消息模式,MESSAGE和PAYLOAD。

       当使用MESSAGE模式时我们可以访问整个的消息,包括绑定的任何header和wrapper。而使用PAYLOAD模式时我们仅仅可以访问payload的消息。如当我们的Dispatch在以PAYLOAD模式工作时,它只能访问到返回的SOAPMessage的body部分,而binding层将处理任何绑定的header和wrapper。

 

2       三种数据类型

       Provider和Dispatch在进行信息传递时只能使用三种数据类型:

l  javax.xml.transform.Source

l  javax.xml.soap.SOAPMessage

l  javax.activation.DataSource

 

2.1     Source

       Source是一个接口,它持有一个XML文档对象。每一个Source接口的实现类都提供了一系列的方法来访问和操纵其持有的XML文档的内容。Source接口的实现类有DOMSource、SAXSource和StreamSource等。

 

2.2     SOAPMessage

       SOAPMessage是一个抽象类,使用SOAPMessage的时候需要满足两个条件:

       第一:Provider实现类使用的是SOAP绑定,即SOAPBinding;

       第二:Provider实现类使用的是MESSAGE Mode。

       SOAPMessage持有一个SOAP消息。

2.3     DataSource

       DataSource是一个接口,使用时需要满足以下两个条件:

       第一:Provider实现类使用的是Http绑定,即HttpBinding;

       第二:Provider实现使用的是MESSAGE Mode。

       DataSource是对数据集合的抽象,在适当的时候可以通过InputStream和OutputStream的形式提供对该数据的访问。其实现类有FileDataSource和URLDataSource。

 

3       服务端访问底层信息

       服务端访问底层信息是通过Provider接口进行的。通过实现Provider接口并且把实现类发布为一个WebService,我们就可以在客户端发起请求时访问到其发送过来的底层消息对象,Source、SOAPMessage或者DataSource。Provider接口只定义了一个invoke方法,该方法接收一个消息对象,并返回一个同类型的消息对象,而且消息对象的类型只能是上面介绍的三种类型之一。

       在使用Provider的时候我们需要在其实现类上使用@WebServiceProvider进行标记(使用@WebService标记好像也行),并且Provider<T>指定的消息对象类型必须是上面提到的三种数据类型之一。WebService使用的消息模式默认为PAYLOAD,我们可以在Provider实现类上使用@ServiceMode来指定其它值,如@ServiceMode(Service.Mode.MESSAGE)。

       下面我们来看一个使用Provider的例子:

 
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.Provider;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceProvider;
 
@WebServiceProvider(serviceName = "SOAPMessageService", portName = "SOAPMessagePort", targetNamespace = "http://provider.jaxws.sample.cxftest.tiantian.com/")
@ServiceMode(Mode.MESSAGE)
public class SOAPMessageModeProvider implements Provider<SOAPMessage> {
 
   public SOAPMessage invoke(SOAPMessage request) {
      SOAPMessage response = null;
      try {
         System.out.println("客户端以SOAPMessage通过MESSAGE Mode请求如下: ");
         request.writeTo(System.out);
         response = MessageUtil.getInstance().create(null, "/provider/SOAPMessageResp.xml");
      } catch (Exception ex) {
         ex.printStackTrace();
      }
      return response;
   }
 
}

 

 

       在上面代码中,我们的SOAPMessageModeProvider:

l  实现了Provider接口;

l  通过Provider接口定义的泛型指定使用的消息对象数据类型为SOAPMessage;

l  通过@WebServiceProvider标注其为一个WebService,并指定了serviceName等属性;

l  通过@ServiceMode指定其使用的消息模式为MESSAGE;

l  在invoke方法中接收了一个SOAPMessage,并返回了一个SOAPMessage。

 

       其中MessageUtil类的代码为:

 
import java.io.IOException;
import java.io.InputStream;
 
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
 
public class MessageUtil {
 
   private static MessageUtil instance = new MessageUtil();
  
   private MessageFactory factory;
  
   private MessageUtil() {
      try {
         factory = MessageFactory.newInstance();
      } catch (SOAPException e) {
         e.printStackTrace();
         thrownew RuntimeException(e);
      }
   }
  
   public static MessageUtil getInstance() {
      returninstance;
   }
  
   /**
    * 创建一个默认的SOAPMessage
    * @return
    * @throws SOAPException
    */
   public SOAPMessage create() throws SOAPException {
      returnfactory.createMessage();
   }
  
   /**
    * 根据MimeHeaders和soap格式文件路径创建一个SOAPMessage
    * @param headers
    * @param filePath
    * @return
    * @throws IOException
    * @throws SOAPException
    */
   public SOAPMessage create(MimeHeaders headers, String filePath) throws IOException, SOAPException {
      InputStream is = MessageUtil.class.getResourceAsStream(filePath);
      SOAPMessage message = factory.createMessage(headers, is);
      is.close();
      return message;
   }
  
   /**
    * 获取MessageFactory
    * @return
    */
   public MessageFactory getMessageFactory() {
      returnfactory;
   }
  
}

 

 

       文件SOAPMessageResp.xml的内容为:

<?xml version="1.0" encoding="utf-8" ?>
<SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <SOAP-ENV:Body>
        <ns4:sayHiResponse xmlns:ns4="http://provider.jaxws.sample.cxftest.tiantian.com/">
            <ns4:responseType>SOAPMessage Response</ns4:responseType>
        </ns4:sayHiResponse>       
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

 

 

       有了Provider实现类之后我们就可以把它发布为一个WebService了,如:

   Object service = new SOAPMessageModeProvider();
   Endpoint.publish("http://localhost:8080/test/jaxws/services/SOAPMessage", service);

 

 

4       客户端访问底层消息

       客户端访问底层消息是通过Dispatch接口进行的,跟服务端的Provider接口一样,Dispatch接口中同样定义了一个invoke方法,该方法负责向服务端发送一种数据类型的消息,并返回一个对应类型的消息。不同的是Dispatch接口的实现类可以不需要我们自己定义和实现。我们可以通过创建代表服务端对应WebService对象的Service对象来创建一个Dispatch对象。Service类中定义了一系列的createDispatch重载方法,但比较常用的还是如下方法:

    public <T> Dispatch<T> createDispatch(QName portName, Class<T> type, Mode mode)

 

       该方法接收三个参数:

l  第一个参数QName类型的portName代表目标Service中对应的portName;

l  第二个参数表示底层发送和接收消息时使用的数据类型,根据配置的不同可以是前面提到的三种数据类型中的一种;

l  第三个参数表示使用的消息模式。

 

       下面我们来看一个创建Dispatch,并使用它来与服务端进行交互的例子:

   public static void main(String args[]) throws Exception {
      //定义serviceName对应的QName,第一个参数是对应的namespace
      QName serviceName = new QName("http://provider.jaxws.sample.cxftest.tiantian.com/", "SOAPMessageService");
      //定义portName对应的QName
     QName portName = new QName("http://provider.jaxws.sample.cxftest.tiantian.com/", "SOAPMessagePort");
      //使用serviceName创建一个Service对象,该对象还不能直接跟WebService对象进行交互
      Service service = Service.create(serviceName);
      //创建一个port,并指定WebService的地址,指定地址后我们就可以创建Dispatch了。
      service.addPort(portName, SOAPBinding.SOAP11HTTP_BINDING, "http://localhost:8080/test/jaxws/services/SOAPMessage");
      //创建一个Dispatch对象
      Dispatch<SOAPMessage> dispatch = service.createDispatch(portName, SOAPMessage.class, Mode.MESSAGE);
      //创建一个SOAPMessage
      SOAPMessage request = MessageUtil.getInstance().create(null, "/dispatch/SOAPMessageReq.xml");
      //调用Dispatch的invoke方法,发送一个SOAPMessage请求,并返回一个SOAPMessage响应。
      SOAPMessage response = dispatch.invoke(request);
      System.out.println("服务端返回如下: ");
      response.writeTo(System.out);
   }

 

 

       在上面的代码中,我们先通过serviceName创建了一个Service对象,然后再通过addPort方法指定其对应的WebService地址。其实,我们也可以像下面这样,通过WebService对应的wsdl文件和serviceName创建对应的Service对象。

   //指定wsdl文件的位置
   URL wsdl = new URL("http://localhost:8080/test/jaxws/services/SOAPMessage?wsdl");
   Service service = Service.create(wsdl, serviceName);

 

 

       上述例子中对应的SOAPMessageReq.xml文件的内容如下:

<?xml version="1.0" encoding="utf-8" ?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
   xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <SOAP-ENV:Body>
      <ns4:sayHi xmlns:ns4="http://provider.jaxws.sample.cxftest.tiantian.com/">
         <ns4:requestType>SOAPMessage Request</ns4:requestType>
      </ns4:sayHi>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

 

 

4.1          Dispatch的三种请求方式

同步请求

       上述Dispatch的invoke方法请求是同步的,也就是阻塞式的,程序在调用了invoke方法之后会一直等待服务端的返回。

 

异步请求

       异步请求是通过Dispatch的invokeAsync方法进行的。Dispatch的异步请求有两种方式,一种是通过定期轮询Dispatch调用invokeAsync方法返回的Response对象是否已经可以返回,另一种是通过回调函数的形式。所以,针对于这两种方式,Dispatch的invokeAsync有两个重载方法:

    public Response<T> invokeAsync(T msg);
    public Future<?> invokeAsync(T msg, AsyncHandler<T> handler);

 

 

       使用定期轮询的方式时,我们在执行完invokeAsync之后会返回一个Response对象,该对象会定期轮询判断invokeAsync方法是否已经完成。当invokeAsync方法调用完成之后,Response对象的isDone()方法会返回true,但是这种调用的完成并不一定是成功的完成,有可能是出异常了,或者其他什么问题。在调用完成,也就是isDone()方法的结果为true之后,我们就可以通过Response对象的get()方法尝试获取对应的返回对象了,之所以说是尝试获取,是因为我们的invokeAsync方法不一定是正常的完成了,如果没有正常完成,调用get()方法将抛出异常。上面Dispatch调用的例子如果我们把它改为定期轮询的异步请求的话,其调用过程的代码可以是这样子:

      SOAPMessage request = MessageUtil.getInstance().create(null, "/dispatch/SOAPMessageReq.xml");
      Response<SOAPMessage> response = dispatch.invokeAsync(request);
      System.out.println("开始判断调用是否已完成");
      while (!response.isDone()) {
         Thread.sleep(200l);
      }
      SOAPMessage responseMsg = null;
      try {
         responseMsg = response.get();
      } catch (Exception e) {
         System.out.println("调用失败");
      }
      if (responseMsg != null) {
         System.out.println("服务端返回如下: ");
         responseMsg.writeTo(System.out);
      }

 

 

       使用回调函数的方式时,我们需要给invokeAsync方法传递一个AsyncHandler接口的实现类作为回调对象。AsyncHandler接口中定义了一个handleResponse方法可以处理服务端返回的结果。当请求完成以后,Dispatch后端的线程会调用AsyncHandler对象的handleResponse方法。前面Dispatch调用的例子如果我们把它改为使用回调函数异步调用的话,其核心代码可以是如下这个样子:

      SOAPMessage request = MessageUtil.getInstance().create(null, "/dispatch/SOAPMessageReq.xml");
     Future<?> future = dispatch.invokeAsync(request, new AsyncHandler<SOAPMessage>() {
 
         @Override
         public void handleResponse(Response<SOAPMessage> res) {
            try {
                System.out.println("回调函数被调用了……");
                SOAPMessage responseMsg = res.get();
                responseMsg.writeTo(System.out);
            } catch (Exception e) {
                e.printStackTrace();
            }
         }
        
      });
      System.out.println("可以开始做其他事情了……");
      while (!future.isDone()) {
         System.out.println("在请求完成之前,整个程序不能结束,否则回调函数不会被调用");
         Thread.sleep(200l);
      }

 

       注意,在使用回调函数方式使用Dispatch的异步请求时,请求结果未返回前整个程序不能停止。如果在请求结果返回以前,整个程序结束了,回调函数不会被调用。当然这种情况只会出现在如上单次执行的测试环境下,我们经常使用的Web环境是不会出现此问题的。由此看来,回调函数应该是被Dispatch内部的守护线程调用的。

 

 

一次请求

       一次请求是通过invokeOneWay方法来进行的。它表示我们的客户端只需要发送请求,而不需要等待服务端的返回。

   SOAPMessage request = MessageUtil.getInstance().create(null, "/dispatch/SOAPMessageReq.xml");
   dispatch.invokeOneWay(request);

 

 

5       统一入口

       JaxWs基于消息编程的一个好处是我们可以在服务端使用一个Provider来接收和处理所有的WebService请求,使用一个Dispatch或多个Dispatch来发送请求,从而达到对WebService的统一管理;另一个好处是客户端可以不定义或者说是不需要使用SEI接口及其相关的类。下面我们来看一个客户端和服务端之间直接通过消息编程的简单示例。

       在服务端定义一个Provider<DOMSource>的实现类UniteServiceProvider。

@WebServiceProvider(serviceName="UniteService", portName="UniteServicePort", targetNamespace="http://provider.jaxws.sample.cxftest.tiantian.com/")
@ServiceMode(Service.Mode.MESSAGE)
@BindingType(HTTPBinding.HTTP_BINDING)
public class UniteServiceProvider implements Provider<DOMSource> {
 
   @Override
   public DOMSource invoke(DOMSource request) {
      DOMSource response = null;
      MessageUtil.getInstance().printSource(request);
      Document requestDoc = (Document)request.getNode();
      Element commandEle = (Element)requestDoc.getElementsByTagName("command").item(0);
      Element paramEle = (Element)requestDoc.getElementsByTagName("param").item(0);
      String command = commandEle.getTextContent();
      try {
         response = this.getResponse(command, paramEle);
      } catch (Exception e) {
         e.printStackTrace();
      }
      MessageUtil.getInstance().printSource(response);
      return response;
   }
 
   /**
    * 根据指令和对应的参数进行相关操作并返回对应的操作结果
    * @param command
    * @param paramEle
    * @return
    * @throws Exception
    */
   private DOMSource getResponse(String command, Element paramEle) throws Exception {
      String responseContent = "<response><product><id>1</id><name>Apple</name></product></response>";
      InputStream is = new ByteArrayInputStream(responseContent.getBytes("UTF-8"));
      Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
      DOMSource response = new DOMSource(doc);
      return response;
   }
  
}

 

 

   在上述代码中我们的服务端接收到一个DOMSource的request请求之后,通过DOMSource的getNode方法取到存放在其中的Document对象。之后我们对该Document的内容作了一个解析,然后把对应的返回结果封装成一个DOMSource进行返回。

 

       上述使用到的MessageUtil类里面的printSource方法的代码为:

   /**
    * 输出Source的内容
    * @param source
    */
   public void printSource(Source source) {
      StreamResult result = new StreamResult(System.out);
      try {
         TransformerFactory.newInstance().newTransformer().transform(source, result);
         System.out.println();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
  

 

 

       发布上述Provider的过程这里就不再赘述了。接着来看一下客户端调用的代码:

public class UniteServiceClient {
 
   public static void main(String args[]) throws Exception {
      String requestContent = "<request><command>10001</command><parameter><id>1</id></parameter></request>";
      InputStream is = new ByteArrayInputStream(requestContent.getBytes("UTF-8"));
      Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
      DOMSource requestMsg = new DOMSource(doc);
      MessageUtil.getInstance().printSource(requestMsg);
      QName serviceName = new QName("http://provider.jaxws.sample.cxftest.tiantian.com/", "UniteService");
     QName portName = new QName("http://provider.jaxws.sample.cxftest.tiantian.com/", "UniteServicePort");
      Service service = Service.create(serviceName);
      //指定绑定方式为HTTPBinding
      service.addPort(portName, HTTPBinding.HTTP_BINDING, "http://localhost:8080/test/jaxws/services/UniteService");
     Dispatch<DOMSource> dispatch = service.createDispatch(portName, DOMSource.class, Mode.MESSAGE);
      DOMSource responseMsg = dispatch.invoke(requestMsg);
      System.out.println("服务端返回来的信息是:");
      MessageUtil.getInstance().printSource(responseMsg);
   }
  
}

 

 

6       注意

6.1     MESSAGE和PAYLOAD的区别

   在文章的开始部分介绍了两种消息模式,以及它们之间的区别。MESSAGE模式是访问的整个消息,而PAYLOAD模式访问的只是消息的部分内容。我们也知道,当我们使用DOMSource作为消息对象时我们可以使用MESSAGE和PAYLOAD这两种模式。所以接下来我们就来说说使用DOMSource作为消息对象时使用MESSAGE模式和PAYLOAD模式的区别。

   为了具有可比性,我们指定使用的BindingType为SOAPBinding,(未指定BindingType时默认也为SOAPBinding),下面来看看使用MESSAGE模式和PAYLOAD模式的区别。

 

MESSAGE 模式

 

   使用MESSAGE模式,我们在发送DOMSource消息对象时,如果我们的DOMSource消息对象里面持有的Document不是一个SOAPPart(SOAPPart是一个实现了Document接口的抽象类),那么系统会先生成一个SOAPPart,然后把我们的DOMSource里面持有的Document作为SOAPPart关联的SOAPEnvelope对象的SOAPBody部分。然后再把该SOAPPart作为DOMSource持有的Document对象。这个时候如果我们只想获取到最原始的document,也就是SOAPBody包裹的那一段文档,我们得这样来取:

   public DOMSource invoke(DOMSource request) {
      SOAPPart soapPart = (SOAPPart) request.getNode();
      try {
         SOAPEnvelope soapEnvelop= soapPart.getEnvelope();
         SOAPBody soapBody = soapEnvelop.getBody();
         Document preDoc = soapBody.extractContentAsDocument();
      } catch (SOAPException e1) {
         e1.printStackTrace();
      }
      returnnull;
   }

 

       如果DOMSource本身持有的Document对象就是一个SOAPPart的话就可以直接发送了,不需要再做转换了。当我们的DOMSource持有的不是一个SOAPPart时,系统在生成SOAPPart时很可能会抛出异常信息:HIERARCHY_REQUEST_ERR: 尝试在不允许的位置插入节点。所以当我们配合使用SOAPBinding、DOMSource消息对象和MESSAGE模式时,我们最好给DOMSource传入一个SOAPPart对象或者是SOAPPart格式的Document对象。

 

PAYLOAD 模式

   使用PAYLOAD模式时,我们发送的DOMSource消息会直接发送过去。对方接收到的内容和发送时的内容是一样的,注意只是内容是一样的,其持有的Document对象还是会当做一个普通的Document对象处理,如DocumentImpl。比如发送的时候DOMSource持有的是一个SOAPPart,那么接收的时候接收到的DOMSource里面的Document的内容还是发送时SOAPPart的内容,但是对象却是一个普通的Document对象,而不是发送时的SOAPPart对象;而如果发送的时候发送的是一个普通的Document对象,那么接收到的内容也只是一个普通Document的内容,不会像MESSAGE模式那样会有多余的SOAPHeader等信息。

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [jaxws 消息 编程] 推荐:

JaxWs基于消息编程

- - 行业应用 - ITeye博客
1     两种消息模式. 2     三种数据类型. 3     服务端访问底层信息. 4     客户端访问底层消息. 4.1     Dispatch的三种请求方式. 6.1     MESSAGE和PAYLOAD的区别.        通过SEI(Service Endpoint Interface)在服务端和客户端进行操作时,我们是直接使用的对应实现类或代理类对象.

CXF中jaxb-api.jar、jaxws-api.jar与jdk1.6不兼容问题

- - ITeye博客
服务器是tomcat6,在cxf-2.3.10中的:jaxb-api-2.2.3.jar jaxb-impl-2.2.4-1.jar 在jdk1.6中不兼容的问题,报错如下:. 原因是:需要jaxb 2.1,却加载了2.0,因为jdk1.6自带jaxb 2.0造成的,解决的办法是在D:\tomcat6下加入endorsed文件夹,.

消息两则

- 藏书人 - 李志官方博客
1,经过深思熟虑,我放弃了十月份做个人小巡演的计划,全心全意投入跨年音乐会的准备工作. 如不出意外,12月31日南京见. 2,如果不出意外,第六张专辑会在十一之前发布. 经过深思熟虑,我决定不做实体,直接放到官网提供下载,能者多劳,愿者给钱. 3,当然对我而言,意外是常态.

周一消息树

- 水御龙神 - 1416 教室
每一个光鲜的封面,都饱含美术编辑的”血泪“和杂志主编的“阴谋”——今天的消息树让我们将掀开封面往里瞅瞅. 最新一期的美国新闻周刊封面,实在让人有点儿难以置信. 优雅的戴安娜王妃突然出现在二十一世纪的街头,旁边是她的儿媳妇Kate,但仔细看,她却不是当年的王妃,变老了,变丑了——这是新闻周刊编辑们想象中的一个五十岁的女人的样子.

对象的消息模型

- loudly - 酷壳 - CoolShell.cn
[ ———— 感谢 Todd 同学 投递本文,原文链接 ———— ]. 话题从下面这段C++程序说起,你认为它可以顺利执行吗. 试试的确可以顺利运行输出hello world,奇怪吗. 其实并不奇怪,根据C++对象模型,类的非虚方法并不会存在于对象内存布局中,实际上编译器是把Hello方法转化成了类似这样的全局函数:.

Nexus Prime 消息总汇

- tossking - 爱范儿 · Beats of Bits
Google 的每一个亲儿子都倍受瞩目,比如 Nexus One 登场时被冠以“Super Phone”的称号,而 Nexus S 的曝光是由施密特亲手操办的. 那么在今年会有新的 Nexus 手机发布吗. 现在的传言是:9 月或 10 月发布一款名为  Nexus Prime 的超强手机. 关于 Nexus Prime 手机的传言来源较广,但至少有三条可信度较高的消息:.

apache kafka消息服务

- - CSDN博客架构设计推荐文章
apache kafka中国社区QQ群:162272557. apache kafka参考. 消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息. 消息被消费以后,queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息. Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费.

【Android】消息机制原理

- - CSDN博客推荐文章
Android 消息机制涉及到的类主要有. 下面结合 Android API 22 的源码分析上面几个类的内部实现细节,以窥探其中的原理一二. Looper 是一个循环处理消息的类,Looper内部维护一个消息队列,循环的从消息队列中取出消息并处理,如果队列为空则等待新消息. Looper 必须关联到某个线程中,这样其才能获得操作系统的调度而执行消息循环处理,默认情况下,通过 Thread 类创建的线程是没有 Looper 的,如果需要该线程拥有消息循环的功能,需要像下面这样在 Runnable 接口实现方法 run() 中创建Looper.