【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