一、简介

在微服务中,服务消费者需要请求服务生产者的接口进行消费,可以使用SpringBoot自带的RestTemplate或者HttpClient实现,但是都过于麻烦。

这时,就可以使用Feign了,它可以帮助我们更加便捷、优雅地调用HTTP API。

本文代码全部已上传至我的github,点击这里获取

二、为服务消费者整合Feign

1.复制项目microservice-consumer-movie,并修改为microservice-consumer-movie-feign

2.pom文件添加依赖:

     <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

3.创建一个Feign接口,并添加@FeignClient注解

 package cn.sp.client;

 import cn.sp.bean.User;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; /**
* @author ship
* @Description
* @Date: 2018-07-17 13:25
*/
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient { @GetMapping("/{id}")
User findById(@PathVariable("id") Long id);
}

@FeignClient注解中的microservice-provider-user是一个任意的客户端名称,用于创建Ribbon负载均衡器。

再这里,由于使用了Eureka,所以Ribbon会把microservice-provider-user解析为Eureka Server服务注册表中的服务。

4.Controller层代码

 /**
* 请求用户微服务的API
* Created by 2YSP on 2018/7/8.
*/
@RestController
public class MovieController { @Autowired
private UserFeignClient userFeignClient; @GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return userFeignClient.findById(id);
}
}

5.修改启动类,添加@EnableFeignClients注解

 /**
* 使用Feign进行声明式的REST Full API调用
*/
@EnableFeignClients(basePackages = {"cn.sp.client"})
@EnableEurekaClient
@SpringBootApplication
public class MicroserviceConsumerMovieFeignApplication { public static void main(String[] args) {
SpringApplication.run(MicroserviceConsumerMovieFeignApplication.class, args);
}
}

这里我添加了basePackages属性指定扫描的包,开始没添加报错了。

这样,电影微服务就可以调用用户微服务的API了。

1.启动microservice-discovery-eureka 

2.启动生产者microservice-provider-user

3.启动microservice-consumer-movie-feign

4.访问http://localhost:8010/user/1获得返回数据。

三、手动创建Feign

1.复制microservice-provider-user并修改artifactId为microservice-provider-user-with-auth

2.添加依赖

 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

3.代码部分

 package cn.sp.conf;

 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; /**
* @author ship
* @Description
* @Date: 2018-07-18 09:51
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired
CustomUserDetailService userDetailService; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(userDetailService).passwordEncoder(this.passwordEncoder());
} @Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
//所有请求都需要经过HTTP basic认证
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
} @Bean
public PasswordEncoder passwordEncoder(){
//明文编码器,这是一个不做任何操作的密码编码器,是Spring提供给我们做明文测试的
return NoOpPasswordEncoder.getInstance();
} } @Component
class CustomUserDetailService implements UserDetailsService{
/**
* 模拟两个账号:用户名user和用户名admin
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if ("user".equals(username)){
return new SecurityUser("user","password1","user-role");
}else if ("admin".equals(username)){
return new SecurityUser("admin","password2","admin-role");
}
return null;
}
} class SecurityUser implements UserDetails{ private Long id;
private String username;
private String password;
private String role; public SecurityUser(String username,String password,String role){
this.username = username;
this.password = password;
this.role = role;
} public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public void setUsername(String username) {
this.username = username;
} public void setPassword(String password) {
this.password = password;
} public String getRole() {
return role;
} public void setRole(String role) {
this.role = role;
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.role);
authorities.add(authority);
return authorities;
} @Override
public String getPassword() {
return this.password;
} @Override
public String getUsername() {
return this.username;
} @Override
public boolean isAccountNonExpired() {
return true;
} @Override
public boolean isAccountNonLocked() {
return true;
} @Override
public boolean isCredentialsNonExpired() {
return true;
} @Override
public boolean isEnabled() {
return true;
}
}
 package cn.sp.controller;

 import cn.sp.bean.User;
import cn.sp.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController; import java.util.Collection; /**
* Created by 2YSP on 2018/7/8.
*/
@RestController
public class UserController { @Autowired
private UserService userService; private static final Logger log = LoggerFactory.getLogger(UserController.class); @GetMapping("/{id}")
public User findById(@PathVariable Long id){
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails){
UserDetails user = (UserDetails) principal;
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
for (GrantedAuthority g : authorities){
//打印用户当前信息
log.info("当前用户是:{},角色是:{}",user.getUsername(),g.getAuthority());
}
}else {
//do other things
}
return userService.findById(id);
}
}

4.测试修改后的用户服务

先启动microservice-discovery-eureka,再启动microservice-provider-user-with-auth,访问http://localhost:8000/1,即可弹出一个登录框,输入用户名和密码(user/password1和admin/password)才能获取结果。

5.修改电影服务,复制microservice-consumer-movie-feign并改为microservice-consumer-movie-feign-manual

6.去掉Feign接口的@FeignClient注解,去掉启动类的@EnableFeignClients注解

7.controller层代码修改如下。

/**
* 请求用户微服务的API
* Created by 2YSP on 2018/7/8.
*/
@Import(FeignClientsConfiguration.class)
@RestController
public class MovieController { private UserFeignClient userUserFeignClient; private UserFeignClient adminUserFeignClient; @Autowired
public MovieController(Decoder decoder, Encoder encoder, Client client, Contract contract){
this.userUserFeignClient = Feign.builder().client(client).decoder(decoder).encoder(encoder)
.contract(contract).requestInterceptor(new BasicAuthRequestInterceptor("user","password1"))
.target(UserFeignClient.class,"http://microservice-provider-user/"); this.adminUserFeignClient = Feign.builder().client(client).decoder(decoder).encoder(encoder)
.contract(contract).requestInterceptor(new BasicAuthRequestInterceptor("admin","password2"))
.target(UserFeignClient.class,"http://microservice-provider-user/");
} @GetMapping("/user-user/{id}")
public User findByIdUser(@PathVariable Long id){
return userUserFeignClient.findById(id);
} @GetMapping("/user-admin/{id}")
public User findByIdAdmin(@PathVariable Long id){
return adminUserFeignClient.findById(id);
}
}

8.启动microservice-consumer-movie-feign-manual,并访问http://localhost:8010/user-user/4获取结果同时看到用户微服务打印日志。

-- ::06.320  INFO  --- [nio--exec-] cn.sp.controller.UserController          : 当前用户是:user,角色是:user-role

访问http://localhost:8010/user-admin/4打印日志:

-- ::13.772  INFO  --- [nio--exec-] cn.sp.controller.UserController          : 当前用户是:admin,角色是:admin-role

四、Feign对继承的支持

Feign还支持继承,将一些公共操作弄到父接口,从而简化开发

比如,先写一个基础接口:UserService.java

 public interface UserService {
@RequestMapping(method= RequestMethod.GET,value="/user/{id}")
User getUser(@PathVariable("id") long id);
}

服务提供者Controller:UserResource.java

 @RestController
public class UserResource implements UserService { //...
}

服务消费者:UserClient.java

 @FeignClient("users")
public interface UserClient extends UserService {
}

虽然很方便但是官方不建议这样做。

五、Feign对压缩的支持

Feign还可以对传输的数据进行压缩,只需要在appllication.properties文件添加如下配置即可。

feign.compression.request.enable=true
feign.compression.response.enable=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

六、设置Feign的日志

1.复制项目microservice-consumer-movie-feign,修改为microservice-consumer-movie-feign-logging

2.编写Feign配置类

 package cn.sp.conf;

 import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* Created by 2YSP on 2018/7/18.
*/
@Configuration
public class FeignLogConfiguration { /**
* NONE:不记录任何日志(默认)
* BASIC:仅记录请求方法、URL、响应状态代码以及执行时间
* HEADERS:记录BASIC级别的基础上,记录请求和响应的header
* FULL:记录请求和响应的header,body和元数据
* @return
*/
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}

3.修改Feign,使用指定配置类

 @FeignClient(name = "microservice-provider-user",configuration = FeignLogConfiguration.class)
public interface UserFeignClient { @GetMapping("/{id}")
User findById(@PathVariable("id") Long id);
}

4.在application.yml中添加如下内容,设置日志级别,注意:Feign的日志打印只会对DEBUG级别做出响应

5测试:启动microservice-discovery-eurekamicroservice-provider-usermicroservice-consumer-movie-feign-logging,访问http://localhost:8010/user/1,可看到日志结果。

七、使用Feign构造多参数请求

当我们用Get请求多参数的URL的时候,比如:http://microservice-provider-user/get?id=1&username=zhangsan,可能会采取如下的方式

 @FeignClient(name = "microservice-provider-user")
public interface UserFeignClient { @RequestMapping(value = "/get",method = RequestMethod.GET)
User get0(User user); }

然而会报错,尽管指定了GET方法,Feign仍然会使用POST方法发起请求。

正确处理方式一:使用@RequestParam注解

  @RequestMapping(value = "/get",method = RequestMethod.GET)
User get1(@RequestParam("id") Long id,@RequestParam("username") String username);

但是这种方法也有个缺点,如果参数比较多就要写很长的参数列表。

正确处理方式二:使用map接收

 @RequestMapping(value = "/get",method = RequestMethod.GET)
User get2(Map<String,Object> map);

处理方式三:如果请求方式没有限制的话,换成POST方式

  @RequestMapping(value = "/get",method = RequestMethod.POST)
User get3(User user);

排版比较乱敬请见谅,参考资料:SpringCloud与Docker微服务架构实战。

【SpringCloud构建微服务系列】Feign的使用详解的更多相关文章

  1. 【SpringCloud构建微服务系列】分布式链路跟踪Spring Cloud Sleuth

    一.背景 随着业务的发展,系统规模越来越大,各微服务直接的调用关系也变得越来越复杂.通常一个由客户端发起的请求在后端系统中会经过多个不同的微服务调用协同产生最后的请求结果,几乎每一个前端请求都会形成一 ...

  2. 【SpringCloud构建微服务系列】微服务网关Zuul

    一.为什么要用微服务网关 在微服务架构中,一般不同的微服务有不同的网络地址,而外部客户端(如手机APP)可能需要调用多个接口才能完成一次业务需求.例如一个电影购票的手机APP,可能会调用多个微服务的接 ...

  3. 【SpringCloud构建微服务系列】学习断路器Hystrix

    一.Hystrix简介 在微服务架构中经常包括多个服务层,比如A为B提供服务,B为C和D提供服务,如果A出故障了就会导致B也不可用,最终导致C和D也不可用,这就形成了雪崩效应. 所以为了应对这种情况, ...

  4. 【SpringCloud构建微服务系列】使用Spring Cloud Config统一管理服务配置

    一.为什么要统一管理微服务配置 对于传统的单体应用而言,常使用配置文件来管理所有配置,比如SpringBoot的application.yml文件,但是在微服务架构中全部手动修改的话很麻烦而且不易维护 ...

  5. SpringCloud与微服务系列专栏

    一. 前置知识 学习SpringCloud之前需要具备和掌握如下框架和工具的使用:SpringMVC,Spring,Spring Boot,Mybatis,Maven,Git. SpringCloud ...

  6. SpringCloud 构建微服务架构-练习

    我使用的springboot的版本为2.0.2.RELEASE,这里概念性的东西我就不粘贴复制了,百度一下 都是 一.启动Eureka注册中心服务 1.新建springboot项目,pom.xml配置 ...

  7. 【微服务】之二:从零开始,轻松搞定SpringCloud微服务系列--注册中心(一)

    微服务体系,有效解决项目庞大.互相依赖的问题.目前SpringCloud体系有强大的一整套针对微服务的解决方案.本文中,重点对微服务体系中的服务发现注册中心进行详细说明.本篇中的注册中心,采用Netf ...

  8. 实战SpringCloud响应式微服务系列教程(第八章)构建响应式RESTful服务

    本文为实战SpringCloud响应式微服务系列教程第八章,讲解构建响应式RESTful服务.建议没有之前基础的童鞋,先看之前的章节,章节目录放在文末. 1.使用springboot2.1.4构建RE ...

  9. 《Spring Cloud构建微服务架构》系列博文示例

    SpringCloud-Learning   源码下载地址:http://download.csdn.net/detail/k21325/9650968     本项目内容为Spring Cloud教 ...

随机推荐

  1. 转:TLV 格式及编解码示例

    TLV是一种可变格式,意思就是: Type类型, Lenght长度,Value值: Type和Length的长度固定,一般那是2.4个字节(这里统一采用4个字节): Value的长度有Length指定 ...

  2. 代理服务器squid简介

    Squid 是一个高性能.开源的代理缓存服务器和 Web 缓存进程,支持 FTP.Internet Gopher.HTTPS 和 SSL 等多种协议.它通过一个非阻塞的.I/O 事件驱动的单一进程处理 ...

  3. How do you check if a variable is an array in JavaScript? [duplicate]

    https://stackoverflow.com/questions/767486/how-do-you-check-if-a-variable-is-an-array-in-javascript ...

  4. How To Configure a Redis Cluster on Ubuntu 14.04

    原文:https://www.digitalocean.com/community/tutorials/how-to-configure-a-redis-cluster-on-ubuntu-14-04 ...

  5. Dell R420 RAID建立以及系统安装

    http://thefallenheaven.blog.51cto.com/450907/1753472 Dell R420的RAID划分,以及系统安装 3块2T的盘,装好硬盘后开机,这里有3种方式去 ...

  6. Binder系列8—如何使用Binder(转)

    一.Native层Binder 源码结构: ClientDemo.cpp: 客户端程序 ServerDemo.cpp:服务端程序 IMyService.h:自定义的MyService服务的头文件 IM ...

  7. Nginx在Linux下的安装部署

    Nginx简单介绍 Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 server,也是一个 IMAP/POP3/SMTP server.Nginx作为 ...

  8. devres in linux driver

    写 driver 时, probe 中常常要为设备分配一些资源, 如 内存 / irq / gpio / iomap 等. 而在 probe 中失败时又要小心的释放掉这些资源. 底层驱动开发人员可能会 ...

  9. XML中的CDATA是什么?PCDATA是什么?

    PCDATA表示已解析的字符数据. 在CDATA内部的所有内容都会被解析器忽略.

  10. tunctl和虚拟网卡

    1 tun/tap驱动 1.1 网卡驱动 同tcp/ip协议栈打交道,接受和发送数据包 1.2 字符驱动 内核和用户态通过字符设备交换数据包. 2 tun虚拟网卡的创建 tunctl -t tun0 ...