一、简介

在微服务中,服务消费者需要请求服务生产者的接口进行消费,可以使用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-eurekamicroservice-provider-usermicroservice-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的使用详解的更多相关文章

  1. 【SpringCloud构建微服务系列】分布式链路跟踪Spring Cloud Sleuth

    一.背景 随着业务的发展,系统规模越来越大,各微服务直接的调用关系也变得越来越复杂.通常一个由客户端发起的请求在后端系统中会经过多个不同的微服务调用协同产生最后的请求结果,几乎每一个前端请求都会形成一 ...

  2. 【SpringCloud构建微服务系列】微服务网关Zuul

    一.为什么要用微服务网关 在微服务架构中,一般不同的微服务有不同的网络地址,而外部客户端(如手机APP)可能需要调用多个接口才能完成一次业务需求.例如一个电影购票的手机APP,可能会调用多个微服务的接 ...

  3. 【SpringCloud构建微服务系列】学习断路器Hystrix

    一.Hystrix简介 在微服务架构中经常包括多个服务层,比如A为B提供服务,B为C和D提供服务,如果A出故障了就会导致B也不可用,最终导致C和D也不可用,这就形成了雪崩效应. 所以为了应对这种情况, ...

  4. 【SpringCloud构建微服务系列】使用Spring Cloud Config统一管理服务配置

    一.为什么要统一管理微服务配置 对于传统的单体应用而言,常使用配置文件来管理所有配置,比如SpringBoot的application.yml文件,但是在微服务架构中全部手动修改的话很麻烦而且不易维护 ...

  5. SpringCloud与微服务系列专栏

    一. 前置知识 学习SpringCloud之前需要具备和掌握如下框架和工具的使用:SpringMVC,Spring,Spring Boot,Mybatis,Maven,Git. SpringCloud ...

  6. SpringCloud 构建微服务架构-练习

    我使用的springboot的版本为2.0.2.RELEASE,这里概念性的东西我就不粘贴复制了,百度一下 都是 一.启动Eureka注册中心服务 1.新建springboot项目,pom.xml配置 ...

  7. 【微服务】之二:从零开始,轻松搞定SpringCloud微服务系列--注册中心(一)

    微服务体系,有效解决项目庞大.互相依赖的问题.目前SpringCloud体系有强大的一整套针对微服务的解决方案.本文中,重点对微服务体系中的服务发现注册中心进行详细说明.本篇中的注册中心,采用Netf ...

  8. 实战SpringCloud响应式微服务系列教程(第八章)构建响应式RESTful服务

    本文为实战SpringCloud响应式微服务系列教程第八章,讲解构建响应式RESTful服务.建议没有之前基础的童鞋,先看之前的章节,章节目录放在文末. 1.使用springboot2.1.4构建RE ...

  9. 《Spring Cloud构建微服务架构》系列博文示例

    SpringCloud-Learning   源码下载地址:http://download.csdn.net/detail/k21325/9650968     本项目内容为Spring Cloud教 ...

随机推荐

  1. codeforces 875F(基环外向树)

    题意 有一个左边m个点,右边n个点的二分图(n,m<=1e5),左边每个点向右边恰好连两条权值相同的边. 求这个二分图的最优匹配 分析 对于这种二选一问题,即左边的a连向右边的b和c,权值为d, ...

  2. Spring Cloud(9):Config配置中心

    Config配置中心作用简单来讲:统一配置,方便管理 开源配置中心: 1.百度Disconf 2.阿里Diamand 3.Spring Cloud Config 搭建Config-Server 快速上 ...

  3. freeswitch电话代接

    Misc. Dialplan Tools intercept Description Allows one channel to bridge itself to the a or b leg of ...

  4. Eclipse运行Maven命令时出现:-Dmaven.multiModuleProjectDirectory system property is not set. Check $M2_HOME environment variable and mvn script match.问题解决

    错误: -Dmaven.multiModuleProjectDirectory system property is not set. Check $M2_HOME environment varia ...

  5. C# 读自己的资源文件

    Assembly assm = this.GetType().Assembly;//Assembly.LoadFrom(程序集路径); foreach (string resName in assm. ...

  6. 【Nginx】负载均衡

    本文介绍的负载均衡是针对的客户端请求在多个Nginx进程之间的均衡.注意与客户端请求在多个后端服务器之间的均衡相区别. 负载均衡问题的产生 在nginx中,建立连接的时候,会设计负载均衡问题.在多个子 ...

  7. 【Nginx】Hello world程序

    模块如何在运行中生效 配置文件中的location块决定了匹配某种URL的请求将会由相应的HTTP模块处理,因此,运行时HTTP框架会在接收完毕HTTP请求的头部后,将请求的URL与配置文件中的所有l ...

  8. ART虚拟机之Trace原理(转)

    一.概述 Android 6.0系统采用的art虚拟机,所有的Java进程都运行在art之上,当应用发生ANR(Application Not Response,其中最终的一个环节便是向目标进程发送信 ...

  9. Process类的使用

    Process process= new Process(); process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellE ...

  10. python 深浅复制与指针内存

    Python是一门非常好的语言,他的长处在于拥有巨大灵活性的同一时候也拥有无比的严谨性,其它语言规定了非常多语法.告诉你什么情况下,语法就是这种,而Python却用非常少的规定,延伸出非常多语法,有些 ...