在上一篇笔记-注册中心中,服务sample只是简单实现了eureka客户端,现在为它添加一个controller

1
2
3
4
5
6
7
8
9
10
11
@Controller
@RequestMapping("/")
public class TestController {
@ResponseBody
@RequestMapping("param")
public Object test(HttpServletRequest request){
Map params = HttpServletUtil.getRequestParameters(request);
params.put("msg","请求成功");
return params;
}
}

这个接口很简单,获取请求参数,在参数Map中加一个msg,然后再原样返回给请求方。

通常来说,请求方可以直接通过ip:port/param?key=value的形式访问sample,但是这样做就跟传统的web项目没区别了,注册中心也就失去了它本来的意义。

在spring cloud中,服务的请求有两种方式,一种是ribbon+restTemplate,另一种是feign,下面分别介绍下两种方式的使用方式。

比如存在另一个服务service1,需要在sample服务中调用service1服务,两种实现方式如下

继续在sample工程中添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

ribbon+restTemplate

使用ribbon和断路器时需要在主类上添加@EnableHystrix注解。

创建restTemplate的bean

1
2
3
4
5
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}

创建一个bean,封装一些访问的方法

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
@Bean
public class RibbonClient{
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "callError")
public String postForObject(String serviceId, String methodMapping, Object params) {
return restTemplate.postForObject("http://"+serviceId+"/"+methodMapping,params,String.class);
}
@HystrixCommand(fallbackMethod = "callError")
public String postForObject(String serviceId, String controllerMapping, String methodMapping, Object params) {
return restTemplate.postForObject("http://"+serviceId+"/"+controllerMapping+"/"+methodMapping,params,String.class);
}
@HystrixCommand(fallbackMethod = "callError")
public String getForObject(String serviceId, String methodMapping, String params) {
return restTemplate.getForObject("http://"+serviceId+"/"+methodMapping+"?"+params,String.class);
}
@HystrixCommand(fallbackMethod = "callError")
public String getForObject(String serviceId, String controllerMapping, String methodMapping, String params) {
return restTemplate.getForObject("http://"+serviceId+"/"+controllerMapping+"/"+methodMapping+"?"+params,String.class);
}
public String callError(String serviceId, String methodMapping, Object params){
return "调用服务"+serviceId+"发生错误,服务可能已下线,请稍后再试";
}
public String callError(String serviceId, String controllerMapping, String methodMapping, Object params){
return "调用服务"+serviceId+"发生错误,服务可能已下线,请稍后再试";
}
public String callError(String serviceId, String methodMapping, String params){
return "调用服务"+serviceId+"发生错误,服务可能已下线,请稍后再试";
}
public String callError(String serviceId, String controllerMapping, String methodMapping, String params){
return "调用服务"+serviceId+"发生错误,服务可能已下线,请稍后再试";
}
}

上面代码中分别封装了两个POST方法和两个GET方法,使用restTemplate的getForObject或postForObject方法发送get或post请求,这里直接使用serviceId代替具体的url地址,在ribbon中它会根据服务名来选择具体的服务实例,在请求时使用具体的url替换掉服务名。

方法上面的注解@HystrixCommand(fallbackMethod = "callError")就是断路器,在请求的服务不可用时可以快速返回,避免因为单个服务出现问题,调用者出现线程阻塞。在上面代码中,如果被调用服务不可用,则调用者会回退调用指定的CallError方法。

restTemplate.postForObject方法的第二个参数是服务的请求入参,方法上指定的类型是Object,实际上最好是HttpEntity对象,一个构建HttpEntity方法示例如下,实际可根据业务需求修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected HttpEntity buildPostParam(Object params,HttpServletRequest request){
HttpHeaders headers = new HttpHeaders();
String contentType = request.getContentType();
if(StringUtils.isBlank(contentType)){
contentType = "text/plain;charset=UTF-8";
}
headers.setContentType(MediaType.parseMediaType(contentType));
HttpEntity<String> formEntity = null;
if(params instanceof String){
formEntity = new HttpEntity<String>((String)params, headers);
}else{
formEntity = new HttpEntity<String>(JSONUtil.toJSONString(params), headers);
}
return formEntity;
}

feign

feign是一个声明式的伪Http客户端,采用基于接口的注解,默认集成了ribbon并和eureka结合,实现了客户端负载均衡。

需要在主类添加@EnableFeignClients注解。

创建一个接口

1
2
3
4
5
@FeignClient(value = "service1",,fallback = TestClientHiHystric.class)
public interface TestClient {
@RequestMapping(value = "test/param",method = RequestMethod.POST)
public String callTest(@RequestParam(value = "name") String value);
}

实现断路器,需要修改application.yml配置文件,实现TestClient接口并注入到Ioc容器中

1
2
3
feign:
hystrix:
enabled: true

1
2
3
4
5
6
7
@Component
public class TestClientHiHystric implements TestClient {
@Override
public String callTest(String value) {
return "sorry "+name;
}
}

通过@FeignClient注解的value值指定了该接口调用的是service1服务,fallback值指定了断路器,方法上的注解指定了调用服务service1上的test/param地址,使用POst方式。方法入参指定了参数名和值。方法的返回类型指定了调用该服务接口的返回类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
@RequestMapping("test")
public class ClientTestController {
@Autowired
TestClient testClient;
@ResponseBody
@RequestMapping("test")
public Object hi(){
String response = testClient.callTest("zhagnsan");
return response;
}
}

上面例子中在sample服务的一个controller中调用另外一个服务service1的test/param接口,参数是name=zhagnsan。

对于服务sample来说,并不知道服务service1的具体地址,也不知道到底有几个service1的实例在运行,仅仅需要指定需要调用的服务名就行,feign自己会从eureka注册中心获取服务service1的url信息完成调用。