国内最全IT社区平台 联系我们 | 收藏本站
华晨云阿里云优惠2
您当前位置:首页 > php开源 > php教程 > 框架 day64 WebService(注解),CXF框架(jax-ws,Jax-rs,与spring整合)

框架 day64 WebService(注解),CXF框架(jax-ws,Jax-rs,与spring整合)

来源:程序员人生   发布时间:2016-08-03 08:54:14 阅读次数:2617次

WebService第2天

 

1     webservice的注解


1.1   案例:

摹拟查询天气信息,返回3天的天气情况。

 

1.1.1       实现步骤

服务端:

第1步:创建1个天气信息pojo,包括天气的信息、最高、最低温度、日期。

第2步:编写SEI

第3步:编写SEI实现类,返回天气list

第4步:发布Webservice。

 

1.1.2       代码实现

1.1.2.1    Pojo

public class WeatherModel { private String info; private int maxTemp; private int minTemp; private Date date;

 

1.1.2.2    SEI

天气查询SEI,返回3天的天气情况

@WebService public interface WeatherInterface { List<WeatherModel> queryWeather(String cityName); }


1.1.2.3    SEI实现类

实现类使用@WebService实现wsdl文档元素名称的修改

@WebService( //endpointInterface="com.itheima.weather.service.WeatherInterface" //可以指定SEI接口 name="WeatherInterface", //不使用SEI接口时规范portType的名称 serviceName="WeatherService", //服务视图的名称 portName="WeatherPort", //Service节点中port节点的name属性 targetNamespace="http://weather.itheima.com/" //wsdl的命名空间 ) public class WeatherInterfaceImpl implements WeatherInterface { @Override @WebMethod/*(exclude=true)*/ @WebResult(name="WeatherInfo") public List<WeatherModel> queryWeather(@WebParam(name="cityName")String cityName) { System.out.println("客户端发送的城市:" + cityName); //查询天气信息 List<WeatherModel> info = getWeatherInfo(cityName); //返回天气信息 return info; } private List<WeatherModel> getWeatherInfo(String cityName) { List<WeatherModel> weatherList = new ArrayList<>(); Calendar calendar = Calendar.getInstance(); //第1天 WeatherModel model1 = new WeatherModel(); model1.setInfo("雷阵雨"); model1.setMaxTemp(31); model1.setMinTemp(22); model1.setDate(calendar.getTime()); weatherList.add(model1); //第2天 WeatherModel model2 = new WeatherModel(); model2.setInfo("多云"); model2.setMaxTemp(33); model2.setMinTemp(25); calendar.set(Calendar.DATE, calendar.get(Calendar.DATE) + 1); model2.setDate(calendar.getTime()); weatherList.add(model2); //第3天 WeatherModel model3 = new WeatherModel(); model3.setInfo("多云"); model3.setMaxTemp(35); model3.setMinTemp(25); calendar.set(Calendar.DATE, calendar.get(Calendar.DATE) + 1); model3.setDate(calendar.getTime()); weatherList.add(model3); return weatherList; } }


1.1.2.4    客户端

public class WeatherClient { public static void main(String[] args) throws Exception { //创建服务视图 Service service = Service.create(new URL("http://127.0.0.1:12345/weather"), new QName("http://service.weather.itheima.com/", "WeatherInterfaceImplService")); //从服务视图取得protType对象 WeatherInterfaceImpl weatherInterfaceImpl = service.getPort(WeatherInterfaceImpl.class); //调用服务端方法 List<WeatherModel> list = weatherInterfaceImpl.queryWeather("北京"); //显示天气信息 for (WeatherModel weatherModel : list) { System.out.println(weatherModel.getDate().toGregorianCalendar().getTime().toLocaleString()); System.out.println(weatherModel.getInfo()); System.out.println(weatherModel.getMaxTemp()); System.out.println(weatherModel.getMinTemp()); } } }


 

1.2   要规范wsdl需要使用到webservice注解


1.2.1       @Webservice


@WebService(

              //endpointInterface="com.itheima.weather.service.WeatherInterface"//可以指定SEI接口

              name="WeatherInterface",//不使用SEI接口时规范portType的名称

              serviceName="WeatherService",   //服务视图的名称

              portName="WeatherPort",                   //Service节点中port节点的name属性

              targetNamespace="http://weather.itheima.com/"   //wsdl的命名空间

                     )

 

 

1.2.2       @WebMethod

如果不指定@WebMethod注解默许是吧实现类中所有的public方法都发布成服务方法。

如果类中有的public方法不想发布成服务,就能够使用@WebMethod(exclude=true)把此方法排除,也就是不发布为webservice方法。

 

注意:每一个Porttype中,必须有1个public方法并且不能标注为@WebMethod(exclude=true)

 

1.2.3       @WebParam、@WebResult

可以规范参数的名称

@WebResult(name="xxxx")修改返回值的元素的父标签名字

@WebParam(name="xxxxx")修改传入参数的元素的父标签名字



 

2     CXF框架


2.1   甚么是CXF

Apache CXF = Celtix + Xfire,开始叫 Apache CeltiXfire,后来更名为 Apache CXF 了,以下简称为 CXF。Apache CXF 

是1个开源的 web Services 框架,CXF 帮助您构建和开发 web Services ,它支持多种协议,比如:SOAP1.1,1,2、XML/HTTP、RESTful  或 CORBA。

RESTful:1种风格而不是1个协议。它理念是网络上的所有事物都被抽象为资源,每一个资源对应1个唯1的资源标识符。

CORBA(Common Object Request Broker Architecture公共对象要求代理体系结构,初期语言使用的WS。C,c++,C#)

Cxf是基于SOA总线结构,依托spring完成模块的集成,实现SOA方式。

灵活的部署:可以运行在Tomcat,Jboss,Jetty(内置),weblogic上面。

 

2.2   CXF的安装及配置

从官网下载:cxf.apache.org

学习使用的版本是:3.0.2

 

使用的方法,直接把cxf的jar包添加到工程中就能够了。

 

环境配置

JAVA_HOME 需要jdk的支持

CXF_HOME  解压目录(bin的上层目录) 需要使用bin目录的可履行命令生成客户端代码

 

path = %JAVA_HOME%\bin;%CXF_HOME%\bin;

 

 

 

2.3   使用CXF实现java-ws规范的webservice

Soap1.1:

2.3.1       服务端

实现步骤:

第1步:创建1java工程。

第2步:导入jar包导入cxf的jar包共138个。

第3步:编写SEI,在SEI上添加@Webservice注解。

第3步:编写SEI实现类。需要实现SEI接口,可以不加@Webservice注解

第4步:发布服务。

1、创建1个JaxWsServerFactoryBean对象。

2、设置服务的发布地址,是1个http url

3、设置SEI接口

4、设置SEI实现类对象

5、调用create方法发布服务。

2.3.2       代码实现

2.3.2.1    SEI

@WebService public interface WeatherInterface { String queryWeather(String cityName); }


2.3.2.2    实现类

public class WeatherInterfaceImpl implements WeatherInterface { @Override public String queryWeather(String cityName) { System.out.println("城市名称:" + cityName); String result = "多云"; return result; } }


2.3.3       发布服务


服务发布类:后面整合spring依照此步骤配置便可

public class WeatherServer { public static void main(String[] args) { //创建1个JaxWsServerFactoryBean对象 JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean(); //设置服务发布的地址 factoryBean.setAddress("http://127.0.0.1:12345/weather"); //设置SEI factoryBean.setServiceClass(WeatherInterface.class); //设置实现类对象 factoryBean.setServiceBean(new WeatherInterfaceImpl()); //发布服务 factoryBean.create(); } }


 

2.4   客户端

2.4.1       Wsdl2java

可使用wsimport生成客户端调用代码,也能够使用CXF自带的工具生成。Wsdl2java。

可以实现wsimport一样的功能,两个工具生成的代码都是1样,wsdl2java工具的版本高。Wsimport对soap1.2支持的不好。

它包括以下参数:

a) -d参数,指定代码生成的目录。

b) -p参数,指定生成的新的包结构。

例:

在命令行履行

wsdl2java –d . -p cn.test.cxftext http://127.0.0.1:6666/helloworld?wsdl


 

2.4.2       客户真个代码实现

2.4.2.1    实现步骤


1、可以直接使用生成的代码调用服务端方法。

2、使用CXF提供的工厂类调用。

第1步:创建JaxWsProxyFactoryBean对象

第2步:设置服务的url,服务端地址

第3步:设置SEI(portType)类型

第4步:调用Create方法取得portType对象。

第5步:调用服务端方法。

 

2.4.2.2    代码实现


使用CXF工厂调用webservice,后面与spring整合配置可以用来参考

public class WeatherClient { public static void main(String[] args) { //创建1个JaxWsProxyFactoryBean对象 JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean(); //设置服务的url factoryBean.setAddress("http://127.0.0.1:12345/weather?wsdl"); //设置SEI的类型 factoryBean.setServiceClass(WeatherInterface.class); //取得porttype对象 WeatherInterface portType = (WeatherInterface) factoryBean.create(); //调用服务端方法 String result = portType.queryWeather("北京"); System.out.println(result); } }


2.5   Jax-rs规范的webservice


2.5.1       甚么是rest服务


1句话解释:URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描写操作。

简单来说,就是可以用httprequest 调用某个function. 比如在阅读器里输入www.chx.site/api/guesswhoisawesome,就会调用后台的某个function得到1个response(可以是Json).


REST 是1种软件架构风格,设计风格而不是标准,只是提供了1组设计原则和束缚条件。

它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更容易于实现缓存等机制。

rest服务采取HTTP 做传输协议,REST 对HTTP 的利用分为以下两种:资源定位资源操作

 

关于JAX-WS与JAX-RS:

二者是不同风格的SOA(面向服务的体系结构)架构。

前者以动词为中心,指定的是每次履行函数。

而后者以名词为中心,每次履行的时候指的是资源。

 

    资源定位

Rest要求对资源定位更加准确,以下:

非rest方式:http://ip:port/queryUser.action?userType=student&id=001

Rest方式:http://ip:port/user/student/001

Rest方式表示互联网上的资源更加准确,但是也有缺点,可能目录的层级较多不容易理解。


    资源操作

利用HTTP 的GET、POST、PUT、DELETE 4种操作来表示数据库操作的SELECT、UPDATE、INSERT、DELETE 操作。

比如:

查询学生方法:

设置Http的要求方法为GET,url以下:

http://ip:port/user/student/001

添加学生方法:

设置http的要求方法为PUT,url以下:

http://ip:port/user/student/001/张3/......

 

Rest经常使用于资源定位,资源操作方式较少使用。

REST 是1种软件架构理念,现在被移植到Web 服务上,那末在开发Web 服务上,

偏于面向资源的服务适用于REST,REST 简单易用,效力高,

SOAP 成熟度较高,安全性较好。


注意:REST 不是WebService,JAX-RS 只是将REST 设计风格利用到Web 服务开发上。

 

代码实现


2.5.2       服务端

2.5.2.1    实现步骤

第1步:创建1个pojo,返回值的类型。需要在pojo上添加@XmlRootElement。

第2步:创建1个SEI。也就是1个接口。需要用到的注解

1、@Path:标注要求url

2、@GET、@POST、@PUT、@DELETE:标注操作的方法

3、@Produce:指定返回结果的数据类型,xml或json等。

第3步:创建SEI的实现类。可以不使用任何注解。

第4步:发布rest服务。

1、使用JAXRsServerFactoryBean对象发服务。

2、设置服务发布的地址。设置url

3、设置SEI实现类对象。

4、发布服务,create方法。

2.5.2.2    代码实现

2.5.2.2.1 Pojo

返回值pojo类需要添加1个@XmlRootElement注解

@XmlRootElement public class WeatherModel { private String info; private int maxTemp; private int minTemp; private Date date;

 

2.5.2.2.2 SEI

rest服务SEI

//窄化要求映照 @Path("/weather") public interface WeatherInterface { //方法的要求路径{}中的内容就是参数,需要对应的使用@PathParam注解取参数 @Path("/city/{cityName}") //要求的方法 @GET //返回结果的数据类型,可以是xml也能够是json @Produces({MediaType.APPLICATION_JSON+";charset=utf⑻", MediaType.APPLICATION_XML}) List<WeatherModel> queryWeather(@PathParam(value="cityName")String cityName); }

 

2.5.2.2.3 SEI实现类

public class WeatherInterfaceImpl implements WeatherInterface{ @Override public List<WeatherModel> queryWeather(String cityName) { List<WeatherModel> weatherInfo = getWeatherInfo(cityName); return weatherInfo; } ...


 

2.5.2.2.4 发布服务

使用履行命令生成相应的代码后

public class WeatherServer { public static void main(String[] args) { //创建1个JAXRSServerFactoryBean对象 JAXRSServerFactoryBean factoryBean = new JAXRSServerFactoryBean(); //设置服务发布的地址 factoryBean.setAddress("http://127.0.0.1:12345/rest"); //设置实现类对象 factoryBean.setServiceBean(new WeatherInterfaceImpl()); //发布服务 factoryBean.create(); } }


 

2.5.3       查看结果

http://127.0.0.1:12345/rest/weather/city/上海

 


2.5.4       如果是多个参数可使用

 

 

2.5.5       结果数据格式

2.5.5.1    Json

@Produces({MediaType.APPLICATION_JSON+";charset=utf⑻", MediaType.APPLICATION_XML})

其中charset=utf⑻ 为了避免出现乱码


2.5.6       同1个方法返回两种数据类型

支持xml或json数据格式

MediaType.APPLICATION_JSON

MediaType.APPLICATION_XML

默许返回xml格式的数据

 

如果想访问json格式的数据需要加参数:?_type=json

http://127.0.0.1:12345/rest/weather/city/%E4%B8%8A%E6%B5%B7?_type=json

 

2.5.7       客户端

可使用ajax要求json数据。还可使用HttpClient访问数据。需要自己转换成java对象。

 


 

3     CXF整合spring


3.1   整合的思路

Spring就是1个容器,服务端让spring容器实现服务的发布。客户端使用spring容器完成取得porttype的进程,直接从spring容器中取得1个porttype对象。

 

3.2   案例需求

服务端发布手机号查询服务供客户端调用,服务端配置调用公网手机号查询服务客户端,调用公网WebService查询手机号。

 

3.3   分析

 

  

 

3.4   实现步骤

服务端:有两个身份,1个是调用公网webservice的客户端。1个是对外提供手机号查询服务的服务端。

 

第1步:实现调用公网webservice的客户端

1、根据公网webservice的wsdl生成客户端调用代码

2、配置spring容器,完成创建服务视图取得porttype的进程。直接从spring容器中取得prottype对象。

3、直接调用porttype对象的服务端方法,查询手机号。

 

第2步:对外发布服务,实现手机号查询。

1、编写1个SEI接口。需要在SEI上添加@Webservice注解。

2、编写1个SEI实现类,调用公网webservice的porttype实现手机号查询。

3、发布服务,使用spring容器完成。

 

3.5   开发环境的搭建

第1步:创建1个Web工程

第2步:导入jar包。需要spring的jar包和cxf的jar包

 

3.6   代码实现

3.6.1       服务端

3.6.1.1    生成调用公网webservice的客户端调用代码 

3.6.1.2    在spring容器中配置porttype


applicationContext.xml

<?xml version="1.0" encoding="UTF⑻"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:cxf="http://cxf.apache.org/core" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd"> <!-- 配置调用公网webservice的客户端 --> <!-- 相当于1个普通的bean,可以当作对象使用 --> <jaxws:client id="mobileCodeWSSoap" address="http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx" serviceClass="cn.com.webxml.MobileCodeWSSoap"/> <!-- 把SEI的实现类配置到spring容器 --> <bean id="mobileInterfaceImpl" class="com.itheima.mobile.service.MobileInterfaceImpl"> <property name="mobileCodeWSSoap" ref="mobileCodeWSSoap"></property> </bean> </beans>

NoClassDefFoundError: org/apache/cxf/logging/FaultListener
解决方法:添加FaultListener。
<cxf:bus> <cxf:properties> <entry key="org.apache.cxf.logging.FaultListener"> <!-- 这个类可以换成自己的,如果不换就使用cxf中实现过上述接口的类 --> <bean class="org.apache.cxf.logging.NoOpFaultListener"/> </entry> </cxf:properties> </cxf:bus>

需要的xmlns和xsd
xmlns:cxf="http://cxf.apache.org/core http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd



3.6.1.3    SEI

调用公网webservice查询手机号的SEI

@WebService public interface MobileInterface { Mobile queryMobile(String code); }


 

3.6.1.4    SEI实现类

手机号归属地查询服务

public class MobileInterfaceImpl implements MobileInterface { //公网webservice的porttype配置注入 private MobileCodeWSSoap mobileCodeWSSoap; public void setMobileCodeWSSoap(MobileCodeWSSoap mobileCodeWSSoap) { this.mobileCodeWSSoap = mobileCodeWSSoap; } @Override public Mobile queryMobile(String code) { //第1个参数:手机号 //第2个参数:免费用户是空串 String info = mobileCodeWSSoap.getMobileCodeInfo(code, ""); Mobile mobile = new Mobile(); mobile.setAddress(info); mobile.setCode(code); return mobile; } }


 

3.6.1.5    发布服务

 

3.6.1.6    Web.xml

配置spring容器,配置cxf使用servlet。

web.xml

<?xml version="1.0" encoding="UTF⑻"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>B06_spring_cxf</display-name> <!-- 配置spring容器 --> <!-- 使用spring来加载cxf的服务类,服务类的对象由spring来创建,服务类的对象存在springIoc的容器中 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- cxf的配置 --> <!-- servlet负责发布服务类 --> <servlet> <servlet-name>cxf</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> <!--设置随着服务器启动而载servlet,不设置访问时才会加载显示数据慢 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>cxf</servlet-name> <url-pattern>/ws/*</url-pattern> </servlet-mapping> </web-app>

applicationContext.xml发布自己的服务

<?xml version="1.0" encoding="UTF⑻"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:cxf="http://cxf.apache.org/core" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd"> <!-- 配置调用公网webservice的客户端 --> <!-- 相当于1个普通的bean,可以当作对象使用 --> <jaxws:client id="mobileCodeWSSoap" address="http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx" serviceClass="cn.com.webxml.MobileCodeWSSoap"/> <!-- 把SEI的实现类配置到spring容器 --> <bean id="mobileInterfaceImpl" class="com.itheima.mobile.service.MobileInterfaceImpl"> <property name="mobileCodeWSSoap" ref="mobileCodeWSSoap"></property> </bean> <!-- 发布服务 --> <!-- address就是服务发布的地址,只是从1个目录开始 --> <!-- serviceClass是发布服务的sei --> <jaxws:server address="/mobile" serviceClass="com.itheima.mobile.service.MobileInterface"> <jaxws:serviceBean> <ref bean="mobileInterfaceImpl"/> </jaxws:serviceBean> </jaxws:server> <!-- rest服务的实现类 --> <bean id="mobileRestImpl" class="com.itheima.mobile.rest.MobileRestImpl"> <property name="mobileCodeWSSoap" ref="mobileCodeWSSoap"></property> </bean> <!-- 发布rest服务 --> <jaxrs:server address="/rest"> <jaxrs:serviceBeans> <ref bean="mobileRestImpl"/> </jaxrs:serviceBeans> </jaxrs:server> </beans>




 

3.6.1.7    访问路径

http://localhost:8081/B06_spring_cxf/ws/mobile?wsdl

 


 

3.6.2       客户端

步骤:

1、生成客户端调用代码

2、创建服务视图

3、取得porttype

4、调用服务端方法


3.7   Spring整合cxf发布rest服务


3.7.1       实现步骤

第1步:调用公网webservice查询手机号,需要公网webservice的porttype。

第2步:发布rest服务

1、先写1个pojo,需要加上@xmlrootElement注解。

2、编写1个SEI,需要添加@path、@GET、@Produces

3、编写1个SEI实现类,需要调用公网的webservice查询手机号。

4、发布服务,使用spring容器发布服务。

 

3.7.2       代码实现

3.7.2.1    SEI

rest风格的服务

@Path("/mobile") public interface MobileRest { @Path("/code/{code}") @GET @Produces({MediaType.APPLICATION_JSON+";charset=utf⑻", MediaType.APPLICATION_XML}) Mobile queryMobile(@PathParam(value = "code")String code); }


3.7.2.2    SEI实现类

public class MobileRestImpl implements MobileRest { //公网webservice的porttype private MobileCodeWSSoap mobileCodeWSSoap; public void setMobileCodeWSSoap(MobileCodeWSSoap mobileCodeWSSoap) { this.mobileCodeWSSoap = mobileCodeWSSoap; } @Override public Mobile queryMobile(String code) { String address = mobileCodeWSSoap.getMobileCodeInfo(code, ""); Mobile mobile = new Mobile(); mobile.setAddress(address); mobile.setCode(code); return mobile; } }


 

3.7.2.3    发布服务

<!-- rest服务的实现类 --> <bean id="mobileRestImpl" class="com.itheima.mobile.rest.MobileRestImpl"> <property name="mobileCodeWSSoap" ref="mobileCodeWSSoap"></property> </bean> <!-- 发布rest服务 --> <jaxrs:server address="/rest"> <jaxrs:serviceBeans> <ref bean="mobileRestImpl"/> </jaxrs:serviceBeans> </jaxrs:server>


 

3.7.2.4    Url的构成


 


使用jquery调用cxf(ws)

$(function(){ $("#mybutton").click(function(){ var data = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:q0="http://server.web.cxf.rl.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +'<soapenv:Body>' +'<q0:sayHello>' +' <arg0>sss</arg0>' +' </q0:sayHello>' +'</soapenv:Body>' +'</soapenv:Envelope>'; $.ajax({ url:'http://localhost:8080/cxf-web-server/services/hello', type:'post', dataType:'xml', contentType:'text/xml;charset=UTF⑻', data:data, success:function(responseText){ alert($(responseText).find('return').text()); }, error:function(){ alert("error"); } }) }) })




 

生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠
程序员人生
------分隔线----------------------------
分享到:
------分隔线----------------------------
关闭
程序员人生