在开发 Spring Cloud 微服务的时候,我们知道,服务之间都是以 HTTP 接口的形式对外提供服务的,因此消费者在进行调用的时候,底层就是通过 HTTP Client 的这种方式进行访问。当然我们可以使用JDK原生的 URLConnection、Apache 的 HTTP Client、Netty 异步 Http Client,Spring 的 RestTemplate 去实现服务间的调用。但是最方便、最优雅的方式是通过 Spring Cloud Open Feign 进行服务间的调用。Spring Cloud 对 Feign 进行了增强,使 Feign 支持 Spring Mvc 的注解,并整合了 Ribbon 等,从而让 Feign 的使用更加方便。
Feign是一个声明式的Web服务客户端,使用Feign可使得Web服务客户端的写入更加方便。 它具有可插拔注释支持,包括Feign注解和JAX-RS注解、Feign还支持可插拔编码器和解码器、Spring Cloud增加了对Spring MVC注释的支持,并HttpMessageConverters在Spring Web中使用了默认使用的相同方式。Spring Cloud集成了Ribbon和Eureka,在使用Feign时提供负载平衡的http客户端。
Feign作用:可以解决不同服务器接口之间的相互调用,即跨域请求!feign结合eureka注册中心,把不同的服务项目注册到eureka中,通过feign客户端进行调用,可以解决负载均衡问题。
接下来就简单讲述一下Feign的入门使用
一、引入依赖及配置编写
1、引入依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>10.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
2、编写配置文件
@Configuration
public class FeignConfiguration {
@Bean
public Contract feignContract() {
return new Contract.Default();
}
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
Decoder feignDecoder() {
return new GsonDecoder();
}
@Bean
Encoder feignEncoder() {
return new GsonEncoder();
}
}
3、在应用主类Application里,通过@EnableFeignClients注解开启Spring Cloud Feign的支持功能。
// 在启动类上加上注解
// 开启 Feign 扫描支持
@EnableFeignClients
二、编写Feign接口及使用
1、编写Feign接口
@FeignClient注解指定服务名来绑定服务,然后再使用Spring MVC的注解来绑定具体该服务提供的REST接口。
@FeignClient(name = "myApi", url = "http://localhost:8080")
public interface MyService {
// 调用另外一个服务的接口
@RequestLine("GET /getUsers?searchString={searchString}")
@Headers("Content-Type: application/json")
List<User> getUsers(@Param("searchString") String searchString);
}
2、使用接口
Feign接口不需要实现类,可直接调用
private MyService myService;
@GetMapping("/userList")
public List<User> getUsers(@RequestParam String searchString){
List<User> userList = myService.getUsers(searchString);
return userList;
}
三、携带token请求
为了安全考虑要访问的服务的接口需要token验证才能访问,因此需要携带token才能访问。关于新的服务搭建安全框架,使用与要访问的平台一致的token生成和验证机制,这里就不赘述了。
1、方案一:直接在@Headers注解中加token
这种方案可以用来测试,因为,这种方式token是写死的,不能根据浏览器携带的token进行验证。
@FeignClient(name = "myApi", url = "http://localhost:8080")
public interface MyService {
@RequestLine("GET /getUsers?searchString={searchString}")
// 直接在@Headers注解中加token
@Headers({"Content-Type: application/json", "Authorization: Bearer eyJh..."})
List<User> getUsers(@Param("searchString") String searchString);
}
2、方案二:根据浏览器动态获取token
如何从浏览器中拿到token?
可以看到javax.servlet.http包下有个getHeader的方法,可以获得当前浏览器Header中的信息。
如何将token放到跨域请求中?
在fegin包中的请求拦截器RequestInterceptor有个apply方法,该方法的默认实现如下:
可以看到,默认的Authorization是通过用户名和密码进行base64加密得到的,跟我们的token生成方式不一样,所以直接使用默认的是无法验证通过的,因此,只需实现RequestInterceptor,重写apply方法即可
解决方案
编写配置类,实现RequestInterceptor,重写apply方法,把浏览器header拿到的token放进去。
@Slf4j
@Configuration
@AllArgsConstructor
public class NimBusRequestInterceptor implements RequestInterceptor {
private HttpServletRequest req;
private static final String HEADER_STRING = "Authorization";
@Override
public void apply(RequestTemplate requestTemplate) {
// 如果header没有auth头,从cookie获取token
String token = req.getHeader(HEADER_STRING);
Cookie[] cookies = req.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
if (Objects.equals(cookie.getName(), "token")) {
try {
token = URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
log.error(LogUtil.getStack(e));
}
}
}
}
requestTemplate.header(HEADER_STRING, token);
}
}
以上就实现了Feign基本使用与携带token请求。