【SpringCloud构建微服务系列】Feign的使用详解 一、简介 二、为服务消费者整合Feign 三、手动创建Feign 四、Feign对继承的支持 五、Feign对压缩的支持 六、设置Feign的日志 七、使用Feign构造多参数请求

【SpringCloud构建微服务系列】Feign的使用详解
一、简介
二、为服务消费者整合Feign
三、手动创建Feign
四、Feign对继承的支持
五、Feign对压缩的支持
六、设置Feign的日志
七、使用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级别做出响应

【SpringCloud构建微服务系列】Feign的使用详解
一、简介
二、为服务消费者整合Feign
三、手动创建Feign
四、Feign对继承的支持
五、Feign对压缩的支持
六、设置Feign的日志
七、使用Feign构造多参数请求

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

【SpringCloud构建微服务系列】Feign的使用详解
一、简介
二、为服务消费者整合Feign
三、手动创建Feign
四、Feign对继承的支持
五、Feign对压缩的支持
六、设置Feign的日志
七、使用Feign构造多参数请求

七、使用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微服务架构实战。