一、简介
在微服务中,服务消费者需要请求服务生产者的接口进行消费,可以使用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 }
@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 }

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 }
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获取结果同时看到用户微服务打印日志。
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-eureka,microservice-provider-user和microservice-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微服务架构实战。