Spring5.x+CXF3.x+Tomcat搭建REST服务

       如今REST风格的服务比较流行,调用起来也方便,受到广泛欢迎,我在网上找了一堆材料来试图搭建,过程中碰到很多问题,我没有找到使用Spring5.x来搭建的博客,可能是我碰到很多问题的重要原因之一,另外没有打框架日志也是一个重要原因。

       这次的搭建与上一篇搭建SOAP服务在同一个工程里,主要差别是引入的jar包、接口、服务发布配置不同,另外使用了数据库,建了1个简单的表,web.xml文件没有任何改动。

1.引入jar包,最好的方式仍然是放到lib目录下,这里除了上一篇的搭建SOAP服务引入的包之外就全都是搭建REST服务使用的,主要来源是Spring、CXF以及第三方的包,如jackson的,能够自动转换Object与json格式

CXF的REST支持:cxf-rt-frontend-jaxrs-{version}.jar

@GET、@POST、@Produces、@Consumes等注解的包:javax.ws.rs-api-{version}.jar

jackson的包支持自动转换对象与json:jackson-core-{version}.jar、jackson-annotations-{version}.jar、jackson-databind-{version}.jar、jackson-jaxrs-base-{version}.jar、jackson-jaxrs-json-provider-{version}.jar

2.web.xml文件(与搭建SOAP服务一样,没有任何修改)

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring.xml</param-value><!--  -->
  </context-param>  
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>cxf</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>cxf</servlet-name>
    <url-pattern>/ws/*</url-pattern>
  </servlet-mapping>

3.创建接口

这里给接口加了很多注解,@Consumes代表一个资源可以接受的MIME类型,@Produces代表一个资源可以返回的资源类型。这里的GET服务使用路径的方式访问,常见的还有在浏览器通过?后带参数的方式访问,此时把接口定义的路径和参数注解改一下,比如

package com.weijie.rest.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.core.MediaType;
import com.weijie.accounts.Account;

//

@Path("/public/account")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public interface IAccountService {
    @POST
    @Path("/fetch")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public String fetch(String uid);
    //public Account[] fetch(Account account);
    
    @POST
    @Path("/create")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Account create(Account account);
    
    @GET
    @Path("/hello/{name}")//使用路径的方式访问
    public String sayHello(@PathParam("name") String name);

    /*@GET
    @Path("/hello")//使用?后加参数的方式访问
    public String sayHello(@QueryParam("name") String name);//这里的注解和上面的不一样*/

}

这里仅提供了GET和POST请求,另有一个非自定义类型(String)的GET服务,作为基础已经够简单明了。

关于HTTP协议的几个动作解释如下,更详细的应该学习一下HTTP协议。

       GET(SELECT):从服务器取出资源(一项或多项)。

        POST(CREATE):在服务器新建一个资源。

        PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。

        PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。

        DELETE(DELETE):从服务器删除资源。

      举例如下:

        GET /zoos:列出所有动物园

        POST /zoos:新建一个动物园

        GET /zoos/ID:获取某个指定动物园的信息

        PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)

        PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)

        DELETE /zoos/ID:删除某个动物园

        GET /zoos/ID/animals:列出某个指定动物园的所有动物

        DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

4.实现接口

这里的DBHelper是自己实现的一个类,提供根据读取配置文件来获取数据库连接的方法,还提供一个方法传入驱动串和sql返回ResultSet。这里还没引入mybatis,所以sql是直接写的,另外找了个oracle,创建了1个表,测试了插入和查询数据。Gson是谷歌的一个json包,能够将Object转换为json格式的串,这里主要用于测试返回,后面主要在客户端使用。

package com.weijie.rest.impl;
import com.weijie.rest.api.IAccountService;
import com.weijie.accounts.Account;
import com.weijie.accounts.AccountUtils;
import com.weijie.DBHelper.DBHelper;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import org.apache.log4j.Logger;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class AccountService implements IAccountService{
    private Logger log = Logger.getLogger(AccountService.class);
    //private Logger log = Logger.getLogger("AccountService");
    public Account create(Account account)
    {
        if(AccountUtils.isEmpty(account)) return null;
        DBHelper helper = new DBHelper("iexcel_sit_url","iexcel_sit_user","iexcel_sit_pwd");
        String driver = "oracle.jdbc.driver.OracleDriver";
        String sql = "insert into weijie_account_t (id,userid,password,telephonenumber,email,created_by,create_date) "
                + "values(weijie_account_id_sequence_s.nextval,'"+account.getUid()+"','huawei','1008612','10086@140.com','test3',sysdate)";
        String sql1 = "select * from weijie_account_t where userid = '" + account.getUid() +"'";
        helper.executeSQL(driver, sql);
        ResultSet result = helper.executeSQL(driver,sql1);
        Account temp = resultSet2Account(result)[0];
        return temp ;
    }
    
    public String sayHello(String name)
    {
        
        return "Hello World " + name;
    }
    
    public String fetch(String uid)
    {
        Account account = new Account();
        account.setUid(uid);
        Account[] result = null;
        Gson gson = new GsonBuilder().create();
        
        result = fetch(account);
        
        String ss = gson.toJson(result[0]);
        System.out.println(result[0].getEmail());    
        return ss;
    }
    public Account[] fetch(Account account)
    {
        log.info("entry AccountService");
        Account[] result ;
        result = new Account[10];
        try{
            Class.forName("oracle.jdbc.driver.OracleDriver");
            Connection conn = DBHelper.getConnectionByProp("iexcel_sit_url", "iexcel_sit_user", "iexcel_sit_pwd");
            String sql = "select * from iexcel.weijie_account_t where userid =?";
            String userid = account.getUid();
            PreparedStatement pstmt = null;
            ResultSet resultSet = null;            
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, userid);
            resultSet = pstmt.executeQuery();
            if(resultSet != null)
            {
                int i = 0;
                Account temp = new Account();
                try{
                    while(resultSet.next())
                    {
                        
                        ResultSetMetaData metaData = resultSet.getMetaData();
                        String column4 = metaData.getColumnLabel(4);
                        temp.setTelephonenumber(resultSet.getString(column4));
                        temp.setEmail(resultSet.getString(metaData.getColumnLabel(5)));
                        result[i] = temp;
                        System.out.println(result[i].toString());
                        i++;
                    }
                }catch(SQLException e){
                    e.printStackTrace();
                }                            
            }
        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }catch(SQLException e){
            e.printStackTrace();
        }
        return result; 
    }
    public static Account[] resultSet2Account(ResultSet result)
    {
        if(result == null) return null;
        Account[] account;
        int num = 0;/*
        try{
            result.last();
            num = result.getRow();
            result.first();
        }catch(SQLException e){
            e.printStackTrace();
        }*/
        
        account = new Account[10];//num
        try{
            if(result !=null)
            {
                int i=0;
                ResultSetMetaData metaData = result.getMetaData();
                while(result.next())
                {
                    account[i] = new Account();
                    account[i].setUid(result.getString(2));
                    account[i].setTelephonenumber(result.getString(metaData.getColumnLabel(4)));
                    account[i].setEmail(result.getString(metaData.getColumnLabel(5)));
                }
            }
        }catch(SQLException e){
            e.printStackTrace();
        }
        return account;
    }

}
5.发布服务

spring.xml文件的配置,前面引入了REST相关的xmlns等,配置了REST服务的发布,主要差异在引入了CXF的输入输出日志拦截器,在本地测试时会打印出客户端请求的数据,以及服务端的返回数据,方便调试定位问题,在引入前根据网上的各种帖子尝试了很久都没有调通。其中com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider是Json格式自动转换的Provider,比较好用,CXF也有自带的,我在网上找到的大多数博客都是用的这个,推荐一个:http://www.cnblogs.com/zjm701/p/6845813.html写的非常好,但是与上一篇SOAP同样的问题,不需要引入CXF的配置文件,估计是使用较老版本的spring和CXF吧。

<?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:context="http://www.springframework.org/schema/context"
    xmlns:jaxws="http://cxf.apache.org/jaxws" 
    xmlns:jaxrs="http://cxf.apache.org/jaxrs"
    xsi:schemaLocation="
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd
      http://cxf.apache.org/jaxws
      http://cxf.apache.org/schemas/jaxws.xsd
      http://cxf.apache.org/jaxrs
      http://cxf.apache.org/schemas/jaxrs.xsd">  
  <!--  
  <bean id="jaxWsServiceFactoryBean" class="org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean">
       <property name="wrapped" value="true" />
  </bean>-->
  <!-- SOAP服务部分配置 -->
  <jaxws:endpoint id="IExcelExport" implementor="com.weijie.service.impl.ExcelExport" address="/IExcelExport">
    <!--     <jaxws:serviceFactory>
           <ref bean="jaxWsServiceFactoryBean" />
       </jaxws:serviceFactory>-->
   </jaxws:endpoint>
   
   <!-- REST服务部分配置 -->
   <bean id="loggingInInterceptor"  class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
   <bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
   <jaxrs:server address="/rest">
       <jaxrs:serviceBeans>
           <ref bean="IAccountService"/>
       </jaxrs:serviceBeans>
       <jaxrs:providers>
           <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider"/>
       </jaxrs:providers>
       <jaxrs:inInterceptors>
           <ref bean="loggingInInterceptor"/>
       </jaxrs:inInterceptors>
       <jaxrs:outInterceptors>
           <ref bean="loggingOutInterceptor"/>
       </jaxrs:outInterceptors>
   </jaxrs:server>
   <bean id="IAccountService" class="com.weijie.rest.impl.AccountService"></bean>  
  
</beans> 

此时配置了多个路径,那么服务的访问地址即是http://IP:port/项目名/CXF的Servlet地址/服务发布地址/接口配置的路径/方法名/参数

6.客户端访问

可以使用HttpClient或者HttpURLConnection来访问,如下示例使用HttpURLConnection来访问,先写一个通用的HttpURLConnection测试类,然后再传参将会很方便

package com.weijie.util;
import java.net.URL;
import java.net.HttpURLConnection;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;

public class HttpURLConnectionTest {
    private String serviceURL;
    public HttpURLConnectionTest(String serviceURL)
    {
        this.serviceURL = serviceURL;
    }
    
    public String postTest(String data)
    {
        return request("","POST",data);
    }
    
    public String request(String params, String method, String data)
    {
        HttpURLConnection conn = null;
        OutputStream os = null;
        try{
            URL url = new URL(serviceURL+params);
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod(method);
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setUseCaches(false);
            conn.setDoOutput(true);
            if(data !=null && data.trim() != "")
            {
                os = conn.getOutputStream();
                os.write(data.getBytes("UTF-8"));
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if(os !=null){
                try{
                    os.close();
                }catch(IOException e){
                    
                }
            }
        }
        InputStream is = null;
        try{
            is = conn.getInputStream();
        }catch(IOException e){
            System.err.println(e);
            is = conn.getErrorStream();
        }
        try{
            int code = conn.getResponseCode();
            System.out.println(code);
        }catch(IOException e){
            
        }
        StringBuilder sb = new StringBuilder();
        InputStreamReader isr = new InputStreamReader(is);//字节流转换为字符流
        BufferedReader br = new BufferedReader(isr);//BufferedReader需要父类型作为参数
        try{
            String read = br.readLine();//readLine()是BufferedReader独有的方法,
            while(read !=null)
            {
                sb.append(read);
                read = br.readLine();
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if(is !=null){
                try{
                    is.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }

}
传参并发送请求,这里使用了Gson将Account对象转换为json格式字符串再发送,非常方便。此时我们要回顾一下服务端的接口,明明是Account类型,而并非是字符串类型,却能够成功地调用并返回,说明JsonProvider生效了,如果传输的不是json格式,在服务端将会抛出异常,便能很方便调试。这里同样也能够看出客户端调用的服务地址的结构。

port com.weijie.accounts.Account;
import org.junit.Test;
import com.weijie.util.HttpClientTest;
import com.weijie.util.HttpURLConnectionTest;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class AccountServiceTest {
    public String fetch = "http://10.65.171.219:8080/MySecondWeb/ws/rest/public/account/fetch";
    public String create = "http://10.65.171.219:8080/MySecondWeb/ws/rest/public/account/create";
    
    
    @Test
    public void testFetch()
    {
        Account account = new Account();
        account.setUid("user01");
        String result =null;
        HttpClientTest clientTest = new HttpClientTest(fetch,account);
        //clientTest.post();
        HttpURLConnectionTest URLTest = new HttpURLConnectionTest(fetch);
        Gson gson = new GsonBuilder().create();
        result = URLTest.postTest("user01");//gson.toJson(account)
        System.out.println(result);
    }
    
    @Test
    public void testCreate()
    {
        Account account = new Account();
        account.setUid("user04");
        account.setEmail("10086@141.com");
        String result =null;
        /*HttpClientTest clientTest = new HttpClientTest(create,account);
        clientTest.post();*/
        HttpURLConnectionTest URLTest = new HttpURLConnectionTest(create);
        Gson gson = new GsonBuilder().create();
        result = URLTest.postTest(gson.toJson(account));//gson.toJson(account)
        System.out.println(result);
    }

}

路径方式访问GET请求

?后带参数的GET请求访问


由此REST服务搭建的神秘面纱已经被揭下,REST服务的客户端比起SOAP服务要更加的简单,后续会进一步的优化,使过程更加通俗易懂,同时附上解释。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值