详解如何使用Jersey客户端请求Spring Boot(RESTFul)服务

时间:2022-02-09 18:32:24

本文介绍了使用Jersey客户端请求Spring Boot(RESTFul)服务,分享给大家,具体如下:

Jersey客户端获取Client对象实例封装:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@Service("jerseyPoolingClient")
public class JerseyPoolingClientFactoryBean implements FactoryBean<Client>, InitializingBean, DisposableBean{
   
  /**
   * Client接口是REST客户端的基本接口,用于和REST服务器通信。Client被定义为一个重量级的对象,其内部管理着
   * 客户端通信底层的各种对象,比如连接器,解析器等。因此,不推荐在应用中产生大量的的Client实例,这一点在开发中
   * 需要特别小心,另外该接口要求其实例要有关闭连接的保障,否则会造成内存泄露
   */
  private Client client;
   
  /**
   * 一个Client最大的连接数,默认为2000
   */
  private int maxTotal = 2000;
   
  /**
   * 每路由的默认最大连接数
   */
  private int defaultMaxPerRoute = 1000;
   
  private ClientConfig clientConfig;
   
  public JerseyPoolingClientFactoryBean() {
  }
   
  /**
   * 带配置的构造函数
   * @param clientConfig
   */
  public JerseyPoolingClientFactoryBean(ClientConfig clientConfig) {
    this.clientConfig = clientConfig;
  }
 
  public JerseyPoolingClientFactoryBean(int maxTotal, int defaultMaxPerRoute) {
    this.maxTotal = maxTotal;
    this.defaultMaxPerRoute = defaultMaxPerRoute;
  }
 
  /**
   * attention:
   * Details:容器销毁时,释放Client资源
   * @author chhliu
   */
  @Override
  public void destroy() throws Exception {
    this.client.close();
  }
 
  /**
   *
   * attention:
   * Details:以连接池的形式,来初始化Client对象
   * @author chhliu
   */
  @Override
  public void afterPropertiesSet() throws Exception {
    // 如果没有使用带ClientConfig的构造函数,则该类的实例为null,则使用默认的配置初始化
    if(this.clientConfig == null){
      final ClientConfig clientConfig = new ClientConfig();
      // 连接池管理实例,该类是线程安全的,支持多并发操作
      PoolingHttpClientConnectionManager pcm = new PoolingHttpClientConnectionManager();
      pcm.setMaxTotal(this.maxTotal);
      pcm.setDefaultMaxPerRoute(this.defaultMaxPerRoute);
       
      clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, pcm);
      /*
       * 在使用Jersey来请求Spring Boot服务时,Spring Boot默认使用Jackson来解析JSON
       * 而Jersey默认使用MOXy解析JSON,当Jersey Client想Spring Boot服务请求资源时,
       * 这个差异会导致服务端和客户端对POJO的转换不同,造成反序列化的错误
       * 因此,此处需要在Client的Config实例中注册Jackson特性
       */
      clientConfig.register(JacksonFeature.class);
      // 使用配置Apache连接器,默认连接器为HttpUrlConnector
      clientConfig.connectorProvider(new ApacheConnectorProvider());
      client = ClientBuilder.newClient(clientConfig);
    }else{
      // 使用构造函数中的ClientConfig来初始化Client对象
      client = ClientBuilder.newClient(this.clientConfig);
    }
  }
 
  /**
   * attention:
   * Details:返回Client对象,如果该对象为null,则创建一个默认的Client
   * @author chhliu
   */
  @Override
  public Client getObject() throws Exception {
    if(null == this.client){
      return ClientBuilder.newClient();
    }
    return this.client;
  }
 
  /**
   * attention:
   * Details:获取Client对象的类型
   * @author chhliu
   */
  @Override
  public Class<?> getObjectType() {
    return (this.client == null ? Client.class : this.client.getClass());
  }
 
  /**
   * attention:
   * Details:Client对象是否为单例,默认为单例
   * @author chhliu
   */
  @Override
  public boolean isSingleton() {
    return true;
  }
}

请求Spring Boot服务的封装:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
@Component("jerseyClient")
public class JerseyClient {
   
  @Resource(name="jerseyPoolingClient")
  private Client client;
   
  /**
   * attention:
   * Details:通过id来查询对象
   * @author chhliu
   */
  public ResultMsg<GitHubEntity> getResponseById(final String id) throws JsonProcessingException, IOException{
    WebTarget webTarget = client.target("http://localhost:8080").path("/github/get/user/"+id);
    Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON);
    GenericType<ResultMsg<GitHubEntity>> genericType = new GenericType<ResultMsg<GitHubEntity>>(){};
    Response response = invocationBuilder.get();
    if(response.getStatus() == 200){
      /*
       * 当调用readEntity方法时,程序会自动的释放连接
       * 即使没有调用readEntity方法,直接返回泛型类型的对象,底层仍然会释放连接
       */
      return response.readEntity(genericType);
    }else{
      ResultMsg<GitHubEntity> res = new ResultMsg<GitHubEntity>();
      res.setErrorCode(String.valueOf(response.getStatus()));
      res.setErrorMsg(response.getStatusInfo().toString());
      res.setOK(false);
      return res;
    }
  }
   
  /**
   * attention:
   * Details:分页查询
   * @author chhliu
   */
  public ResultMsg<Pager<GitHubEntity>> getGithubWithPager(final Integer pageOffset, final Integer pageSize, final String orderColumn){
    WebTarget webTarget = client.target("http://localhost:8080").path("/github/get/users/page")
        .queryParam("pageOffset", pageOffset)
        .queryParam("pageSize", pageSize)
        .queryParam("orderColumn", orderColumn);
        // 注意,如果此处的媒体类型为MediaType.APPLICATION_JSON,那么对应的服务中的参数前需加上@RequestBody
        Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON);
    GenericType<ResultMsg<Pager<GitHubEntity>>> genericType = new GenericType<ResultMsg<Pager<GitHubEntity>>>(){};
    Response response = invocationBuilder.get();
    if(response.getStatus() == 200){
      return response.readEntity(genericType);
    }else{
      ResultMsg<Pager<GitHubEntity>> res = new ResultMsg<Pager<GitHubEntity>>();
      res.setErrorCode(String.valueOf(response.getStatus()));
      res.setErrorMsg(response.getStatusInfo().toString());
      res.setOK(false);
      return res;
    }
     
  }
   
  /**
   * attention:
   * Details:根据用户名来查询
   * @author chhliu
   */
  public ResultMsg<List<GitHubEntity>> getResponseByUsername(final String username) throws JsonProcessingException, IOException{
    WebTarget webTarget = client.target("http://localhost:8080").path("/github/get/users/"+username);
    Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON);
    GenericType<ResultMsg<List<GitHubEntity>>> genericType = new GenericType<ResultMsg<List<GitHubEntity>>>(){};
    Response response = invocationBuilder.get();
    if(response.getStatus() == 200){
      return response.readEntity(genericType);
    }else{
      ResultMsg<List<GitHubEntity>> res = new ResultMsg<List<GitHubEntity>>();
      res.setErrorCode(String.valueOf(response.getStatus()));
      res.setErrorMsg(response.getStatusInfo().toString());
      res.setOK(false);
      return res;
    }
  }
   
  /**
   * attention:
   * Details:根据id来删除一个记录
   * @author chhliu
   */
  public ResultMsg<GitHubEntity> deleteById(final String id) throws JsonProcessingException, IOException{
    WebTarget target = client.target("http://localhost:8080").path("/github/delete/"+id);
    GenericType<ResultMsg<GitHubEntity>> genericType = new GenericType<ResultMsg<GitHubEntity>>(){};
    Response response = target.request().delete();
    if(response.getStatus() == 200){
      return response.readEntity(genericType);
    }else{
      ResultMsg<GitHubEntity> res = new ResultMsg<GitHubEntity>();
      res.setErrorCode(String.valueOf(response.getStatus()));
      res.setErrorMsg(response.getStatusInfo().toString());
      res.setOK(false);
      return res;
    }
  }
   
  /**
   * attention:
   * Details:更新一条记录
   * @author chhliu
   */
  public ResultMsg<GitHubEntity> update(final GitHubEntity entity) throws JsonProcessingException, IOException{
    WebTarget target = client.target("http://localhost:8080").path("/github/put");
    GenericType<ResultMsg<GitHubEntity>> genericType = new GenericType<ResultMsg<GitHubEntity>>(){};
    Response response = target.request().buildPut(Entity.entity(entity, MediaType.APPLICATION_JSON)).invoke();
    if(response.getStatus() == 200){
      return response.readEntity(genericType);
    }else{
      ResultMsg<GitHubEntity> res = new ResultMsg<GitHubEntity>();
      res.setErrorCode(String.valueOf(response.getStatus()));
      res.setErrorMsg(response.getStatusInfo().toString());
      res.setOK(false);
      return res;
    }
  }
   
  /**
   * attention:
   * Details:插入一条记录
   * @author chhliu
   */
  public ResultMsg<GitHubEntity> save(final GitHubEntity entity) throws JsonProcessingException, IOException{ 
     WebTarget target = client.target("http://localhost:8080").path("/github/post");
     GenericType<ResultMsg<GitHubEntity>> genericType = new GenericType<ResultMsg<GitHubEntity>>(){};
     Response response = target.request().buildPost(Entity.entity(entity, MediaType.APPLICATION_JSON)).invoke();
     if(response.getStatus() == 200){
       return response.readEntity(genericType);
     }else{
      ResultMsg<GitHubEntity> res = new ResultMsg<GitHubEntity>();
      res.setErrorCode(String.valueOf(response.getStatus()));
      res.setErrorMsg(response.getStatusInfo().toString());
      res.setOK(false);
      return res;
     }
  }
}

Jersey客户端接口详解

1 Client接口

创建一个Client实例是通过ClientBuilder构造的,通常使用一个ClientConfig实例作为参数,如果我们使用Client client = ClientBuilder.newClient()的方式来创建Client实例的时候,每次都会创建一个Client实例,但该实例是一个重量级的对象,所以,建议使用HTTP连接池的方式来管理连接,而不是每次请求都去创建一个Client对象,具体的连接池管理方式见上面的代码示例。

2 WebTarget接口

WebTarget接口是为REST客户端实现资源定位的接口,通过WebTarget接口,我们可以定义请求资源的具体地址,查询参数和媒体类型信息等。我们可以通过方法链的方式完成对一个WebTarget实例的配置,但是需要注意的是,虽然WebTarget的使用方式和StringBuffer的方法链方式非常类似,但实质是不一样的,WebTarget的方法链必须设置方法的返回值,作为后续流程的句柄,这个是什么意思了,看下面的几个示例:

示例1:StringBuffer的方法链示例

?
1
2
3
4
StringBuffer sb = new StringBuffer("lch");
     sb.append("hello");
     sb.append("world");
     sb.append("hello").append("world"); // 这种方式和上面的两行代码实现的效果是一样的。

示例2:WebTarget的方法链示例

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用一行代码的方法链来实例化WebTarget
WebTarget webTarget = client.target("http://localhost:8080");
webTarget.path("/github/get/users/page")
  .queryParam("pageOffset", pageOffset)
  .queryParam("pageSize", pageSize)
  .queryParam("orderColumn", orderColumn);
// 下面是分开使用方法链来实例化WebTarget
webTarget.path("/github/get/users/page");
webTarget.queryParam("pageOffset", pageOffset);
webTarget.queryParam("pageSize", pageSize);
// 上面两种实例化的方式最后产生的结果大相径庭,上面的实例化方式是OK的,没有问题,下面的实例化方式却有问题,下面的实例化方式中,每一行都会生成一个
// 新的WebTarget对象,原来的WebTarget并没有起任何作用,毕竟每一行的实例都不一样,如果我们想要分多行实例化了,就必须为每个方法的返回提供一个句柄,方式如下:
 
WebTarget target = client.target("http://localhost:8080");
WebTarget pathTarget = target.path("/github/get/users/page");
WebTarget paramTarget = pathTarget.queryParam("pageOffset", pageOffset);
 
// 最后使用的时候,用最后一个WebTarget实例对象即可

3 Invocation接口

Invocation接口是在完成资源定位配置后,向REST服务端发起请求的接口,请求包括同步和异步两种方式,由Invocation接口内部的Builder接口定义,Builder接口继承了同步接口SyncInvoker,异步调用的使用示例如下:

?
1
2
3
4
5
Future<ResultMsg<List<GitHubEntity>>> response = invocationBuilder.async().get(genericType);
 
    if(response.isDone()){
      return response.get();
    }

Invocation.Builder接口实例分别执行了GET和POST请求来提交查询和创建,默认情况下,HTTP方法调用的返回类型是Response类型,同时也支持泛型类型的返回值,在上面的示例中,我们使用了大量的泛型,这里就不做过多的解释了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://blog.csdn.net/liuchuanhong1/article/details/53537874