SpringCloud学习笔记-Feign

Feign

简介

Feign是Netflix开发的声明式、模板化的HTTP客户端,可以帮助我们更加便捷、优雅地调用HTTP API。
github地址

使用

为服务消费者整合Feign:

  • 引入starter依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 修改启动类,增加 @EnableFeignClients 注解:
@SpringBootApplication
@EnableFeignClients
public class EurekaClientConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClientConsumerApplication.class, args);
    }
}
  • 新建一个Feign接口,并添加 @FeignClient 注解:
@FeignClient(name = "eureka-client-producer")
public interface UserFeignClient {

    @RequestMapping("userInfo")
    String queryUserInfo();
}
@FeignClient中的name是被调用生产者的名称,用于创建Ribbon负载均衡器;
还可以使用url属性指定请求的URL(URL可以是完成的URL或主机名)。
  • 修改Controller代码,让其调用Feign接口:
@RestController
public class TestController {
    @Autowired
    private UserFeignClient userFeignClient;
    
    @GetMapping("invokeMethod")
    public void invokeMethod() {
        String s = userFeignClient.queryUserInfo();
        System.out.println(s);
    }
}

自定义Feign配置

很多场景下,我们需要自定义Feign的配置,例如配置日志级别、定义拦截器等。

使用Java代码配置

在Spring Cloud中,Feign的默认配置类是FeignClientsConfiguration,该类定义了Feign默认使用的编码器、解码器、所使用的契约。
Spring Cloud允许通过注解 @FeignClient 的configuration属性自定义Feign的配置,自定义配置的优先级比FeignClientsConfiguration要高。
Srping Cloud为Feign提供的默认配置:
org.springframework.cloud.openfeign.FeignClientsConfiguration.java

BeanTypebeanNameClassName
DecoderfeignDecoderResponseEntityDecoder
EncoderfeignEncoderSpringEncoder
ContractfeignContractSpringMvcContract
配置指定名称的Feign Client

Feign使用的默认契约是 SpringMvcContract ,因此它可以使用Spring MVC注解。使用自定义Feign配置来进行工作:

public class FeignConfiguration {

   /**
    * 将契约改为Feign原生的默认契约。这样就可以使用Feign自带的注解了。
    * @return feign的默认契约
    * @return
    */
   @Bean
   public Contract feignContract() {
       return new Contract.Default();
   }
}

该类无需添加 @Configuration 注解,如果加了此注解,那么该类不能存放在主应用程序上下文@ComponentScan所扫描的包中。否则该类中的配置就会被所有的@FeignClient共享。

修改Feign接口

使用 @FeignClient 的configuration属性指定配置类,同时,将queryUserInfo上的Spring MVC的注解修改为Feign自带的注解:

@FeignClient(name = "eureka-client-producer",configuration = FeignConfiguration.class)
public interface UserFeignClient {

    @RequestLine("GET /userInfo")
    public String queryUserInfo();
}
全局配置

使用启动类上的 @EnableFeignClients 提供的 defaultConfiguration 用来指定默认的配置类:

@SpringBootApplication
@EnableFeignClients(defaultConfiguration = FeignConfiguration.class)
public class EurekaClientConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaClientConsumerApplication.class, args);
    }

}

使用配置文件配置

使用属性自定义配置比Java代码配置的方式更加方便,并且优先级更高。想让Java代码的优先级更高需要增加属性 feign.client.default-to-properties=false

feign:
  client:
    config:
      # 指定Feign Client的名称,也就是生产者的名称
      eureka-client-producer:
        # 日志级别
        loggerLevel: full
        # 连接超时时间 java.net.HttpURLConnection#getConnectTimeout(),如果使用Hystrix,该配置无效
        connectTimeout: 5000
        # 读取超时时间  java.net.HttpURLConnection#getReadTimeout(),如果使用Hystrix,该配置无效
        readTimeout: 5000
        # 配置契约 默认是org.springframework.cloud.openfeign.support.SpringMvcContract
        contract: feign.Contract.Default
        # 重试接口实现类,默认实现 feign.Retryer.Default
        #retryer:
        # 配置拦截器,相当于代码配置方式中的RequestInterceptor
        #requestInterceptors
        # 是否开启404编码
        #decode404
通用配置

配置所有的Feign Client,需要将指定的Feign Client名称更改成default:

feign:
  client:
    config:
      # 配置所有的Feign Client
      default:
        # 连接超时时间 java.net.HttpURLConnection#getConnectTimeout(),如果使用Hystrix,该配置无效
        connectTimeout: 5000

手动创建Feign

修改生产者

  1. 增加security依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 创建Spring Security配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //所有的请求,都需要经过HTTP basic认证
        http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        //明文编码器。这是一个不做任何操作的密码编码器,是Spring提供测试用的。
        return NoOpPasswordEncoder.getInstance();
    }

    @Autowired
    private CustomUserDetailService customUserDetailService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(this.customUserDetailService).passwordEncoder(NoOpPasswordEncoder.getInstance());
    }

    @Component
    class CustomUserDetailService implements UserDetailsService{
        /**
         * 模拟两个账户acc1和acc2
         * @param s
         * @return
         * @throws UsernameNotFoundException
         */
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            if ("acc1".equals(s)) {
                return new SecurityUser("acc1", "pwd1", "role1");
            } else if ("acc2".equals(s)) {
                return new SecurityUser("acc2", "pwd2", "role2");
            } else {
                return null;
            }
        }
    }

    class SecurityUser implements UserDetails{
        private static final long serialVersionUID = 8121723121091944732L;

        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 SecurityUser() {
        }

        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(this.role);
            authorities.add(simpleGrantedAuthority);
            return authorities;
        }

        @Override
        public String getPassword() {
            return password;
        }

        @Override
        public String getUsername() {
            return 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;
        }
        //省略getter和setter方法
    }

}

不明白Spring Security配置可参考我的另一篇博客:Spring Boot学习笔记-Spring Security(一)

  1. 修改Controller
@RestController
public class UserController {

    @GetMapping("mockUser")
    public String mockUser() {
        String jsonStr = "";
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            UserDetails user = (UserDetails) principal;
            Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
            System.out.println(authorities);
            jsonStr = "'status':'success'";
        } else {
            System.out.println("权限认证失败");
            jsonStr = "'status':'fail'";
        }
        return jsonStr;
    }
}

启动服务,访问链接出现微服务对话框即可。

修改消费者

  1. 去掉Feign接口UserFeignClient上的 @FeignClient 注解
public interface UserFeignClient {

    @RequestLine("GET /userInfo")
    public String queryUserInfo();
}
  1. 去掉启动类上的 @EnableFeignClients 注解
  2. 修改Controller代码
@Import(FeignClientsConfiguration.class)
@RestController
public class TestController {

    private UserFeignClient acc1UserFeignClient;

    private UserFeignClient acc2UserFeignClient;

    public TestController(Client client, Encoder encoder, Decoder decoder, Contract contract) {
        this.acc1UserFeignClient = Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract)
                .requestInterceptor(new BasicAuthRequestInterceptor("acc1", "pwd1"))
                .target(UserFeignClient.class, "http://eureka-client-producer/");
        this.acc2UserFeignClient = Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract)
                .requestInterceptor(new BasicAuthRequestInterceptor("acc2", "pwd2"))
                .target(UserFeignClient.class, "http://eureka-client-producer/");
    }

    @GetMapping("invokeMethod")
    public void invokeMethod() {
        String s = acc1UserFeignClient.queryUserInfo();
        System.out.println("acc1="+s);
        s = acc2UserFeignClient.queryUserInfo();
        System.out.println("acc2="+s);
    }

}
  • @Import 注解导入的FeignClientsConfiguration是Spring Cloud为Feign默认提供的配置类

Feign的日志

每个Feign客户端都会创建一个logger,logger的名称是Feign接口的完整类名。注意:Feign的日志打印只会对DEBUG级别做出响应。

可为每个Feign客户端配备的Logger.Level:

  • NONE:不记录任何日志(默认值)
  • BASIC:仅记录请求方法、URl、响应状态代码以及执行时间
  • HEADERS:记录BASIC级别的基础上,记录请求和响应的header
  • FULL:记录请求和响应header、body和元数据

编码方式配备级别

  1. 编写Feign配置类:
@Configuration
public class FeignLogConfiguration {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

}
  1. 修改Feign的接口,指定配置类:
@FeignClient(name = "eureka-client-producer",configuration = FeignLogConfiguration.class)
public interface UserFeignClient {

    @GetMapping("userInfo")
    String queryUserInfo();
}
  1. 在配置文件中添加以下内容,指定Feign接口的日志级别为DEBUG:
logging:
  level:
    #将Feign接口的日志级别设置成DEBUG,因为Feign的Logger.Level只对DEBUG做出响应
    com.clouddemo.eurekaclientconsumer.service.UserFeignClient: DEBUG

使用属性配置日志级别

feign:
  client:
    config:
      eureka-client-producer:
        loggerLevel: full
logging:
  level:
    #将Feign接口的日志级别设置成DEBUG,因为Feign的Logger.Level只对DEBUG做出响应
    com.clouddemo.eurekaclientconsumer.service.UserFeignClient: DEBUG

使用Feign构造多参数请求

GET请求多个参数

  • 方式一
    使用多个 @RequestParam 注解
@FeignClient(name = "eureka-client-producer")
public interface UserFeignClient {

    @GetMapping("userInfo")
    String queryUserInfo(@RequestParam("id") Long id,@RequestParam("username") String username);
}
  • 方式二
    参数较多时,使用Map构建
@FeignClient(name = "eureka-client-producer")
public interface UserFeignClient {

    @GetMapping("userInfo")
    String queryUserInfo(@RequestParam Map<String,Object> map);
}

POST请求包含多个参数

生产者需要的参数:

@PostMapping("userInfo")
public void userInfo(@RequestBody User user) {
    //TODO
}

消费者请求参数:

@PostMapping("userInfo")
String queryUserInfo(@RequestBody User user);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值