www.9778.com 34

【www.9778.com】Android中使用Android Ksoap2调用WebService

一、WebService介绍

WebService是基于SOAP协议可实现web服务器与web服务器之间的通信,因采用SOAP协议传送XML数据具有平台无关性,也是成为解决异构平台之间通信的重要解决方案,比如Java平台与.net平台之间。因此在web应用中有着举足轻重的作用,很多机构、组织都在各自平台上对外发布了WebService(例如:天气预报、航班信息、股市行情等等),这样任何平台和客户都可以享受到这些服务,当然有些是要付费的。

Android网络编程

首先我们来谈一下为什么需要学习webService这样的一个技术吧….

二、Android ksoap2组件

对于Android端调用WebService,有两种方式,一种自己编写代码主要通过URL获得 HttpURLConnection的方式建立与webservice的连接,然后进行I/O读写传送和获得数据,并对获得数据进行XML解析,比较麻烦。另一种就是使用第三方组件,比较常用的就是ksoap2-android。

ksoap2-android这个开源组件针对Android平台提供了一个轻量级和高效的SOAP类库,可方便实现Android端与WebService之间的通信

基于TCP协议的网络通信

TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信了。Java使用Socket对象来代表两端的通信接口,并通过Socket产生IO流来进行网络通信。

如果我们的网站需要提供一个天气预报这样一个需求的话,那我们该怎么做?????

1、环境搭建

ksoap2-android项目的地址: 大家可以下载最新版本jar,然后将jar加入到项目中即可。

我这里使用是ksoap2-android-assembly-2.5.4-jar-with-dependencies.jar

TCP协议基础

IP协议是Internet上使用的一个关键协议,全称是Internet
Protocol(Internet协议),简称为IP协议。通过使用这个协议,使得Internet成为一个允许连接不同类型的计算机和不同操作系统的网络。
如果要使两台计算机彼此之间进行通信,两台计算机必须使用同一种“语言”,IP协议只保证计算机能发送和接收分组数据。IP协议负责将消息从一个主机传送到另一个主机,消息在传送的过程中被分割成一个个小包。
TCP协议被称为一种端对端协议。它为两台计算机之间的连接起了重要作用:当一台计算机需要与另一台远程计算机连接时,TCP协议会让它们建立一个连接(用于发送和接收数据的虚拟链路)。
TCP协议负责收集信息包,并将其按适当的次序放好传送,在接收端收到后再将其正确地还原。TCP协议保证了数据包在传送中准确无误。
TCP协议使用重发机制:当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体的确认信息,如果没有收到另一个通信实体的确认信息,则会再次重发刚才发送的消息。

TCP协议的通信示意图如下所示:

www.9778.com 1

TCP协议的通信示意图.jpg

虽然IP和TCP两个协议的功能不相同,也可以分开单独使用,但它们是在同一个时期作为一个协议来设计的,并且在功能上是互补的。两者结合,才能保证Internet在复杂的环境下正常运行。凡是要连接到Internet的计算机,都必须同时安装和使用这两个协议,因此统称为TCP/IP协议。

天气预报这么一个功能并不是简单的JS组件就能够实现的,它的数据是依赖数据库分析出来的,甚至需要卫星探测..我们个人建站是不可能搞这么一个数据库的吧。

2、Ksoap2 使用的主要步骤

1)web服务参数准备

// webservice服务地址

String url= “http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx”;

//web服务的命名空间

String namespace=” http://WebXml.com.cn/”;

//请求服务的方法名称

String methodName=”getMobileCodeInfo”;

//soap请求地址

String soapActionAddress = "http://WebXml.com.cn/getMobileCodeInfo";

2)创建HttpTransportSE,该组件可发送请求

HttpTransportSE transport = new HttpTransportSE(url);

3)创建SoapObject,添加要传送的数据(信息载体)

SoapObject soapObject = new SoapObject(namespace,methodName);

soapObject.addProperty(name,value);//添加数据

…

4)创建SoapSerializationEnvelope对象,指定xml版本,以及request中body

SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);

envelope.bodyOut = soapObject;

envelope.setOutputSoapObject(soapObject);

5)发送请求,调用webserivce中的方法

httpTransportSE.call(soapActionAddress, envelope);//服务传回的信息,会放在envelope的bodyIn属性中

6) 获取服务传回的数据

SoapObject object = (SoapObject) envelope.bodyIn;

使用ServerSocket创建TCP服务器端

Java中能接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态。ServerSocket包含一个监听来自客户端连接请求的方法。

  • Socket
    accept():
    如果接收到一个客户端Socket的连接请求,该方法将返回一个与连接客户端Socket对应的Socket;否则该方法将一直处于等待状态,线程也被阻塞。
  • ServerSocket(int
    port):
    用指定的端口port来创建一个ServerSocket。该端口应该有一个有效的端口整数值0~65535。
  • ServerSocket(int port,int
    backlog):
    增加一个用来改变连接队列长度的参数backlog。
  • ServerSocket(int port,int backlog,InetAddress
    localAddr):
    在机器存在多个IP地址的情况下,允许通过localAddr这个参数来指定将ServerSocket绑定到指定的IP地址。

当ServerSocket使用完毕后,应使用ServerSocket的close()方法来关闭该ServerSocket。

那么既然我们自己干不了,我们可以去找别人吗???我们从搜索引擎搜索,可以发现很多提供天气预报的网站,但是它返回的是一个网页,而我们仅仅需要的是对应的数据

三、实现案例——通过调用webservice查询手机号码的归属地

执行效果如下:

www.9778.com 2

完整代码实现:

public class MainActivity extends Activity {
    ///手机归属地Webservice的参数信息
    private static final String nameSpaceAddress = "http://WebXml.com.cn/";
private static final String urlAddress
 = "http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx";
    private static final String methodNameAddress = "getMobileCodeInfo";
    private static final String soapActionAddress = "http://WebXml.com.cn/getMobileCodeInfo";
    private TextView telAddress = null;
    private EditText tel = null;
    private Button btnAddress = null;
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnAddress = (Button) this.findViewById(R.id.btnSearchAddress);
        telAddress = (TextView) this.findViewById(R.id.telAddress);
        tel = (EditText) this.findViewById(R.id.telNo);
        btnAddress.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    public void run() {
                        getTelAddress();
                    }
                }).start();

            }
        });
/**
* 请求WebService并获得返回的手机号码归属地信息
*/
public void getTelAddress() {
        SoapObject soapObject = new 
SoapObject(nameSpaceAddress, methodNameAddress);//创建SOAP对象
        //设置属性,这些属性值通过SOAP协议传送给服务器
        soapObject.addProperty("mobileCode", tel.getText().toString());//要查询的电话号码
        soapObject.addProperty("userId", "");        
        SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
                SoapEnvelope.VER11);
        envelope.bodyOut = soapObject;
        envelope.dotNet = true;        
        envelope.setOutputSoapObject(soapObject);
        HttpTransportSE httpTransportSE = new HttpTransportSE(urlAddress);
        try {
            //调用服务
            httpTransportSE.call(soapActionAddress, envelope);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //获取服务传回的数据,手机归属地信息
        SoapObject object = (SoapObject) envelope.bodyIn;
        txtAddress = object.getProperty(0).toString();
        //向主线程发送消息成功,getTelAddress函数执行完毕
        handlerAddress.sendEmptyMessage(0);

    }
    Handler handlerAddress = new Handler() {
        public void handleMessage(Message msg) {
            telAddress.setText(txtAddress);
            Toast.makeText(MainActivity.this, 
"获取号码归属地成功"+txtAddress, Toast.LENGTH_LONG).show();
        }
    };
}

使用Socket进行通信

客户端通常可以使用Socket的构造器来连接到指定服务器,Socket通常可以提供如下两种构造器:

  • Socket(InetAddress/String remoteAddress,int
    port):
    创建连接到指定远程主机、远程端口的Socket,该构造器没有指定本地地址、本地端口,默认使用本机主机的默认IP地址,默认使用系统动态分配的端口。
  • Socket(InetAddress/String remoteAddress,int port,InetAddress
    localAddr,int
    localPort):
    创建连接到指定远程主机、远程端口的Socket,并指定本地IP地址和本地端口,适用于本地主机有多个IP地址的情形。

Socket提供了如下两个方法来获取输入流和输出流:

  • 【www.9778.com】Android中使用Android Ksoap2调用WebService。InputStream
    getInputStream():
    返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据。
  • OutputStream
    getOutputStream():
    返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据。

我们可能就在想,我们能不能仅仅只要它返回的数据,而并不是经过加工处理后返回的网页呢??

四、附:常见的WebService服务URL

手机归属地服务

天气预报Web服务,数据来源于中国气象局

IP地址来:

中文 <-> 英文双向翻译 WEB 服务:

火车时刻表

航班查询服务

中国股票行情数据 WEB 服务

中国电视节目预告

加入多线程

服务器端应该包含多条线程,每个Socket对应一条线程,该线程负责读取Socket对应输入流的数据,并将读到的数据向每个Socket输出流发送一遍,因此需要在服务器端使用List来保存所有的Socket。
每个客户端应该包含两条线程:一条负责生成主界面,响应用户动作,并将用户输入的数据写入Socket对应的输出流中;另一条负责读取Socket对应输入流中的数据,并负责将这些数据在程序界面上显示出来。

于是乎,webService就诞生了,webservice就是一个部署在Web服务器上的,它向外界暴露出一个能够通过Web进行调用的API。也就是说:当我们想要获取天气预报的信息,我们可以调用别人写好的service服务,我们调用就能够得到结果了

使用URL访问网络资源

URL(Uniform Resource
Locator)代表统一资源定位符,它是指向互联网“资源”的指针。资源既可以是简单的文件或目录,也可以是对更复杂的对象的引用,例如对数据库或搜索引擎的查询。通常情况下,URL由协议名、主机、端口和资源组成,满足下面的格式:
**protocol://host:port/resourceName**

URL提供了以下方法来访问该URL对应的资源:

  • String getFile():获取此URL的资源名。
  • String getHost():获取此URL的主机名。
  • String getPath():获取此URL的路径部分。
  • int getPort():获取此URL的端口号。
  • String getProtocol():获取此URL的协议名称。
  • String getQuery():获取此URL的查询字符串部分。
  • URLConnection
    openConnection():
    返回一个URLConnection对象,它表示到URL所引用的远程对象的连接。
  • InputStream
    openStream():
    打开与此URL的连接,并返回一个用于读取该URL资源的InputStream。

可是我们写网站主流的就有好几个平台:Java、.net、PHP等等,那么部署在Web服务器上的服务器也就是webserice怎么能够就让我们不同的平台都能够调用呢??

用URLConnection提交请求

URL的openConnection()方法将返回一个URLConnection对象,这个对象表示应用程序和URL之间的通信连接。程序可以通过URLConnection实例向该URL发送请求,读取URL引用的资源。
通常情况下,创建一个和URL的连接,并发送请求、读取此URL引用的资源需要如下几个步骤:

  1. 通过调用URL对象的openConnection()方法来创建URLConnection对象。
  2. 设置URLConnection的参数和普通请求属性。
  3. 如果只是发送GET方式的请求,那么使用connect方法建立和远程资源之间的实际连接即可;如果需要发送POST方式的请求,则需要获取URLConnection实例对应的输出流来发送请求参数。
  4. 远程资源变为可用,程序可以访问远程资源的头字段,或通过输入流读取远程资源的数据。

在建立和远程资源的实际连接之前,程序可以通过如下方法来设置请求头字段:

  • setAllowUserInteraction:设置该URLConnection的allowUserInteraction请求头字段的值。
  • setDoInput:设置该URLConnection的doInput请求头字段的值。
  • setDoOutput:设置该URLConnection的doOutput请求头字段的值。
  • setIfModidiedSince:设置该URLConnection的ifModifiedSince请求头字段的值。
  • setUseCaches:设置URLConnection的useCaches请求头字段的值。

使用如下方法来设置或增加通用的头字段:

  • setRequestProperty(String key , String
    value):
    设置该URLConnection的key请求头字段的值value。
  • addRequestProperty(String key , String
    value):
    为该URLConnection的key请求头字段增加value值,该方法并不会覆盖原请求头字段的值,而是将新值追加到原请求头字段中。

程序可以使用以下方法来访问头字段和内容:

  • Object getContent():获取该URLConnection的内容。
  • String getHeaderField(String name):获取指定响应头字段的值。
  • getInputStream():返回该URLConnection对应的输入流,用于获取URLConnection响应的内容。
  • getOutputStream():返回该URLConnection对应的输出流,用于向URLConnection发送请求参数。

Java提供了以下方法来访问特定响应头字段的值:

  • getContentEncoding():获取content-encoding响应头字段的值。
  • getContentLength():获取content-length响应头字段的值。
  • getContentType():获取content-type响应头字段的值。
  • getDate():获取date响应头字段的值。
  • getExpiration():获取expires响应头字段的值。
  • getLastModified():获取last-modified响应头字段的值。

我们知道java、.net这样的平台他们语言的基本数据类型、复杂数据类型就可能不一样,那么怎么能够实现调用的呢???

使用HTTP访问网络

HttpURLConnection是URLConnection的一个子类,在URLConnection的基础上增加了一些用于操作HTTP资源的便捷方法。

来引用一段话

使用HttpURLConnection

它在URLConnection的基础上提供了如下便捷的方法:

  • int getResponseCode():获取服务器的响应代码。
  • String getResponseMessage():获取服务器的响应消息。
  • String getRequestMethod():获取发送请求的方法。
  • void setRequestMethod(String method):设置发送请求的方法。

程序可按照如下步骤实现多线程下载:

  1. 创建URL对象。
  2. 获取指定URL对象所指向资源的大小,此处用到了HttpURLConnection类。
  3. 在本地磁盘上创建一个与网络资源相同大小的空文件。
  4. 计算每条线程应该下载网络资源的哪个部分。
  5. 依次创建、启动多条线程来下载网络资源的指定部分。

大家在写应用程序查询数据库时,并没有考虑过为什么可以将查询结果返回给上层的应用程序,甚至认为,这就是数据库应该做的,其实不然,这是数据库通过TCP/IP协议与另一个应用程序进行交流的结果,而上层是什么样的应用程序,是用什么语言,数据库本身并不知道,它只知道接收到了一份协议,这就是SQL92查询标准协议。

使用Apache HttpClient

使用HttpClient发送请求、接收响应很简单,只要如下几步:

  1. 创建HttpClient对象。
  2. 如果需要发送GET请求,则创建HttpGet对象;如果需要发送POST请求,则创建HttpPost对象。
  3. 如果需要发送请求参数,则可调用HttpGet、HttpPost共同的setParams(HttpParams
    params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity
    entity)方法来设置请求参数。
  4. 调用HttpClient对象的execute(HttpUriRequest
    request)方法发送请求,执行该方法返回一个HttpResponse。
  5. 调用HttpResponse的getAllHeaders()、getHeaders(String
    name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。

无论是Java、.net、PHP等等的平台,只要是网页开发都是可以通过http协议来进行通信的,并且返回的数据要是通用的话,那么我们早就学过这样的一种技术

Android 5.0 增强的WebView

WebView组件本身就是一个浏览器实现,Android 5.0增强的WebView基于Chromium
M37,直接支持WebRTC、WebAudio和WebGLS。

所以webservice实际上就是http+XML

使用WebView浏览网页

WebView提供了大量方法来执行浏览器操作,如下所示:

  • void goBack():后退。
  • void goForward():前进。
  • void loadUrl(String url):加载指定URL对应的网页。
  • boolean zoomIn():放大网页。
  • boolean zoomOut():缩小网页。

www.9778.com 3这里写图片描述

使用WebView加载HTML代码

WebView提供了下面两个方法:

  1. loadData(String data , String mimeType , String
    encoding):
    该方法可用于加载并显示HTML代码。
  2. loadDtaWithBaseURL(String baseUrl , String data , String mimeType
    , ,String encoding , String
    historyUrl):
    这个方法是上面方法的增强版,但是它不会产生乱码。参数说明如下:
  • data:指定需要加载的HTML代码。
  • mimeType:指定HTML代码的MIME类型,对于HTML代码可指定为text/html。
  • encoding:指定HTML代码编码所用的字符集。比如指定为CBK。

WebService,顾名思义就是基于Web的服务。它使用Web方式,接收和响应外部系统的某种请求。从而实现远程调用.

使用WebView中的JavaScript调用Android方法

为了让WebView中的JavaScript脚本调用Android方法,WebView提供了一个配套的WebSettings工具类,这个工具类提供了大量方法来管理WebView的选项设置,其中它的setJavaScriptEnabled(true)即可让WebView中的JavaScript脚本来调用Android方法。除此之外,为了把Android对象暴露给WebView中JavaScript代码,WebView提供了addJavascriptInterface(Object object , String name)方法,该方法负责把object对象暴露成JavaScript中的name对象。

在WebView的JavaScript中调用Android方法只要如下三个步骤:

  1. 调用WebView关联的WebSettings的setJavaScriptEnabled(true)启用JavaScript调用功能。
  2. 调用WebView的addJavascriptInterface(Object object , String
    name)方法将object对象暴露给JavaScript脚本。
  3. 在JavaScript脚本中通过刚才暴露的name对象调用Android方法。

我们可以调用互联网上查询天气信息Web服务,然后将它嵌入到我们的程序(C/S或B/S程序)当中来,当用户从我们的网点看到天气信息时,他会认为我们为他提供了很多的信息服务,但其实我们什么也没有做,只是简单调用了一下服务器上的一段代码而已。

使用Web Service进行网络编程

学习WebService可以将你的服务发布到互联网上让别人去调用,也可以调用别人机器上发布的WebService,就像使用自己的代码一样.。

Web Service平台概述

Web Service平台主要涉及的技术有SOAP、WSDL、UDDI。
1. SOAP(简单对象访问协议)
SOAP是一种具有扩展性的XML消息协议。SOAP允许一个应用程序向另一个应用程序发送XML消息,SOAP消息是从SOAP发送者传至SOAP接收者的单路消息,任何应用程序均可作为发送者或接收者。
SOAP消息包含如下三种主要元素:

  • <Envelope…/>根元素,SOAP消息对应的XML文档以该元素作为根元素。
  • 可选的<Header../>元素,包含SOAP消息的头信息。
  • 必需的<Body…/>元素,包含所有的调用和响应信息。

2.WSDL(Web Service描述语言)
WSDL使用XML描述Web Service ,包括访问和使用Web
Service所必需的信息,定义该Web
Service的位置、功能及如何通信等描述信息。
一份WSDL文件清晰地定义了三个方面的内容:

  • WHAT部分:用于定义Web Service所提供的操作,也就是Web
    Service能做些什么。
  • HOW部分:用于定义如何访问Web Service,包括数据格式详情和访问Web
    Service操作的必要协议。
  • WHERE部分:用于定义Web Service位于何处。

一份WSDL文档通常可分为两个部分:第一部分定义了服务接口;第二部分定义了服务实现。

3.UDDI(统一描述、发现和整合协议)
UDDI是一套信息注册规范,具有如下特点:

  • 基于Web;
  • 分布式。
    UDDI包括一组允许企业向外注册Web
    Service,以使其他企业发现访问的实现标准。

我们在学习Java基础网络编程章节已经知道了Scoket这么一个连接了。

使用Android应用调用Web Service

为Android应用增加ksoap2-android支持请按如下步骤:

  1. 登录http://code.google.com/p/ksoap2-android/站点,该站点有介绍下载ksoap2-android项目的方法。
  2. 下载项目的jar包。
  3. 将下载得到的JAR包添加到Android项目的libs目录下,并通过Project面板选中该JAR包后,通过右键菜单的“Add
    as library”菜单项添加该JAR包。

使用ksoap2-android调用Web Service操作的步骤如下:

  1. 创建HttpTransportSE对象,该对象用于调用Web Service操作。
  2. 创建SoapSerializationEnvelope对象。
  3. 创建SoapObject对象,创建该对象时需要传入所要调用Web
    Service的命名空间、Web Service方法名。
  4. 如果有参数需要传给Web
    Service服务器端,则调用SoapObject对象的addProperty(String name ,
    Object
    value)方法来设置参数,该方法的name参数指定参数名;value参数指定参数值。
  5. 调用SoapSerializationEnvelope的setOutputSoapObject()方法,或者直接对bodyOut属性赋值,将创建的SoapObject对象设为SoapSerializationEnvelope的传出SOAP消息体。
  6. 调用对象的call()方法,并以SoapSerializationEnvelope作为参数调用远程Web
    Service。
  7. 调用完成后,访问SoapSerializationEnvelope对象的bodyIn属性,该属性返回一个SoapObject对象,该对象就代表了Web
    Service的返回消息。解析该SoapObject对象,即可获取调用Web
    Service的返回值。
public class SocketSer { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket; boolean flag = true; while  { //接收客户端的请求 System.out.println("监听客户端的数据:"); Socket sc = ss.accept(); InputStream is = sc.getInputStream(); byte[] buffer = new byte[1024]; int len = -1; len = is.read; String getData = new String(buffer, 0, len); System.out.println("从客户端获取的数据:" + getData); //业务处理 大小写转化 String outPutData = getData.toUpperCase(); //向客户端写数据 OutputStream os = sc.getOutputStream(); os.write(outPutData.getBytes; //释放资源 os.close(); is.close(); sc.close(); } ss.close(); }}

public class SocketClient { public static void main(String[] args) throws Exception { //获取用户输入的数据 Scanner input = new Scanner(System.in); System.out.println; String inputData = input.nextLine(); //开启一个Socket端口 Socket sc = new Socket("127.0.0.1", 6666); OutputStream os = sc.getOutputStream(); os.write(inputData.getBytes; //获取服务端回传的数据 InputStream is = sc.getInputStream(); byte[] buffer = new byte[1024]; int len = -1; len = is.read; String getData = new String(buffer, 0, len); System.out.println("从服务端获取的数据:" + getData); //是否流 is.close(); os.close(); sc.close(); }}
好的,到此结束!睡觉咯!

www.9778.com 4

二维码.jpg

当我们从客户端输入数据以后,那么服务端就会把数据转成是大写

www.9778.com 5这里写图片描述www.9778.com 6这里写图片描述

其实HTTP协议就是基于Socket对其进行封装,我们也可以在IE浏览器中对其进行访问.我们一样能够获取得到数据!

www.9778.com 7这里写图片描述www.9778.com 8这里写图片描述www.9778.com 9这里写图片描述

ISO的七层模型 :
物理层、数据链路层、网络层、传输层、表示层、会话层、应用层

  • Socket访问 :
    Socket属于传输层,它是对Tcp/ip协议的实现,包含TCP/UDP,它是所有通信协议的基础,Http协议需要Socket支持,以Socket作为基础

  • Socket通信特点:

    • 开启端口,该通信是 长连接的通信
      很容易被防火墙拦截,可以通过心跳机制来实现 ,开发难度大
    • 传输的数据一般是字符串 ,可读性不强
    • socket端口不便于推广
    • 性能相对于其他的通信协议是最优的
  • Http协议访问 :属于应用层的协议,对Socket进行了封装

    • 跨平台
    • 传数据不够友好
    • 对第三方应用提供的服务,希望对外暴露服务接口问题:
  • 数据封装不够友好 :可以用xml封装数据

  • 希望给第三方应用提供web方式的服务 (http + xml) = web Service

  • 名词1:XML. Extensible Markup Language -扩展性标记语言
    • XML,用于传输格式化的数据,是Web服务的基础。
    • namespace-命名空间。
    • xmlns=“ 使用默认命名空间。
    • xmlns:itcast=“
  • 名词2:WSDL – WebService Description Language
    Web服务描述语言。

    • 通过XML形式说明服务在什么地方-地址。
    • 通过XML形式说明服务提供什么样的方法 – 如何调用。
  • 名词3:SOAP-Simple Object Access Protocol
    • SOAP作为一个基于XML语言的协议用于有网上传输数据。
    • SOAP = 在HTTP的基础上+XML数据。
    • SOAP是基于HTTP的。
    • SOAP的组成如下:
      • Envelope – 必须的部分。以XML的根元素出现。
      • Headers – 可选的。
      • Body –
        必须的。在body部分,包含要执行的服务器的方法。和发送到服务器的数据。

首先,我们来尝试一下调用别人写好的webService,来体验一把:我们访问

www.9778.com 10这里写图片描述

进入到里边

www.9778.com 11这里写图片描述

当我们输入一个号码,它就能够查询出我们的手机位置信息:

www.9778.com 12image

我们现在要做的就是将这个服务让我们自己写的应用程序中也可以调用,那怎么做呢???

www.9778.com 13这里写图片描述

public void get(String mobileCode ,String userID ) throws Exception{ URL url=new URL("http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx/getMobileCodeInfo?mobileCode="+mobileCode+ "&userID="+userID); HttpURLConnection conn=(HttpURLConnection) url.openConnection(); conn.setConnectTimeout; conn.setRequestMethod; if(conn.getResponseCode()==HttpURLConnection.HTTP_OK){ //结果码=200 InputStream is=conn.getInputStream(); //内存流 , ByteArrayOutputStream boas=new ByteArrayOutputStream(); byte[] buffer=new byte[1024]; int len=-1; while((len=is.read!=-1){ boas.write(buffer, 0, len); } System.out.println("GET请求获取的数据:"+boas.toString; boas.close(); is.close(); } }

www.9778.com 14这里写图片描述

为什么要使用HttpClient工具:

  • 原生态的Socket基于传输层,现在我们要访问的WebService是基于HTTP的属于应用层,所以我们的Socket通信要借助HttpClient发HTTP请求,这样格式才能匹配

HttpClient使用步骤如下:

  1. 创建 HttpClient 的实例
  2. 创建某种连接方法的实例,在这里是 GetMethod。在 GetMethod
    的构造函数中传入待连接的地址
  3. 配置要传输的参数,和消息头信息
  4. 调用第一步中创建好的实例的 execute 方法来执行第二步中创建好的 method
    实例
  5. 通过response读取字符串
  6. 释放连接。无论执行方法是否成功,都必须释放连接

 //2.Post请求 :通过Http-Client 框架来模拟实现 Http请求 public void post(String mobileCode ,String userID) throws Exception{/**HttpClient访问网络的实现步骤: * 1. 准备一个请求客户端:浏览器 * 2. 准备请求方式: GET 、POST * 3. 设置要传递的参数 * 4.执行请求 * 5. 获取结果 */ HttpClient client=new HttpClient(); PostMethod postMethod=new PostMethod("http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx/getMobileCodeInfo"); //3.设置请求参数 postMethod.setParameter("mobileCode", mobileCode); postMethod.setParameter("userID", userID); //4.执行请求 ,结果码 int code=client.executeMethod(postMethod); //5. 获取结果 String result=postMethod.getResponseBodyAsString(); System.out.println("Post请求的结果:"+result); } //2.Post请求 :通过Http-Client 框架来模拟实现 Http请求 public void soap() throws Exception{ HttpClient client=new HttpClient(); PostMethod postMethod=new PostMethod("http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx"); //3.设置请求参数 postMethod.setRequestBody(new FileInputStream("c:/soap.xml")); //修改请求的头部 postMethod.setRequestHeader("Content-Type", "text/xml; charset=utf-8"); //4.执行请求 ,结果码 int code=client.executeMethod(postMethod); System.out.println("结果码:"+code); //5. 获取结果 String result=postMethod.getResponseBodyAsString(); System.out.println("Post请求的结果:"+result); }

上面我们使用的是GET方式或者使用Http-Client框架来调用webservice的服务,其实这两种方式也有弊端

  • 传递参数麻烦【get方式都写在请求地址上、post方式要一个一个封装】
  • 解析结果麻烦【根据返回的XML来解析字符串】

如果我们可以把整个对象传递进去,返回的结果更加友好的话,就好像我们平常调用Java类一样使用webservice就好咯

Java也提供了类似的方法,把webservice服务搞成是Java类让我们自己调用,既然是Java类的话,那么我们使用起来就非常方便了!

把webservice服务搞成是Java类让我们自己调用其实就是Java帮我们生成本地代理,再通过本地代理来访问webservice

wsimport是Java自带的一个命令,我们想要使用该命令,就必须配置环境变量,并且jdk的版本最好是1.7或以上

值得注意的是:ide带的JDK版本要和wsimport生成本地的版本一致,不然就用不了!!!

  • wsimport使用:wsimport命令后面跟着的是WSDL的url路径 语法
    wsimport [opations] <wsdl_uri>

    • wsdl_uri:wsdl 的统一资源标识符

    • d :指定要输出的文件的位置

    • s :表示要解析java的源码 ,默认解析出的是class字节码

    • p : 指定输出的包名

www.9778.com 15这里写图片描述

首先我们先把cmd的路径退到桌面上:

www.9778.com 16这里写图片描述

然后对WSDL文件生成本地代理

www.9778.com 17这里写图片描述www.9778.com 18这里写图片描述

该本地代理其实就是一堆的字节码文件

www.9778.com 19这里写图片描述

将得到的字节码文件打包成jar,那么我们只要在项目中导入jar包,就可以调用了!

语法

jar cvf test.jar 打包目录

www.9778.com 20这里写图片描述

本来我是想将本地代理的class文件生成jar包,然后导入到idea环境下,那么直接调用就行了。可是idea老是报出找不到对应的类,找了半天也找不到,很烦呀!!!!我考虑了以下的几种情况

  • ** 生成的class文件的JVM和idea下的JVM环境不匹配**
  • idea缓存原因,把idea所有缓存去掉也不行
  • 生成的本地代理包名cn不行【???idea就是对cn这个包名报错,后来我改成自定义的包名也不行】

最后我还是没有找到办法,如果知道是什么原因的,麻烦在评论中告诉我吧….因此这次的测试import,我就不仅仅生成class字节码文件,还生成了.java文件。我就直接使用java文件来测试了。

在zhongfucheng目录下生成本地代理,把java源码也带上

www.9778.com 21这里写图片描述

于是我就把java源码复制到我的项目中,用java源码来进行测试

www.9778.com 22这里写图片描述

有的同学可能会疑问,为啥wsimport能那么厉害,将http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?WSDL这么一个url生成本地代理,其实我们看了WSDL文件就知道了。

www.9778.com 23这里写图片描述

www.9778.com,值得注意的是,本地代理仅仅是有其方法,类,并不能解析出具体的实现的。具体的操作其实还是webservice去完成的。代理这么一个概念就更加清晰了。

我们在上一章节中已经使用wsimport生成本地代理来调用webservice的服务了,其实我们自己写的web应用程序也是可以发布webservice的

我们发布了webservice的话,那么其他人也是可以调用我们自己写的webservice!

那么我们怎么自定义webservice然后发布出去呢???

在jdk 1.6 版本以后 ,通过jax-ws 包提供对webservice的支持

  • 该方式通过注解的方式来声明webservice
  • 通过 jdk EndPoint.publish()发布webserive服务

写一个实体:

public class Phone { private String name;//操作系统名 private String owner;//拥有者 private int total;//市场占有率 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } public int getTotal() { return total; } public void setTotal(int total) { this.total = total; }}

发布service,通过注解来让WSDL文件更加可读…

package cn.it.ws.d;import cn.it.ws.model.Phone;import javax.jws.WebMethod;import javax.jws.WebParam;import javax.jws.WebResult;import javax.jws.WebService;import javax.xml.ws.Endpoint;/**手机的业务类,该业务类通过webservice 对外提供服务 * 1. 声明: @webservice * 2. 发布 EndPoint */@WebService (serviceName="PhoneManager",//修改服务名 targetNamespace="http://dd.ws.it.cn") //修改命名空间 ,默认包名,取反//声明该业务类 对外提供webservice服务 ,默认只是对public 修饰的方法对外以webservice形式发布public class PhoneService {/**@WebMethod(operationName="getMObileInfo"): 修改方法名 * @WebResult(name="phone"):修改返回参数名 * @WebParam(name="osName"):修改输入参数名 */ @WebMethod(operationName="getMObileInfo") public @WebResult(name="phone") Phone getPhoneInfo(@WebParam(name="osName")String osName){ Phone phone=new Phone(); if(osName.endsWith("android")){ phone.setName("android");phone.setOwner;phone.setTotal; }else if(osName.endsWith{ phone.setName;phone.setOwner;phone.setTotal; }else{ phone.setName("windows phone");phone.setOwner("microsoft");phone.setTotal; } return phone; } @WebMethod(exclude=true)//把该方法排除在外 public void sayHello(String city){ System.out.println("你好:"+city); } private void sayLuck(String city){ System.out.println("好友:"+city); } void sayGoodBye(String city){ System.out.println("拜拜:"+city); } protected void saySayalala(String city){ System.out.println("再见!"+city); } public static void main(String[] args) { String address1="http://127.0.0.1:8888/ws/phoneService";// String address2="http://127.0.0.1:8888/ws/phoneManager";/** * 发布webservice服务 * 1.address:服务的地址 * 2:implementor 服务的实现对象 */ Endpoint.publish(address1, new PhoneService;// Endpoint.publish(address2, new PhoneService; System.out.println("wsdl地址 :"+address1+"?WSDL"); }}
  1. 在类上添加@WebService注解,代表发布一个WebService服务
  2. 通过EndPoint发布一个webService。Endpoint也是jdk提供的一个专门用于发布服务的类,它的publish方法接收两个参数,一个是本地的服务地址,二是提供服务的类。它位于javax.xml.ws.*包中。
  3. Endpoint.publish(String address, Object implementor)
    静态方法在给定地址处针对指定的实现者对象创建并发布端点
  4. 给类添加上@WebService注解后,类中所有的非静态方法都将会对外公布
  5. 如果希望某个方法不对外公开,可以在方法上添加@WebMethod(exclude=true),阻止对外公开。
  6. 如果一个类上,被添加了@WebService注解,则必须此类至少有一个可以公开的方法,否则将会启动失败。protected、private、final、static方法不能对外公开

@WebService // 添加了此注解,代表是一个WebServicepublic class HelloWorld { // 非 static final private 方法默认会发布 public String sayHi(String name) { return "hello" + name; } @WebMethod(exclude=true) public void exclude(){ // 被注解排除的方法 } protected void protected1(){ //受保护的方法默认不发布 } private void private1(){ // 私有方法默认不发布 } public static void static1(){ // static 方法默认不发布 } public final void final1(){ // final 方法默认不发布 }}

www.9778.com 24这里写图片描述

生成的webservice能够在浏览器访问

www.9778.com 25这里写图片描述www.9778.com 26这里写图片描述www.9778.com 27这里写图片描述www.9778.com 28这里写图片描述

目前WebService的协议主要有SOAP1.1和1.2。

  • 两者的命名空间不同。
    • Soap1.1的命名空间:
      • xmlns:soap=“ “
    • Soap1.2 命名空间:
      • xmlns:soap=”
  • SOAP1.1版本与SOAP1.2版本在头信息上存在差异。
    • SOAP1.1存在SOAPAction的请求头。
    • SOAP1.2没有SOAPAction的请求头。
  • 基于SOAP1.1生成的WSDL和基于SOAP1.2生成的WSDL也不一样。主要看命名空间。
  • 在CXF中两种协议请求的方式也不一样。
    • 1.1为content-Type:text/xm;charset=UTF-8
    • 1.2为content-Type:application/soap+xml;charset=UTF-8

www.9778.com 29这里写图片描述www.9778.com 30这里写图片描述www.9778.com 31这里写图片描述

Soa(Service-Oriented Architecture)
面向服务的架构,它是一种思想,IBM大力倡导是即插即用的,IBM大力提倡,希望以组装电脑的方式来开发应用

组成:

  • 面向web的服务,面向web的组件 :WebService : 硬盘、cpu、内存条

  • 企业服务总线 (EnterPrise Service Bus :ESB)。主板

uddi (Universal Description, Discovery and
Integration)统一描述、发现、集成

  • 它是目录服务,通过该服务可以注册和发布webservcie,以便第三方的调用者统一调用

  • 用得并不太多。

import javax.jws.WebService;/**面向接口的webservice发布方式 * * */@WebServicepublic interface JobService { public String getJob();}

import javax.jws.WebService;@WebService(endpointInterface="cn.it.ws.e.JobService")//设置服务端点接口 ,指定对外提供服务的接口public class JobServiceImpl implements JobService { @Override public String getJob() { return "JEE研发工程师|Android研发工程师|数据库工程师|前端工程师|测试工程师|运维工程师"; } public void say(){ System.out.println; }}

import javax.xml.ws.Endpoint;public class Test { public static void main(String[] args) { JobService jobService=new JobServiceImpl(); String address="http://192.168.114.10:9999/ws/jobservice"; Endpoint.publish(address, jobService); System.out.println("wsdl地址:"+address+"?WSDL"); }}

Apache CXF 是一个开源的 Services 框架,CXF 帮助您来构建和开发 Services
这些 Services 可以支持多种协议,比如:SOAP、POST/HTTP、RESTful HTTP
CXF 大大简化了 Service可以天然地和 Spring 进行无缝集成。

CXF介绍 :soa的框架

    • cxf 是 Celtrix 和 XFire(webserivice)
      合并而成,并且捐给了apache
    • CxF的核心是org.apache.cxf.Bus,类似于Spring的 ApplicationContext
    • CXF默认是依赖于Spring的
    • Apache CXF 发行包中的jar,如果全部放到lib中,需要 JDK1.6
      及以上,否则会报JAX-WS版本不一致的问题
    • CXF 内置了Jetty服务器 ,它是servlet容器,好比tomcat

CXF特点

  • 与Spring、Servlet做了无缝对接,cxf框架里面集成了Servlet容器Jetty
  • 支持注解的方式来发布webservice
  • 能够显示一个webservice的服务列表
  • 能够添加拦截器:输入拦截器、输出拦截器 :
  • 输入日志信息拦截器、输出日志拦截器、用户权限认证的拦截器

要想使用CXF框架,那么就先导入jar包

  • asm-3.3.jar
  • commons-logging-1.1.1.jar
  • cxf-2.4.2.jar
  • jetty-continuation-7.4.5.v20110725.jar
  • jetty-http-7.4.5.v20110725.jar
  • jetty-io-7.4.5.v20110725.jar
  • jetty-security-7.4.5.v20110725.jar
  • jetty-server-7.4.5.v20110725.jar
  • jetty-util-7.4.5.v20110725.jar
  • neethi-3.0.1.jar
  • wsdl4j-1.6.2.jar
  • xmlschema-core-2.0.jar

接口

 import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService;@WebService(serviceName="languageManager")public interface LanguageService { public @WebResult(name="language")String getLanguage(@WebParam(name="position")int position);}

实现:

package cn.it.ws.cxf.a;import org.apache.cxf.frontend.ServerFactoryBean;import org.apache.cxf.interceptor.LoggingInInterceptor;import org.apache.cxf.interceptor.LoggingOutInterceptor;import org.apache.cxf.jaxws.JaxWsServerFactoryBean;/**开发语言排行描述服务 * * * @author 李俊 2015年5月17日 */public class LanguageServiceImpl implements LanguageService { /* (non-Javadoc) * @see cn.it.ws.cxf.a.LanguageService#getLanguage */ @Override public String getLanguage(int position){ String language=null; switch  { case 1: language="java"; break; case 2: language="C"; break; case 3: language="Objective-C"; break; case 4: language="C#"; break; default: break; } return language; } /**通过cxf框架发布webservice * 1. ServerFactoryBean * - 不设置注解也可以发布webservice服务, 不支持注解 * - 不支持拦截器的添加 * 2. JaxWsServerFactoryBean * - 支持注解 * - 可以添加拦截器 * 3. webservice 访问流程: * 1. 检测本地代理描述的wsdl是否与服务端的wsdl一致 ,俗称为握手 * 2. 通过soap协议实现通信 ,采用的是post请求 , 数据封装在满足soap规约的xml中 * 3. 返回数据 同样采用的是soap通信, 数据封装在满足soap规约的xml中 * @param args public static void main(String[] args) { LanguageService languageService=new LanguageServiceImpl(); ServerFactoryBean bean=new ServerFactoryBean(); //Endpoint :地址 , 实现对象 bean.setAddress("http://192.168.114.10:9999/ws/cxf/languangeService"); bean.setServiceClass(LanguageService.class);//对外提供webservcie的业务类或者接口 bean.setServiceBean(languageService);//服务的实现bean bean.create();//创建,发布webservice System.out.println("wsdl地址:http://192.168.114.10:9999/ws/cxf/languangeService?WSDL"); } */ public static void main(String[] args) { LanguageService languageService=new LanguageServiceImpl(); JaxWsServerFactoryBean bean=new JaxWsServerFactoryBean(); //Endpoint :地址 , 实现对象 bean.setAddress("http://192.168.114.10:9999/ws/cxf/languangeService"); bean.setServiceClass(LanguageService.class);//对外提供webservcie的业务类或者接口 bean.setServiceBean(languageService);//服务的实现bean //添加输入拦截器 :输入显示日志信息的拦截器 bean.getInInterceptors().add(new LoggingInInterceptor; //添加输出拦截器 :输出显示日志信息的拦截器 bean.getOutInterceptors().add(new LoggingOutInterceptor; bean.create();//创建,发布webservice System.out.println("wsdl地址:http://192.168.114.10:9999/ws/cxf/languangeService?WSDL"); }}
  • 建立一个web项目
  • 准备所有jar包,将CXF_HOMElib项目下的所有jar包,全部都拷贝新项目的lib目录下.其中里面已经包含了Sring3.0的jar包
    其中jetty 服务器的包可以不要.因为我们要部署的tomcat服务器中了
  • 在web.xml中配置cxf的核心servlet,CXFServlet
  • 此配置文件的作用类 拦截/ws/*的所有请求 类似Struts2的过滤器

web.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>CXF_Server</display-name> <!-- 添加 CXF 的Servlet ,处理 webservice的请求 --> <servlet> <servlet-name>cxf</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>cxf</servlet-name> <url-pattern>/ws/*</url-pattern> </servlet-mapping> <!-- Spring 监听添加 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param></web-app>

实体:

public class Employee { private Integer id; private String name; private Integer age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; }}

接口:

package cn.it.ws.cxf.b;import java.util.List;import javax.jws.WebParam;import javax.jws.WebResult;import javax.jws.WebService;import cn.it.ws.cxf.bean.Employee;@WebService(serviceName="EmployeeService")public interface EmployeeManager { void add(@WebParam(name="employee")Employee employee); @WebResult(name="employees")List<Employee> query();}

接口实现:

package cn.it.ws.cxf.b;import java.util.ArrayList;import java.util.List;import cn.it.ws.cxf.bean.Employee;/**员工管理的业务实现类 * @author 李俊 2015年5月17日 */public class EmployeeManagerImpl implements EmployeeManager { private List<Employee> employees=new ArrayList<>(); @Override public void add(Employee employee){ //添加到集合中 employees.add; } @Override public List<Employee> query(){ return employees; }}

Spring配置信息:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"> <bean ></bean> <!-- 配置cxf 地址: http://192.168.114.10:8080/CXF_Server/ws/employeeManager 组成 : http://192.168.114.10:8080 +CXF_Server+ws+/employeeManager 服务类 : 服务的实现类: 拦截器 --> <jaxws:server address="/employeeManager" serviceClass="cn.it.ws.cxf.b.EmployeeManager"> <jaxws:serviceBean> <ref bean="employeeManagerImpl"/> </jaxws:serviceBean> <!-- 配置输入显示日志信息的拦截器 --> <jaxws:inInterceptors> <bean ></bean> </jaxws:inInterceptors> <jaxws:outInterceptors> <bean ></bean> </jaxws:outInterceptors> </jaxws:server></beans>

我们的Intellij idea是一个非常好用的java
ide,当然了,它也支持webservice开发。非常好用…由于在网上见到的教程非常多,我就贴几个我认为比较好的教程:

我们现在webservice就基本入门了,现在我想要做的就是自己写的网站能够拿到天气预报的信息,于是我去

这个是天气预报的WSDL地址:

www.9778.com 32这里写图片描述

如果不想得到所有的信息,那么我们可以在服务上找到我们想要对应的数据,也就是说:

www.9778.com 33这里写图片描述www.9778.com 34这里写图片描述

  • 应用webservice的原因就在于我们需要一些服务、这些服务是我们自己不能手动写的。比如天气预报,于是就出现了webService技术。webService能够让我们可以获取网上别人发布出来的服务。我们只要调用它,就可以获取相关的数据了。
  • Socket其实就是对TCP/IP协议的一个封装,而我们在网上使用的是HTTP协议。WebService也是Web应用程序。它也当然支持HTTP协议了。不过WebService需要给不同语言都能够使用,因此它使用XML来进行传输。
  • 于是,它就有自己一种协议:SOAP。其实SOAP就是Http+XML
  • 我们可以使用http-get方式访问webservice,由于它使用的是原生Socket来进行访问。会有点复杂。于是我们可以借助Http-Client
    框架来访问WebService。Http-Client
    框架比HTTP-GET方式会简单一点。但还是不够简洁。
  • 最后,我们可以使用Java自带的WsImport来实现本地代理。这种方法会将WebService翻译成Java类,我们使用类一样去访问WebService就行了。非常好用。
  • 我们是可以自己写webService的。对服务类上加上注解。通过EndPoint就能够把我们webService服务类发布出去了。
    • 为了让WDSL文件更加读取,可以使用注解的方式来写好对应的参数名称。
    • 也可以控制某方法是否被发布出去
  • SOAP其实上就是使用XML进行传输的HTTP协议。
  • SOA:面向服务架构。即插即用。也就是耦合非常低,用的时候加上就行了。
  • UDDI (Universal Description, Discovery and
    Integration)统一描述、发现、集成,其实就是一个webservice的目录结构,不过我们很少把webservice发布到上面去
  • 实现接口的webservice只是在类上对其的一种抽象而已,没什么大不了的。
  • CXF框架可以与spring无缝连接,就不用我们自己Endpoint了。它还能记录日志之类的。
  • 我们还可以使用Idea下的webservice,能够使用图形画面的方式获取本地代理和生成WSDL文件。

如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y