【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 }
@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微服务架构实战。
本文作者:烟味i
本文链接:https://www.cnblogs.com/2YSP/p/9328158.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步