Sprig Cloud(五):Feign

  • Demo 源码下载
  • 本案例为源码分支的 feign 分支

简介

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

快速入门

  • 通过 RestTemplate 调用 API 大致代码如下
restTemplate.getForObject("http://flim-user/"+id,User.class);
  • 但当 URL 有多个参数时,就会变得很低效,如要请求这样的请求 URL: http://localhost:8010/search?name=张三&username=account1&age=20
Map 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 依赖

    org.springframework.cloud
    spring-cloud-starter-feign
    1.4.4.RELEASE

  • 创建一个 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库

    org.springframework.boot
    spring-boot-starter-security

  • 创建 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 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);
    }
}
  • 测试:
    • 启动 Eureka Server、film-user、film-consumer
    • 访问 http://localhost:8010/user/1 与 http://localhost:8010/admin/1 可看到控制台输出不同结果
    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作出响应/
  • 访问 http://localhost:8010/user/1 会输出
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 map);
    
    public User get(String username,String password){
        HashMap 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)