【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微服务架构实战。
【SpringCloud构建微服务系列】Feign的使用详解的更多相关文章
- 【SpringCloud构建微服务系列】分布式链路跟踪Spring Cloud Sleuth
一.背景 随着业务的发展,系统规模越来越大,各微服务直接的调用关系也变得越来越复杂.通常一个由客户端发起的请求在后端系统中会经过多个不同的微服务调用协同产生最后的请求结果,几乎每一个前端请求都会形成一 ...
- 【SpringCloud构建微服务系列】微服务网关Zuul
一.为什么要用微服务网关 在微服务架构中,一般不同的微服务有不同的网络地址,而外部客户端(如手机APP)可能需要调用多个接口才能完成一次业务需求.例如一个电影购票的手机APP,可能会调用多个微服务的接 ...
- 【SpringCloud构建微服务系列】学习断路器Hystrix
一.Hystrix简介 在微服务架构中经常包括多个服务层,比如A为B提供服务,B为C和D提供服务,如果A出故障了就会导致B也不可用,最终导致C和D也不可用,这就形成了雪崩效应. 所以为了应对这种情况, ...
- 【SpringCloud构建微服务系列】使用Spring Cloud Config统一管理服务配置
一.为什么要统一管理微服务配置 对于传统的单体应用而言,常使用配置文件来管理所有配置,比如SpringBoot的application.yml文件,但是在微服务架构中全部手动修改的话很麻烦而且不易维护 ...
- SpringCloud与微服务系列专栏
一. 前置知识 学习SpringCloud之前需要具备和掌握如下框架和工具的使用:SpringMVC,Spring,Spring Boot,Mybatis,Maven,Git. SpringCloud ...
- SpringCloud 构建微服务架构-练习
我使用的springboot的版本为2.0.2.RELEASE,这里概念性的东西我就不粘贴复制了,百度一下 都是 一.启动Eureka注册中心服务 1.新建springboot项目,pom.xml配置 ...
- 【微服务】之二:从零开始,轻松搞定SpringCloud微服务系列--注册中心(一)
微服务体系,有效解决项目庞大.互相依赖的问题.目前SpringCloud体系有强大的一整套针对微服务的解决方案.本文中,重点对微服务体系中的服务发现注册中心进行详细说明.本篇中的注册中心,采用Netf ...
- 实战SpringCloud响应式微服务系列教程(第八章)构建响应式RESTful服务
本文为实战SpringCloud响应式微服务系列教程第八章,讲解构建响应式RESTful服务.建议没有之前基础的童鞋,先看之前的章节,章节目录放在文末. 1.使用springboot2.1.4构建RE ...
- 《Spring Cloud构建微服务架构》系列博文示例
SpringCloud-Learning 源码下载地址:http://download.csdn.net/detail/k21325/9650968 本项目内容为Spring Cloud教 ...
随机推荐
- hdu6110(线段树+lca)
题目 http://acm.hdu.edu.cn/showproblem.php?pid=6110 分析 注意到,若干条路径的交一定也是条路径 我们可以维护一个线段树,seg[l..r]存着第l条~第 ...
- PostgreSQL 9.3.1 中文手册(解决关键词报错的问题)
http://www.postgres.cn/docs/9.3/sql-keywords-appendix.html
- 一起talk C栗子吧(第一百回:C语言实例--使用信号量进行进程间同步与相互排斥一)
各位看官们.大家好,上一回中咱们说的是进程间同步与相互排斥的样例,这一回咱们说的样例是:使用信号量进行进程间同步与相互排斥. 闲话休提,言归正转.让我们一起talk C栗子吧! 看官们,信号量是由著名 ...
- 【C++基础 02】深拷贝和浅拷贝
我的主题是.每天积累一点点. =========================================== 在类定义中,假设没有提供自己的拷贝构造函数,则C++提供一个默认拷贝构造函数. C ...
- windows下开发PHP扩展dll(无需Cygwin)
windows下开发php扩展网上很多资料都说需要Cygwin,其实完全可以不必安装该东东.没错,是可以在linux下生成骨架后拷到windos下来用,但是,如果没有linux环境呢?什么,装虚拟机? ...
- java.util.MissingResourceException: Can't find resource for bundle oracle.sysman.db.rsc.LoginResourc
http://blog.itpub.net/197458/viewspace-1055358/ oracle 10.2.0.4 windows 2003 X64 平台 系统安装EMCA正常.第一次 ...
- hdu 4057 Rescue the Rabbit
题意 给出n(n<=10)个串,每个串有个权值,然后让你构造一个长度为l(l<=100)的串,如果他包含给出的串就得到相应的权值,求可能得到的最大权值 解法 AC自动机+DP,很显然要建立 ...
- long类型字段转换成varchar2类型
參考文档: How to Convert a Long to Varchar2 (文档 ID 228532.1) /*long类型字段转换成varchar2类型*/ --建表 create table ...
- ZOJ 3316 Game 一般图最大匹配带花树
一般图最大匹配带花树: 建图后,计算最大匹配数. 假设有一个联通块不是完美匹配,先手就能够走那个没被匹配到的点.后手不论怎么走,都必定走到一个被匹配的点上.先手就能够顺着这个交错路走下去,最后一定是后 ...
- 使用 Vagrant 构建开发环境
使用 Vagrant 构建开发环境 摘要:本文描述了如使用 Vagrant 构建统一的开发环境. 问题 作为开发人员,我们通常面临的问题有: 开发环境需要手工安装配置,这包括操作系统(CentOS.U ...