Sprig Cloud(五):Feign

作者: 聪明的奇瑞 | 来源:发表于2018-04-04 17:19 被阅读392次

    简介

    • Feign 是声明式、模版化的 HTTP 客户端,可以更加便捷的调用 HTTP API

    快速入门

    • 通过 RestTemplate 调用 API 大致代码如下
    restTemplate.getForObject("http://flim-user/"+id,User.class);
    
    Map<String,Object> map = new HashMap<>();
    map.put("name","张三");
    map.put("username","account1");
    map.put("age",20);
    this.restTemplate.getForObject(" http://localhost:8010/search?name={name}&username={username}&age={age}",User[].class,map);
    
    • 而使用 Feign 非常简单,创建一个接口,并添加一些注解就可以了

    服务消费者整合 Feign

    • 添加 Feign 依赖
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-feign</artifactId>
        <version>1.4.4.RELEASE</version>
    </dependency>
    
    • 创建一个 Feign 接口,并添加 @FeignClient 注解
    @FeignClient(name = "film-user")       // 服务提供者名称
    public interface UserFeignClient {
        @GetMapping("/{id}")
        UserEntity findById(@PathVariable("id") int id);
    }
    
    • 由于使用 Eureka,film-user 会被解析成 Eureka Server 中注册的微服务,若不想使用 Eureka,可以使用 service.ribbon.listOfServers 属性配置服务列表
    • 还可使用 url 属性指定请求 URL
    @FeignClient(name = "film-user",url = "http://localhost:8080")
    
    • 修改 Controller 代码,调用其 Feign 接口
    @Autowired
    private UserFeignClient userFeignClient;
    
    @GetMapping("/user/{id}")
    public UserEntity findById(@PathVariable int id){
        return userFeignClient.findById(id);
    }
    
    • 启动类添加 @EnableFeignClients

    自定义 Feign 配置

    • Feign 默认配置类是 FeignClientsConfiguration,该类定义了默认解码器、编码器、所用的契约等,Spring Cloud 允许通过 @FeignClient 的 configuration 属性自定义配置,优先级比默认要高,Feign 可使用 SpringMVC 注解进行工作
    • 创建 Feign 配置类
    @Configuration
    public class FeignConfiguration {
        //使用Feign自带的注解
        @Bean
        public Contract feignContract(){
            return new feign.Contract.Default();
        }
    }
    
    • 修改 Feign 接口,使用 @FeignClient 的 configuration 属性指定配置类,同时将 findById 上的 SpringMVC 注解改为 Feign 自带的注解
    @FeignClient(name = "film-user",configuration = FeignConfiguration.class)
    public interface UserFeignClient {
        //使用Feign自带的注解@RequestLine
        @RequestLine("GET /{id}")
        UserEntity findById(@PathVariable("id") int id);
    }
    
    • 还可以定义 Feign 的解码器、编码器、日志打印、拦截器等,一些接口需要 HttpBasic 认证后才能调用

    手动创建 Feign

    • Feign 默认的方式可能无法满足业务需求,可用 Feign Builder API 手动创建 Feign,例如案例要实现的需求如下:
      • 用户微服务的接口需要登录才能调用,不同的用户角色有不同的行为
      • 电影微服务中,不同的账号登录,调用不同的微服务接口

    修改服务提供者

    • 添加Security库
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
    • 创建 SpringSecurity 配置类,添加两个账号 user 和 admin,密码分别是 password1 和 password2,角色分别是 user-role 和 admin-role
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                    .withUser("user").password("password1").roles("USER")
                    .and()
                    .withUser("admin").password("password2").roles("ADMIN")
                    .and()
                    .passwordEncoder(this.passwordEncoder());
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //所有请求都要经过httpBasic认证
            http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
        }
    
        @Bean
        public PasswordEncoder passwordEncoder(){
            //返回一个明文密码器,Spring 提供给我们做明文测试的
            return NoOpPasswordEncoder.getInstance();
        }
    }
    
    • 修改 UserController 打印当前登录的用户信息
    @RestController
    public class UserController {
        @Autowired
        private UserRepository userRepository;
    
        private final Logger log = LoggerFactory.getLogger(UserController.class);
    
        @GetMapping("/{id}")
        public User findById(@PathVariable int id){
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            org.springframework.security.core.userdetails.User principal = (org.springframework.security.core.userdetails.User) authentication.getPrincipal();
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            log.info("当前的用户是{},角色是{}",principal.getUsername(),authorities.stream()
                    .map((GrantedAuthority e) -> e.getAuthority()).collect(Collectors.joining(",")));
            User user = userRepository.findOne(id);
            return user;
        }
    }
    
    • 使用 user/password1 登录与 user/password2 登录会看到如下日志
     com.linyuan.controller.UserController    : 当前的用户是user,角色是ROLE_USER
     com.linyuan.controller.UserController    : 当前的用户是admin,角色是ROLE_ADMIN
    

    修改服务消费者

    • 去掉接口 UserFeignClient 上的 @FeignClient 注解,去掉启动类上的 @EnableFeignClients 注解
    • 修改 Controller,通过 @Import 导 FeignClientsConfiguration,FeignClientsConfiguration 是 Spring Cloud 为 Feign 提供的默认配置,这里 userFeignClient 登录账号是 user,adminFeignClient 登录账号是 admin,它们使用的是同一个接口
    @Import(FeignClientsConfiguration.class)
    @RestController
    public class MovieController {
    private UserFeignClient adminFeignClient;
    
        private UserFeignClient userFeignClient;
    
        @Autowired
        public MovieController(Decoder decoder, Encoder encoder, Client client, Contract contract) {
            this.userFeignClient = Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract)
                    .requestInterceptor(new BasicAuthRequestInterceptor("user","password1"))
                    .target(UserFeignClient.class,"http://film-user/");
            this.adminFeignClient = Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract)
                    .requestInterceptor(new BasicAuthRequestInterceptor("admin","password2"))
                    .target(UserFeignClient.class,"http://film-user/");
        }
    
        @GetMapping("/user/{id}")
        public UserEntity findByIdUser(@PathVariable int id){
            return userFeignClient.findById(id);
        }
    
        @GetMapping("/admin/{id}")
        public UserEntity findByIdAdmin(@PathVariable int id){
            return adminFeignClient.findById(id);
        }
    }
    
    • 测试:
      com.linyuan.controller.UserController    : 当前的用户是user,角色是ROLE_USER
      com.linyuan.controller.UserController    : 当前的用户是admin,角色是ROLE_ADMIN
      

    Feign 对压缩的支持

    • 某些场景下,可能需要对请求或响应进行压缩,可通过以下属性启动 Feign 的压缩功能
    feign.compression.request.enabled = true
    feign.compression.response.enabled = true
    
    • 对于请求的压缩,还可以更详细的配置,比如配置支持的媒体类型表,默认是,请求的最小阈值
    feign.compression.request.enabled = true
    feign.compression.request.mine-types = text/xml,application/xml,application/json    #默认配置
    feign.compression.request.min-request-size = 2048   #默认配置
    

    Feign 日志

    • Feign 对日志的处理非常灵活,可为每个 Feign 客户端指定日志策略,每个 Feign 客户端都会创建一个 Logger,默认名称为 Feign 接口的完整类名,但 Feign 日志打印只会对 DEBUG 级别做出响应
    • 我们可为每个 Feign 客户端配置各自的 Logger.level 对象,告诉 Feign 记录哪些日志,Logger.Level 的值有以下选择:
      • NONE:不记录任何日志(默认)
      • BASIC:仅记录请求方法、URL、响应状态码以及执行时间
      • HEADERS:记录 BASIC 级别的基础上,记录请求和响应的 header
      • FULL:记录请求响应的 header、body 和元数据
    • 编写 Feign 配置类
    @Configuration
    public class FeignConfiguration {
        @Bean
        public Logger.Level feignLoggerLevel(){
            return Logger.Level.BASIC;
        }
    }
    
    • 修改 Feign 的接口,指定配置类
    @FeignClient(name = "film-user",configuration = FeignConfiguration.class)
    public interface UserFeignClient {
        @RequestMapping(value = "/{id}", method = RequestMethod.GET)
        UserEntity findById(@PathVariable("id") int id);
    }
    
    • 添加以下配置到配置文件,指定 Feign 接口日志级别为 DEBUG
    logging:
      level: 
        com.linyuan.request.UserFeignClient: DEBUG  #Feign的Logger.Level只对DEBUG作出响应/
    
    2017-12-22 11:41:34.387 DEBUG 7405 --- [nio-8010-exec-3] com.linyuan.request.UserFeignClient      : [UserFeignClient#findById] ---> GET http://flim-user/1 HTTP/1.1
    2017-12-22 11:41:34.395 DEBUG 7405 --- [nio-8010-exec-3] com.linyuan.request.UserFeignClient      : [UserFeignClient#findById] <--- HTTP/1.1 200 (8ms)
    2017-12-22 11:41:35.001 DEBUG 7405 --- [nio-8010-exec-5] com.linyuan.request.UserFeignClient      : [UserFeignClient#findById] ---> GET http://flim-user/1 HTTP/1.1
    2017-12-22 11:41:35.012 DEBUG 7405 --- [nio-8010-exec-5] com.linyuan.request.UserFeignClient      : [UserFeignClient#findById] <--- HTTP/1.1 200 (10ms)
    2017-12-22 11:41:35.591 DEBUG 7405 --- [nio-8010-exec-4] com.linyuan.request.UserFeignClient      : [UserFeignClient#findById] ---> GET http://flim-user/1 HTTP/1.1
    2017-12-22 11:41:35.602 DEBUG 7405 --- [nio-8010-exec-4] com.linyuan.request.UserFeignClient      : [UserFeignClient#findById] <--- HTTP/1.1 200 (11ms)
    

    Feign 构造多参数请求

    GET 请求多参数

    • 假设请求 URL 包含多个参数,如:http://flim-user/get?id=1&username=张三,因为 Feign 支持 SpringMVC 注解,则写法可以为:
      • 方法一
      @FeignClient(name = "film-user")
      public interface UserFeignClient {
          @RequestMapping(value = "/get", method = RequestMethod.GET)
          UserEntity get1(@RequestParam("id") int id,@RequestParam("username")String username);
      }
      
      • 方法二,可使用 Map 来构建,在调用时传递 Map 对象
      @RequestMapping(value = "/get", method = RequestMethod.GET)
      User get2(@RequestParam Map<String,Object> map);
      
      public User get(String username,String password){
          HashMap<String,Object> map = Maps.newHashMap();
          map.put("id",1);
          map.put("username","张三");
          return this.userUserFeignClient.get2(map);
      }
      

    POST 请求多参数

    • 假设服务提供者的 Controller 如下
    @RestController
    public class UserController{
        @PostMapping("/post")
        public User post (@RequestBody User user){
            ...
        }
    }
    
    • 则可以这样去请求
    @FeignClient(name = "flim-user")
    public interface UserFeignClient {
        @RequestMapping(value = "/post", method = RequestMethod.POST)
        UserEntity post(@RequestBody User user);
    }
    

    相关文章

      网友评论

        本文标题:Sprig Cloud(五):Feign

        本文链接:https://www.haomeiwen.com/subject/kmqlhftx.html