菜鸟笔记
提升您的技术认知

WebService总结

WebService简介

含义:webService即web服务,他是一种跨编程语言和跨操作系统平台的远程调用技术。

WebService架构图

理解:

  • 跨语言:右侧为服务端,左侧为客户端;若右边的服务使用PHP语言实现的,那么调用右边服务端的客户端不管是用什么语言实现的,都可以去远程访问右边的客户端的接口。
  • 跨平台:无论右边的服务端部署在那个系统平台,只要其提供了一个接口给外界去调用,那么左边的客户端部署在哪里,使用什么语言,一样可以远程调用服务端。
  • WebService主要适用于多个系统之间的交互以及数据传递

注意:客户端与服务端可能使用不同语言开发的,但是通过webservice提供的服务接口,客户端与服务端之间可以传递对象。

WebService的开发规范

JAX-WS:java API for XML-WebService,jdk1.6版本自带JAX-WS2.1,其底层支持JAXB;JAX-WX规范的API位于javax.xml.ws.*包内,其中大部分都是注解,提供API操作web服务

JAXM&SAAJ:

  • JAXM:java API for XML Message,其主要定义了包含发送和接收消息的API,相当于Web服务的服务器端,其API位于javax.messaging.*包,他是javaEE的可选包,因此需要单独下载
  • SAAJ:soap with Attachment API for java,其为与JAXM搭配使用的API,为构建soap包和解析soap包提供了重要的支持;其支持附件传输,他在客户端和服务端都需要使用,其API位于javax.xml.soap.*包

JAX-RS:java API for RESTful Web Services,其是java针对REST风格定制的一套web服务规范,该API位于javax.ws.rs.*包内。

注意:

  • JAX-WS和JAXM&SAAJ是基于soap协议,而JAX-RS基于http协议。
  • 三者规范中,只有jax-rs规范支持传递json数据,其他的规范都仅支持传递xml数据

SOAP协议

含义:simple object access protocol——简单对象访问协议,它是用于交换XML编码信息的轻量级协议。

soap的组成

  • Envelope:其为必须的部分,以XML的根元素出现
  • Headers:可选的
  • Body:必须的,在body部分包含了要执行的服务器的方法和发送给服务器的数据

理解:

  • soap作为一个基于XML语言的协议用于网上传输数据
  • soap是基于http的,他相当于在http的基础上+xml数据格式
  • soap可以运行在任何其他传输协议上
  • XML-envelope为描述信息内容和如何处理内容定义了框架,将程序编码成了XML对象的规则,执行远程调用(rpc)的约定 

WSDL 

含义:WSDL(网络服务描述语言,Web Services Description Language)是一门基于 XML 的语言,用于描述 Web Services 以及如何对它们进行访问。

理解:

  • 通过wsdl说明书,就可以描述webservice服务端对外发布的服务
  • wsdl说明书基于XML文件,其可以通过XML语言来描述整个服务
  • 在wsdl中描述了:对外发布的服务名称(类)、接口的方法名称(方法)、接口参数(方法参数)、服务返回的数据类型(方法返回值)
  • 一般在webservice的url后面跟上?wsdl来获取WSDL信息

UDDI

含义:UDDI是一个跨产业、跨平台的开放性架构,其可帮助web服务提供商在互联网上发布web服务的信息

理解:

  • UDDI就是一种目录服务,企业可以通过UDDI来注册和搜索web服务
  • UDDI通过soap进行通讯,其构建于.NET之上

WebService的优缺点

WebService优点

  • 异构平台的互通性(跨平台)
  • 更广泛的软件复用(远程调用实现复用)
  • 成本低,可读性强,应用范围广(基于soap协议)
  • 更迅捷的软件发行方式

WebService缺点

由于soap是基于xml传输的,本身使用xml传输会传输一些无关的内容进而影响效率,随着soap的完善,soap协议增加了许多内容,这样就导致了使用soap去完成简单的数据传输而携带的信息变得更多进而影响效率

注意:基于JAX-RS规范下的webservice也可以传输json格式数据,这在一定程度上弥补了传输效率问题

SOA

含义:面向服务架构,其是一种思想,它将应用程序的不通功能单元通过中立的契约联系起来,使得各种形式的功能单元相互集成,目前来说webservice是soa的一种较好的实现方式。

ApacheCXF框架

含义:其是Apache开源基金组织提供的优秀的webservice实现框架

CXF分为JAX-WS和JAX-RS两种开发方式

  • JAX-WS:基于xml协议的WebService技术
  • JAX-RS:基于restful风格的开发方式

ApacheCXF实现WebService(JAX-WS)

服务端发布服务

导入依赖

       <!--进行jaxws开发的核心包-->
       <dependencies>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>3.0.1</version>
        </dependency>
        <!--内置jetty web服务器-->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>3.0.1</version>
        </dependency>
        <!--日志的实现-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.12</version>
        </dependency>
        <!--junit测试类-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

在resources文件内添加日志(log4j.properties) 

#info等级的日志输出到CONSOLE和LOGFILE这两个目的地(LOGFILE表示将日志写到文件中,CONSOLE则将日志写到控制台)
log4j.rootCategory=info,CONSOLE,LOGFILE
#设置日志优先控制台输出
log4j.logger.org.apache.axis.enterprise=FATAL,CONSOLE
#定义控制台日志输出器
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
#控制台日志布局
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
#控制台日志布局的设置
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601}%-6r[%15.15t]%-5p %30.30c %x-%m\n
#定义文件日志输出器
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
#日志的存放位置
log4j.appender.LOGFILE.File=C:\\All\\jax.log
#启用文件日志追加模式
log4j.appender.LOGFILE.Append=true
#文件日志布局
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout

创建服务接口

@WebService
public interface HelloWorld {
    //对外发布服务的接口的方法
    public String sayHello(String name);
}

注意:对外发布服务的接口,需要用@webservice注解来标识这是一个webservice接口

创建接口实现类

public class HelloWorldImpl implements HelloWorld {
    public String sayHello(String name) {
        return name+"hello webservice!";
    }
}

测试类内发布服务

public class WsTest {
    public static void main(String[] args) {
        //创建发布服务的工厂
        JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
        //设置服务地址
        factory.setAddress("http://localhost:8000/ws/hello");
        //设置发布的服务类
        factory.setServiceBean(new HelloWorldImpl());
        //添加日志输入、输出拦截器,观察soap请求以及soap响应内容
        factory.getInInterceptors().add(new LoggingInInterceptor());
        factory.getOutInterceptors().add(new LoggingOutInterceptor());
        //发布服务
        factory.create();
        System.out.println("发布服务成功,端口8000放行");
    }
}

访问wsdl说明书

访问:http://localhost:8000/ws/hello?wsdl

注意:在之前设置服务的地址后面加?wsdl

<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://impl.tedu.cn/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns2="http://schemas.xmlsoap.org/soap/http" xmlns:ns1="http://tedu.cn/" name="HelloWorldImplService" targetNamespace="http://impl.tedu.cn/">
<wsdl:import location="http://localhost:8000/ws/hello?wsdl=HelloWorld.wsdl" namespace="http://tedu.cn/"> </wsdl:import>
<wsdl:binding name="HelloWorldImplServiceSoapBinding" type="ns1:HelloWorld">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="sayHello">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="sayHello">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="sayHelloResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="HelloWorldImplService">
<wsdl:port binding="tns:HelloWorldImplServiceSoapBinding" name="HelloWorldImplPort">
<soap:address location="http://localhost:8000/ws/hello"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

客户端访问服务

导入依赖(和服务端使用的依赖一样)

获得服务端接口

@WebService
public interface HelloWorld {
    public String sayHello(String name);
}

注意:客户端获得的服务端接口的包名.接口名必须与服务端的包名.接口名都相同才可以进行远程调用(也必须有@WebService注解)

远程访问服务端

public class ClientTest {
    public static void main(String[] args) {
        //服务接口的访问地址:http://localhost:8000/ws/hello
        //创建cxf代理工厂
        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
        //设置远程访问服务端的地址
        factory.setAddress("http://localhost:8000/ws/hello");
        //设置接口的类型
        factory.setServiceClass(HelloWorld.class);
        //对该接口生成代理对象
        HelloWorld helloWorld = factory.create(HelloWorld.class);
        //打印代理对象类型
        System.out.println(helloWorld.getClass());
        //远程访问服务端方法
        String msg = helloWorld.sayHello("lili");
        System.out.println(msg);
    }
}

ApacheCXF实现WebService(JAX-RS)

服务端发布服务

导入依赖

    <dependencies>
        <!--进行jaxrs开发的核心包-->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <version>3.0.1</version>
        </dependency>
        <!--内置jetty web服务器-->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>3.0.1</version>
        </dependency>
        <!--日志的实现-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.12</version>
        </dependency>
        <!--客户端调用时需要使用的依赖-->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-rs-client</artifactId>
            <version>3.0.1</version>
        </dependency>
        <!--对json支持的两个依赖(就是不仅可以传递xml数据,也可以传递json数据)-->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-rs-extension-providers</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jettison</groupId>
            <artifactId>jettison</artifactId>
            <version>1.3.7</version>
        </dependency>
        <!--junit测试类-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!--maven编译插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <showWarnings>true</showWarnings>
                </configuration>
            </plugin>
        </plugins>
    </build>

在resources文件内添加日志(log4j.properties) 

#info等级的日志输出到CONSOLE和LOGFILE这两个目的地(LOGFILE表示将日志写到文件中,CONSOLE则将日志写到控制台)
log4j.rootCategory=info,CONSOLE,LOGFILE
#设置日志优先控制台输出
log4j.logger.org.apache.axis.enterprise=FATAL,CONSOLE
#定义控制台日志输出器
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
#控制台日志布局
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
#控制台日志布局的设置
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601}%-6r[%15.15t]%-5p %30.30c %x-%m\n
#定义文件日志输出器
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
#日志的存放位置
log4j.appender.LOGFILE.File=C:\\All\\jax.log
#启用文件日志追加模式
log4j.appender.LOGFILE.Append=true
#文件日志布局
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout

创建实体类(User)

@XmlRootElement(name="User")
public class User {
    private String name;
    private String city;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}

@XmlRootElement(name="User")

作用:基于restful风格的webservice,客户端与服务端之间的通讯可以传递xml数据、json数据;而@XmlRootElement用于指定对象序列化为xml或json数据时根节点的名称。

xml形式
<User>
    <name>张三</name>
    <city>北京</city>
</User>

json形式
{"User":{"name":"张三","city":"北京"}}

创建服务端接口(IUserService)

@Path("/userService")
@Produces("*/*")
public interface IUserService {
    @POST
    @Path("/save")
    @Consumes({"application/xml","application/json"})
    String saveUser(User user);
    @GET
    @Path("/get/{name}")
    @Consumes("application/xml")
    @Produces({"application/xml","application/json"})
    User findUserByName(@PathParam("name") String name);
}

@Path("/userService")

理解:该注解可以用在类上以及方法上,表示当前服务接口或接口方法对应的路径(若要访问接口方法则必须先访问接口,在接口的基础上进行path路径拼接)

@POST或@GET

含义:处理接口对应方法的请求类型

@Produces({"application/xml","application/json"})

含义:服务器所支持的返回的数据格式(xml格式或json格式)

@Consumes("application/xml")

含义:服务器所支持的请求数据的格式类型

@PathParam

作用:注解用于路径中的参数与接口方法中的参数进行绑定

创建接口实现类(UserServiceImpl)

public class UserServiceImpl implements IUserService {
    @Override
    public String saveUser(User user) {
        System.out.println("保存了"+user.toString());
        return "user保存成功";
    }
    @Override
    public User findUserByName(String name) {
        User user = new User();
        if ("lili".equals(name)){
            user.setName("lili");
            user.setCity("北京");
        }else {
            user.setName("随机");
            user.setCity("随机");
        }
        return user;
    }
}

发布服务

public class JaxrsTest {
    public static void main(String[] args) {
        //创建发布服务的工厂
        JAXRSServerFactoryBean factoryBean = new JAXRSServerFactoryBean();
        //设置服务地址
        factoryBean.setAddress("http://localhost:8001/");
        //设置服务类
        factoryBean.setServiceBean(new UserServiceImpl());
        //添加日志输入输出拦截器
        factoryBean.getInInterceptors().add(new LoggingInInterceptor());
        factoryBean.getOutInterceptors().add(new LoggingOutInterceptor());
        //发布服务
        factoryBean.create();
        System.out.println("发布服务成功。端口:8001");
    }
}

访问:http://localhost:8001/userService/get/lili

客户端访问服务

导入依赖(和服务端使用的依赖一样)

制作需要使用的实体类

@XmlRootElement(name="User")
public class User {
    private String name;
    private String city;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}

注意:这里实体类所用的包名可以和服务端的不一样,但是@XmlRootElement(name="User")注解必须存在

远程调用服务端

前言:这里远程调用服务端只需要使用WebClient工具类即可完成,他调用请求方法后所返回的返回值为Response对象,该对象可以通过readEntity()方法来获取特定类型的返回值供我们使用。

WebClient中的静态方法

  • create(String url):表示请求的服务端url地址
  • type(String type):指定请求的数据格式(xml、json)
  • accept(String type):指定接收响应的数据格式(xml、json)
  • post(请求参数):表示要带着该参数来发起post请求

注意:以上方法除了post()和get()等请求的方法返回值为Response类型,其他方法的返回值均为WebClient类型,所以可以实现链式调用

public class ClientTest {
    public static void main(String[] args) {
        User user = new User();
        user.setName("nana");
        user.setCity("广东");
        //通过WebClient对象远程调用服务端(post请求)
        Response response = WebClient.create("http://localhost:8001/userService/save").type("xml").accept("json").post(user);
        //读取response中请求体的内容,并获取特定类型的返回值
        String s = response.readEntity(String.class);
        System.out.println(s);
        //通过WebClient对象远程调用服务端(get请求)
        Response response1 = WebClient.create("http://localhost:8001/userService/get/lili").accept("xml").type("json").get();
        //读取response中请求体的内容,并获取特定类型的返回值
        User user1 = response1.readEntity(User.class);
        System.out.println(user1);
    }
}