【SpringCloud构建微服务系列】Feign的使用详解


一、简介

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

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

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

二、为服务消费者整合Feign

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

2.pom文件添加依赖:

 

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

 

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

 1 package cn.sp.client;  2 
 3 import cn.sp.bean.User;  4 import org.springframework.cloud.netflix.feign.FeignClient;  5 import org.springframework.web.bind.annotation.GetMapping;  6 import org.springframework.web.bind.annotation.PathVariable;  7 
 8 /**
 9  * @author ship 10  * @Description 11  * @Date: 2018-07-17 13:25 12  */
13 @FeignClient(name = "microservice-provider-user") 14 public interface UserFeignClient { 15 
16     @GetMapping("/{id}") 17     User findById(@PathVariable("id") Long id); 18 }
View Code

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

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

4.Controller层代码

 1 /**
 2  * 请求用户微服务的API  3  * Created by 2YSP on 2018/7/8.  4  */
 5 @RestController  6 public class MovieController {  7 
 8  @Autowired  9     private UserFeignClient userFeignClient; 10 
11     @GetMapping("/user/{id}") 12     public User findById(@PathVariable Long id){ 13         return userFeignClient.findById(id); 14  } 15 }

 

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

 1 /**
 2  * 使用Feign进行声明式的REST Full API调用  3  */
 4 @EnableFeignClients(basePackages = {"cn.sp.client"})  5 @EnableEurekaClient  6 @SpringBootApplication  7 public class MicroserviceConsumerMovieFeignApplication {  8 
 9     public static void main(String[] args) { 10         SpringApplication.run(MicroserviceConsumerMovieFeignApplication.class, args); 11  } 12 }

这里我添加了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.添加依赖

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

 3.代码部分

 1 package cn.sp.conf;  2 
 3 import org.springframework.beans.factory.annotation.Autowired;  4 import org.springframework.context.annotation.Bean;  5 import org.springframework.context.annotation.Configuration;  6 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;  7 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;  8 import org.springframework.security.config.annotation.web.builders.HttpSecurity;  9 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;  10 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;  11 import org.springframework.security.core.GrantedAuthority;  12 import org.springframework.security.core.authority.SimpleGrantedAuthority;  13 import org.springframework.security.core.userdetails.UserDetails;  14 import org.springframework.security.core.userdetails.UserDetailsService;  15 import org.springframework.security.core.userdetails.UsernameNotFoundException;  16 import org.springframework.security.crypto.password.NoOpPasswordEncoder;  17 import org.springframework.security.crypto.password.PasswordEncoder;  18 import org.springframework.stereotype.Component;  19 
 20 import java.util.ArrayList;  21 import java.util.Collection;  22 import java.util.List;  23 
 24 /**
 25  * @author ship  26  * @Description  27  * @Date: 2018-07-18 09:51  28  */
 29 @Configuration  30 @EnableWebSecurity  31 @EnableGlobalMethodSecurity(prePostEnabled = true)  32 public class WebSecurityConfig extends WebSecurityConfigurerAdapter{  33 
 34  @Autowired  35  CustomUserDetailService userDetailService;  36 
 37  @Override  38     protected void configure(AuthenticationManagerBuilder auth) throws Exception {  39         super.configure(auth);  40         auth.userDetailsService(userDetailService).passwordEncoder(this.passwordEncoder());  41  }  42 
 43  @Override  44     protected void configure(HttpSecurity http) throws Exception {  45         super.configure(http);  46         //所有请求都需要经过HTTP basic认证
 47  http.authorizeRequests().anyRequest().authenticated().and().httpBasic();  48  }  49 
 50  @Bean  51     public PasswordEncoder passwordEncoder(){  52         //明文编码器,这是一个不做任何操作的密码编码器,是Spring提供给我们做明文测试的
 53         return NoOpPasswordEncoder.getInstance();  54  }  55 
 56 }  57 
 58 @Component  59 class CustomUserDetailService implements UserDetailsService{  60     /**
 61  * 模拟两个账号:用户名user和用户名admin  62  * @param username  63  * @return
 64  * @throws UsernameNotFoundException  65      */
 66  @Override  67     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  68         if ("user".equals(username)){  69             return new SecurityUser("user","password1","user-role");  70         }else if ("admin".equals(username)){  71             return new SecurityUser("admin","password2","admin-role");  72  }  73         return null;  74  }  75 }  76 
 77 class SecurityUser implements UserDetails{  78 
 79     private Long id;  80     private String username;  81     private String password;  82     private String role;  83 
 84     public SecurityUser(String username,String password,String role){  85         this.username = username;  86         this.password = password;  87         this.role = role;  88  }  89 
 90     public Long getId() {  91         return id;  92  }  93 
 94     public void setId(Long id) {  95         this.id = id;  96  }  97 
 98     public void setUsername(String username) {  99         this.username = username; 100  } 101 
102     public void setPassword(String password) { 103         this.password = password; 104  } 105 
106 
107     public String getRole() { 108         return role; 109  } 110 
111     public void setRole(String role) { 112         this.role = role; 113  } 114 
115  @Override 116     public Collection<? extends GrantedAuthority> getAuthorities() { 117         List<GrantedAuthority> authorities = new ArrayList<>(); 118         SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.role); 119  authorities.add(authority); 120         return authorities; 121  } 122 
123  @Override 124     public String getPassword() { 125         return this.password; 126  } 127 
128  @Override 129     public String getUsername() { 130         return this.username; 131  } 132 
133  @Override 134     public boolean isAccountNonExpired() { 135         return true; 136  } 137 
138  @Override 139     public boolean isAccountNonLocked() { 140         return true; 141  } 142 
143  @Override 144     public boolean isCredentialsNonExpired() { 145         return true; 146  } 147 
148  @Override 149     public boolean isEnabled() { 150         return true; 151  } 152 }
View Code
 1 package cn.sp.controller;  2 
 3 import cn.sp.bean.User;  4 import cn.sp.service.UserService;  5 import org.slf4j.Logger;  6 import org.slf4j.LoggerFactory;  7 import org.springframework.beans.factory.annotation.Autowired;  8 import org.springframework.security.core.GrantedAuthority;  9 import org.springframework.security.core.context.SecurityContextHolder; 10 import org.springframework.security.core.userdetails.UserDetails; 11 import org.springframework.web.bind.annotation.GetMapping; 12 import org.springframework.web.bind.annotation.PathVariable; 13 import org.springframework.web.bind.annotation.RestController; 14 
15 import java.util.Collection; 16 
17 /**
18  * Created by 2YSP on 2018/7/8. 19  */
20 @RestController 21 public class UserController { 22 
23  @Autowired 24     private UserService userService; 25 
26     private static final Logger log = LoggerFactory.getLogger(UserController.class); 27 
28 
29 
30 
31     @GetMapping("/{id}") 32     public User findById(@PathVariable Long id){ 33         Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 34         if (principal instanceof UserDetails){ 35             UserDetails user = (UserDetails) principal; 36             Collection<? extends GrantedAuthority> authorities = user.getAuthorities(); 37             for (GrantedAuthority g : authorities){ 38                 //打印用户当前信息
39                 log.info("当前用户是:{},角色是:{}",user.getUsername(),g.getAuthority()); 40  } 41         }else { 42             //do other things
43  } 44         return userService.findById(id); 45  } 46 }
View Code

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); } }
View Code

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

2018-07-18 14:19:06.320  INFO 14256 --- [nio-8000-exec-1] cn.sp.controller.UserController          : 当前用户是:user,角色是:user-role

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

2018-07-18 14:20:13.772  INFO 14256 --- [nio-8000-exec-2] cn.sp.controller.UserController          : 当前用户是:admin,角色是:admin-role

四、Feign对继承的支持

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

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

 

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

服务提供者Controller:UserResource.java

1 @RestController 2 public class UserResource implements UserService { 3 
4   //...
5 }

服务消费者:UserClient.java

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

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

五、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配置类

 

 1 package cn.sp.conf;  2 
 3 import feign.Logger;  4 import org.springframework.context.annotation.Bean;  5 import org.springframework.context.annotation.Configuration;  6 
 7 /**
 8  * Created by 2YSP on 2018/7/18.  9  */
10 @Configuration 11 public class FeignLogConfiguration { 12 
13     /**
14  * NONE:不记录任何日志(默认) 15  * BASIC:仅记录请求方法、URL、响应状态代码以及执行时间 16  * HEADERS:记录BASIC级别的基础上,记录请求和响应的header 17  * FULL:记录请求和响应的header,body和元数据 18  * @return
19      */
20  @Bean 21  Logger.Level feignLoggerLevel(){ 22         return Logger.Level.FULL; 23  } 24 }

 

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

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

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,可能会采取如下的方式

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

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

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

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

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

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

 

 

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

 

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

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

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

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM