文章目录
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
BeanType | beanName | ClassName |
---|---|---|
Decoder | feignDecoder | ResponseEntityDecoder |
Encoder | feignEncoder | SpringEncoder |
Contract | feignContract | SpringMvcContract |
配置指定名称的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
修改生产者
- 增加security依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 创建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(一)
- 修改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;
}
}
启动服务,访问链接出现微服务对话框即可。
修改消费者
- 去掉Feign接口UserFeignClient上的
@FeignClient
注解
public interface UserFeignClient {
@RequestLine("GET /userInfo")
public String queryUserInfo();
}
- 去掉启动类上的
@EnableFeignClients
注解 - 修改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和元数据
编码方式配备级别
- 编写Feign配置类:
@Configuration
public class FeignLogConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
- 修改Feign的接口,指定配置类:
@FeignClient(name = "eureka-client-producer",configuration = FeignLogConfiguration.class)
public interface UserFeignClient {
@GetMapping("userInfo")
String queryUserInfo();
}
- 在配置文件中添加以下内容,指定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);