【SpringCloud构建微服务系列】Feign的使用详解
一、简介
在微服务中,服务消费者需要请求服务生产者的接口进行消费,可以使用SpringBoot自带的RestTemplate或者HttpClient实现,但是都过于麻烦。
这时,就可以使用Feign了,它可以帮助我们更加便捷、优雅地调用HTTP API。
本文代码全部已上传至我的github,点击这里获取。
二、为服务消费者整合Feign
1.复制项目microservice-consumer-movie,并修改为microservice-consumer-movie-feign
2.pom文件添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
3.创建一个Feign接口,并添加@FeignClient注解
package cn.sp.client; import cn.sp.bean.User;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; /**
* @author ship
* @Description
* @Date: 2018-07-17 13:25
*/
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient { @GetMapping("/{id}")
User findById(@PathVariable("id") Long id);
}
@FeignClient注解中的microservice-provider-user是一个任意的客户端名称,用于创建Ribbon负载均衡器。
再这里,由于使用了Eureka,所以Ribbon会把microservice-provider-user解析为Eureka Server服务注册表中的服务。
4.Controller层代码
/**
* 请求用户微服务的API
* Created by 2YSP on 2018/7/8.
*/
@RestController
public class MovieController { @Autowired
private UserFeignClient userFeignClient; @GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return userFeignClient.findById(id);
}
}
5.修改启动类,添加@EnableFeignClients注解
/**
* 使用Feign进行声明式的REST Full API调用
*/
@EnableFeignClients(basePackages = {"cn.sp.client"})
@EnableEurekaClient
@SpringBootApplication
public class MicroserviceConsumerMovieFeignApplication { public static void main(String[] args) {
SpringApplication.run(MicroserviceConsumerMovieFeignApplication.class, args);
}
}
这里我添加了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.添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3.代码部分
package cn.sp.conf; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; /**
* @author ship
* @Description
* @Date: 2018-07-18 09:51
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired
CustomUserDetailService userDetailService; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(userDetailService).passwordEncoder(this.passwordEncoder());
} @Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
//所有请求都需要经过HTTP basic认证
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
} @Bean
public PasswordEncoder passwordEncoder(){
//明文编码器,这是一个不做任何操作的密码编码器,是Spring提供给我们做明文测试的
return NoOpPasswordEncoder.getInstance();
} } @Component
class CustomUserDetailService implements UserDetailsService{
/**
* 模拟两个账号:用户名user和用户名admin
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if ("user".equals(username)){
return new SecurityUser("user","password1","user-role");
}else if ("admin".equals(username)){
return new SecurityUser("admin","password2","admin-role");
}
return null;
}
} class SecurityUser implements UserDetails{ private Long id;
private String username;
private String password;
private String role; public SecurityUser(String username,String password,String role){
this.username = username;
this.password = password;
this.role = role;
} public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public void setUsername(String username) {
this.username = username;
} public void setPassword(String password) {
this.password = password;
} public String getRole() {
return role;
} public void setRole(String role) {
this.role = role;
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.role);
authorities.add(authority);
return authorities;
} @Override
public String getPassword() {
return this.password;
} @Override
public String getUsername() {
return this.username;
} @Override
public boolean isAccountNonExpired() {
return true;
} @Override
public boolean isAccountNonLocked() {
return true;
} @Override
public boolean isCredentialsNonExpired() {
return true;
} @Override
public boolean isEnabled() {
return true;
}
}
package cn.sp.controller; import cn.sp.bean.User;
import cn.sp.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController; import java.util.Collection; /**
* Created by 2YSP on 2018/7/8.
*/
@RestController
public class UserController { @Autowired
private UserService userService; private static final Logger log = LoggerFactory.getLogger(UserController.class); @GetMapping("/{id}")
public User findById(@PathVariable Long id){
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails){
UserDetails user = (UserDetails) principal;
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
for (GrantedAuthority g : authorities){
//打印用户当前信息
log.info("当前用户是:{},角色是:{}",user.getUsername(),g.getAuthority());
}
}else {
//do other things
}
return userService.findById(id);
}
}
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获取结果同时看到用户微服务打印日志。
-- ::06.320 INFO --- [nio--exec-] cn.sp.controller.UserController : 当前用户是:user,角色是:user-role
访问http://localhost:8010/user-admin/4打印日志:
-- ::13.772 INFO --- [nio--exec-] cn.sp.controller.UserController : 当前用户是:admin,角色是:admin-role
四、Feign对继承的支持
Feign还支持继承,将一些公共操作弄到父接口,从而简化开发
比如,先写一个基础接口:UserService.java
public interface UserService {
@RequestMapping(method= RequestMethod.GET,value="/user/{id}")
User getUser(@PathVariable("id") long id);
}
服务提供者Controller:UserResource.java
@RestController
public class UserResource implements UserService { //...
}
服务消费者:UserClient.java
@FeignClient("users")
public interface UserClient extends UserService {
}
虽然很方便但是官方不建议这样做。
五、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配置类
package cn.sp.conf; import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* Created by 2YSP on 2018/7/18.
*/
@Configuration
public class FeignLogConfiguration { /**
* NONE:不记录任何日志(默认)
* BASIC:仅记录请求方法、URL、响应状态代码以及执行时间
* HEADERS:记录BASIC级别的基础上,记录请求和响应的header
* FULL:记录请求和响应的header,body和元数据
* @return
*/
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
3.修改Feign,使用指定配置类
@FeignClient(name = "microservice-provider-user",configuration = FeignLogConfiguration.class)
public interface UserFeignClient { @GetMapping("/{id}")
User findById(@PathVariable("id") Long id);
}
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,可能会采取如下的方式
@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient { @RequestMapping(value = "/get",method = RequestMethod.GET)
User get0(User user); }
然而会报错,尽管指定了GET方法,Feign仍然会使用POST方法发起请求。
正确处理方式一:使用@RequestParam注解
@RequestMapping(value = "/get",method = RequestMethod.GET)
User get1(@RequestParam("id") Long id,@RequestParam("username") String username);
但是这种方法也有个缺点,如果参数比较多就要写很长的参数列表。
正确处理方式二:使用map接收
@RequestMapping(value = "/get",method = RequestMethod.GET)
User get2(Map<String,Object> map);
处理方式三:如果请求方式没有限制的话,换成POST方式
@RequestMapping(value = "/get",method = RequestMethod.POST)
User get3(User user);
排版比较乱敬请见谅,参考资料:SpringCloud与Docker微服务架构实战。
最新文章
- R平方
- 哈希 poj 3274
- Android利用调试器调试程序
- 关于redhat6的服务说明
- 【4】JAVA---地址App小软件(UpdatePanel.class)(表现层)
- Android中ExpandableListView控件基本使用
- octopress command memo
- 对比Tornado和Twisted两种异步Python框架
- eclipse调试的方法和技巧
- C# DataConstruct 数据结构关于 Array,ArrayList,List,HashTable,Dictionnary的学习记录
- 南京邮电大学 JavaA期末复习要点总结
- JSP动作元素<;jsp:include>;和<;jsp:param>;的搭配使用
- 元素视差方向移动jQuery插件-类似github 404页面效果
- 基于Android的百度地图实现输入地址返回经纬度信息
- python代码自动补全配置及Django入门Demo
- linux shell脚本编程笔记(五): 重定向
- 浅谈 Underscore.js 中 _.throttle 和 _.debounce 的差异[转]
- centos7更改主机名
- B/S供应链(打印管理)问题答疑
- MVC Html.DropDownList 和DropDownListFor 的常用方法