Feign 简介

是什么

  • Feign是一个声明式的Web Service客户端,它的出现让微服务之间的调用变得更简单了

  • SpringCloudFeignNetfix Feign的基础上扩展了对SpringMVC的注解支持,所以通过这些注解可以很方便的定义一些服务接口,服务调服务通过接口来调用十分方便,同时SpringCloudFeign整合了Spring Cloud RibbonSpring Cloud Hystrix,具有负载均衡及服务容错保护功能

为什么要使用

  • 只需创建一个接口并用注解的方式来配置它,即可完成服务提供方的接口绑定
  • 在使用过程中与Spring MVC完美衔接
  • 整合了Spring Cloud Ribbon,可实现负载均衡,实现服务高可用
  • 整合了Spring Cloud Hystrix,可实现服务断路及服务降级

简单例子

  • 下面通过一个简单示例来展示SpringCloudFeign在服务客户端定义上所带来的便利

1、pom.xml 添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

2、启动类添加开关注解 @EnableFeignClients

1
2
3
4
5
6
7
8
9
10
@EnableFeignClients
@EnableZuulProxy
@SpringCloudApplication
public class ZuulApplication {

public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}

}

3、新增cloudSso服务接口类,下面可以看到使用了Spring MVC的注解,@FeignClient(value = "cloudSso")value是具体调用服务的服务名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@FeignClient(value = "cloudSso")
public interface SsoClient {

@PostMapping("/sso/createToken")
ResponseMO createToken();

@PostMapping("/sso/checkToken")
ResponseMO checkToken(@RequestBody TokenMO tokenMO);

}

// 也可以这么使用,通过url来访问接口
@FeignClient(value = "cloudOther", url = "https://www.baidu.com")
public interface BaiduClient {

@PostMapping("/baidu")
ResponseMO baidu();

}

SsoController.java 是cloudSso服务下的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@RequestMapping("/sso")
public class SsoController extends BaseController {

@PostMapping("/createToken")
public ResponseMO createToken() {
return ResponseUtil.successWithData("EADF89QWJ0IFJWEJFQHWEFU9QEWH9FH0Q9EW");
}

@PostMapping("/checkToken")
public ResponseMO checkToken(@RequestBody TokenMO tokenMO) {
return ResponseUtil.successWithData(tokenMO);
}
}

4、有了上面的准备之后就可以使用SsoClient的接口了,前面学习Zuul的文章有介绍,如果要对网关服务进行鉴权校验,我们这里添加了一个鉴权Filter,通过调用SsoClient接口的checkToken()方法来校验Token是否有效

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
@Slf4j
@Component
public class AuthenticationFilter extends ZuulFilter {

@Autowired
private SsoClient ssoClient;

private Pattern p = Pattern.compile("/*/pub/*");

@Override
public Object run() {
ResponseMO resMO = new ResponseMO();
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();

String relativeURL = extractRelativePath(request);
String token = request.getHeader(WebConstants.TOKEN_HEADER);
if (p.matcher(relativeURL).find()) {
return null;
}
log.info(">> 鉴权开始[{}]",relativeURL);
ResponseMO resModel = null;

if (relativeURL.startsWith(ApplicationConstants.APPLICATION_ZUUL) ||
relativeURL.startsWith(ApplicationConstants.APPLICATION_USER) ||
relativeURL.startsWith(ApplicationConstants.APPLICATION_SSO)) {
// 调用sso服务鉴权
resModel = ssoClient.checkToken(new TokenMO(token));
} else {
// 其他服务不对其进行路由
authorizationFailed(relativeURL, ctx, resMO);
return null;
}
if (resModel.getCode() != ResponseMO.RESPONSE_CODE_SUCCESS) {
// 鉴权失败不对其进行路由
authorizationFailed(relativeURL, ctx, resMO);
return null;
}
// 从jwt解析后的userId
ctx.addZuulRequestHeader("userId", "reUserId");
log.info("<< 鉴权通过[{}]] ", relativeURL);
return null;
}

/**
* 返回一个boolean值来判断该过滤器是否要执行,true表示执行,false表示不执行
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}

/**
* gives the order in which this filter will be executed, relative to other
* filters
* @return
*/
@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 0;
}

@Override
public String filterType() {
return "pre";
}

/**
* 鉴权失败
* @param relativeURL
* @param ctx
* @param resMO
*/
private void authorizationFailed (String relativeURL, RequestContext ctx, ResponseMO resMO) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
resMO.setAnonymous();
String resBody = convertToString(resMO);
ctx.setResponseBody(resBody);
log.info("<< 鉴权失败[{}]",relativeURL);
}

private String convertToString(ResponseMO resMO) {
String result = "";
ObjectMapper mapper = new ObjectMapper();
try {
result = mapper.writeValueAsString(resMO);
} catch (JsonProcessingException e) {
log.error(e.getMessage());
}
return result;
}

/**
* 获取相对访问路径
* @param request
* @return
*/
private String extractRelativePath(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return requestURI;
}
}

总结

  • SpringCloudFeign基于接口来实现调用,Spring CloudFeign 添加了支持 Spring MVC 注解,这对应我们平常开发来说是很容易上手的