• 【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 }
    View Code

    @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 }
    View Code
     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 }
    View Code

    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);
        }
    }
    View Code

    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-eurekamicroservice-provider-usermicroservice-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微服务架构实战。

  • 相关阅读:
    Vue组件以及组件之间的通信
    VueRouter和Vue生命周期(钩子函数)
    Vuex、axios以及跨域请求处理
    element-ui和npm、webpack、vue-cli搭建Vue项目
    2018PyCharm激活方法
    pycharm修改选中字体颜色
    为自己的博客园添加目录锚点和返回顶部
    python初识
    JAVA判断当前日期是节假日还是工作日
    springmvc使用freemarker
  • 原文地址:https://www.cnblogs.com/2YSP/p/9328158.html
Copyright © 2020-2023  润新知