Spirng Cloud Feign[‘装作’–“安装’工作区’作业”] WebService客户端,目的:是让WebService调用更加简单。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
@SpringBootApplication
@EnableFeignClients <!--该注解表示当程序启动时,会进行包扫描所有带@EnableFeign注解的类并处理-->
public class SpringCloudFeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFeignApplication.class, args);
}
}
@FeignClient(name = "github-client",
url = "https://api.github.com", configuration = HelloFeignServiceConfig.class)
<!--表示手动指定 url=“调用地址/远程服务地址”-->
public interface HelloFeignService {
@RequestMapping(value = "/search/repositories", method = RequestMethod.GET)
String searchRepo(@RequestParam("q") String queryStr);
}
<!--该方法最终请求地址是:远程服务地址/方法定义路径?方法定义参数=请求参数值-->
<!--该方法最终请求地址是:https://api.github.com/search/repositories?q=spring-cloud-dubbo-->
@RestController(控制层)
public class HelloFeignController {
@Autowired
private HelloFeignService helloFeignService;
// 服务消费者对位提供的服务
@GetMapping(value = "/search/github")
public String searchGithubRepoByStr(@RequestParam("str") String queryStr) {
return helloFeignService.searchRepo(queryStr);
}
}
该类==class HelloFeignController==是,服务消费者类 ==searchGithubRepoByStr()== 消费者请求方法
该接口==interface HelloFeignService==是,服务提供者-定义指定地址中间组件/中间请求的‘钥匙’/Fegin组件
步骤:
1-主程序入口添加 @EnableFeignClients 注解开启对 @FeignClient 扫描加载处理。
2-扫描所有 @FeignClient的注解的接口类,并注入SpringIOC容器中。
3-其接口类中的方法被调用是,是通过JDK的代理方式, 生成具体的RequestTemplate(请求范本)对象。1
4-然后有RequestTemplate生成Request,然后Request交给Client去处理。2
5-最后Client被封装到LoadBalanceClient类,该类结合Ribbon负载均衡发起服务之间的调用。
@Target(ElementType.TYPE) --@目标(元素类型.TYPE) 标识着被注释的interface接口作用目标在接口上。
@Retention(RetentionPolicy.RUNTIME) --保留@(保留政策.RUNTIME[运行时]-可被反射读取)
@Documented --备有证明文件的(声明式)
public @interface FeignClient {
@AliasFor("name")
String value() default "";
@Deprecated
String serviceId() default "";
@AliasFor("value")
String name() default "";
String qualifier() default ""
String url() default "";
-1. 一般用于调试,可以手动指定 @FeignClient调用地址。
boolean decode404() default false;
-1.-DeCode404-:当发生404错误❌时,如果该字段为true,
-2.会调用-DeCode-进行解码,否则抛出 FeignException 异常。
Class<?>[] configuration() default {
};
-1. Feign配置类
-2. 可以自定义 Feign的:
-2-1.Encoder.(编码器)
-2-2.Decoder.(解码器)
-2-3.LogLevel.(日志级别)
-2-4.Contract.(协议)
-2-5.以上属性值状态。
Class<?> fallback() default void.class;
-1. 定义容错的处理类:
-2. 当调用远程接口失败或者超时时,会调用对应的容错逻辑.
-3 fallback指定的类必须实现 @FeignClient 标识的接口。
Class<?> fallbackFactory() default void.class;
-1. 工厂类:-回退动作工厂-
-2. 用户生成-fakkBack类-示例;
-3. 通过这个属性可以实现每个接口通用的容错逻辑,减少重复的代码。
String path() default "";
-1.-path-定义当前 FeignClient的统一前缀。
boolean primary() default true;
-- 剩下的字段标识的意思,等大伙儿开问嘞 --
}
Feign 组件开启 ‘霸气模式’ 哈~~~
1-支持对Request请求和Response响应-TransferInfo传输信息进行GZIP压缩,提高通信效率。
-上代码
feign:
compression:
request:
enabled: true
# 配置压缩支持的MIME TYPE
mime-types: text/xml,application/xml,application/json
min-request-size: 2048 # 配置压缩数据大小的下限
response:
enabled: true # 配置响应GZIP压缩
开启GZIP压缩之后,Feign之间的调用通过二进制协议进行Transfer传输,其返回值需要修改为ResposeEntity
才可以正常显示。代码
@FeignClient(name = "github-client", url = "https://api.github.com",
configuration = HelloFeignServiceConfig.class)
public interface HelloFeignService {
@RequestMapping(value = "/search/repositories", method = RequestMethod.GET)
ResponseEntity<byte[]> searchRepo(@RequestParam("q") String queryStr);
}
-看配置信息文件
feign:
client:
config:
feignName: #需要配置的FeignName
connectTimeout: 5000 #连接超时时间
readTimeout: 5000 #读超时时间设置
looggerLevel: full #配置Feign的日志级别
errorDecoder: com.example.SimpleErrorDecoder #Feign的错误❌解码器
retryer: com.example.SimpleRetryer #配置重试
requestInterceptors: -com.example.FooRequestInterceptor
-com.example.BarRequestInterceptor #配置拦截器
decode404: false
encoder: com.example.SimpleEncoder # Feign的编码器
decoder: com.example.SimpleDecoder # Feign的解码器
contract: com.example,SimpleContract #Feign的Contract配置
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
........
Class<?>[] defaultConfiguration() default {
};
-1. 使用-@EnableFeignClients-注解类中的该属性,
-2. 可以将默认配置写成一个类--....
-3. 比如:-创建配置类 'DefaultFeignConfiguration.java',
-4. 然后,在主程序的启动入口使用 defaultConfiguration()来引用-自定义配置类;
.........
}
– 展示主程序类上 ‘Annotation注解’
@SpringBootApplication
@EnableFeignClients(defaultConfiguration=DefaultFeignConfiguration.class)
public class SpringCloudFeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFeignApplication.class, args);
}
}
- 兜兜转转一圈二,大家该理解了吧!!!
feign:
client:
config:
defailt:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
注意 ⚠️⚠️⚠️:如果通过Java代码配置过Feign设置属性类,然后又通过属性文件的方式配置Feign;
| | | | | | | | | | 后者一定会覆盖前者的设置的,但可以配置feign.client.defult-to-properties=false,
| | | | | | | | | | 来改变Feign设置的生效的优先级。
-Feign 为每一个FeignClient都提供来一个 ‘Feign.Logger’ 实例,可以在配置中开启日志。
-步骤1:
'-在application.yml-中设置日志输出级别'
logging:
level:
cn.springcloud.book.feign.service.HelloFeignService: debug
-步骤2:
-1- 通过创建带有-@Configuration-注解的配置类,
-2- 去配置Bean,代码下面
@Configuration
public class HelloFeignServiceConfig {
/**
*
* Logger.Level 的具体级别如下:
NONE:不记录任何信息
BASIC:仅记录请求方法、URL以及响应状态码和执行时间
HEADERS:除了记录 BASIC级别的信息外,还会记录请求和响应的头信息
FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据
* @return
*/
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
-Feign的设置分两层,即Ribbon(客户端负载均衡)和Hystrix(熔断器)的调用,Hystrix默认是关闭的。
-当Feign和Ribbon整合了Hystrix之后,可能会出现首次调用失败问题,
-造成问题出现的原因分析如下:
–1.Hystrix 默认的超时时间是1秒.超过时间未做出响应,将进入FallBack(后退动作)代码。
–2.由于Bean的装配以及懒加载机制等,Feign首次请求都会比较慢。
–3.解决方式:(1)将Hystrix的超时时间调整大,例如- 5 秒
--4. hystrix.command.default.exexcution.isolation.thread.timeoutlnMilliseconds:5000
–5.(2)禁用超时时间
--6.hystrix.command.default.execution.timeout.enabled:false
–7 (3)使用Feign的时候直接关闭Hystrix,该方式不推荐使用
–8.feign.hystrix.enabled.false
1)Ribbon超时设置:
ribbon.ReadTimeout: 120000 # 设置请求处理的超时时间
ribbon.ConnectTimeout:30000 # 设置请求连接的超时时间
> 2)Hystrix超时设置:
feign.hystrix.enabled: truehystrix #熔断机制
hystrix:
shareSecurityContext: true
command:
default:
circuitBreaker:
sleepWindowInMillisecodes: 100000
forceClosed: true
execution:
isolation:
thread:
timeoutInMillisecods: 600000
Feigin 在默认情况下使用的JDK原生的Client 2,没有连接池的。
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
dependency>
<dependency>
<groupId>com.netflix.feigngroupId>
<artifactId>feign-httpclientartifactId>
<version>8.17.0version>
dependency>
feign:
httpclient:
enabled: true
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-okhttpartifactId>
dependency>
feign:
httpclient:
enabled: false
okhttp:
enabled: true
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class)
public class FeignOkHttpConfig {
@Bean
public okhttp3.OkHttpClient okHttpClient(){
return new okhttp3.OkHttpClient.Builder()
//设置连接超时
.connectTimeout(60, TimeUnit.SECONDS)
//设置读超时
.readTimeout(60, TimeUnit.SECONDS)
//设置写超时
.writeTimeout(60,TimeUnit.SECONDS)
//是否自动重连
.retryOnConnectionFailure(true)
.connectionPool(new ConnectionPool())
//构建OkHttpClient对象
.build();
}
}
大家儿都知道,在Web开发中Spring MVC 是支持Get()方法直接绑定POJO(实体类)的,
最常见的解决方式如下:
1- 把POJO(实体类)拆散成一个一个单独的属性放在方法参数里。
2- 把方法参数变成Map传递。
3- 使用Get传递@RequesBody,但此方式违反Restful规范。
--------------------------------------------------------------------------- 666
下面我使用Feign拦截器的方式处理–:
工程名 | 端口 | 描述 |
---|---|---|
R6-1-Eureka-Server | 8761 | 注册中心 |
R6-1-Consumer | 8011 | GET或POST 请求多个参数传递的服务消费者,集成了Swagger用于测试 |
R6-1-Provider | 8012 | GET或POST 请求多参数传递的服务提供者 |
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.5.0version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.5.0version>
dependency>
<!-- R6-1-Consumer 工程 application.yml-->
server:
port: 8011
spring:
application:
name: r6-1-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
<!-- R6-1-Consumer 工程 配置Swagger Config类集-->
(1)
@Configuration
@EnableWebMvc
public class ApplicationExceptionAdapter extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
(2)
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors
.basePackage("cn.springcloud.book.feign.controller"))
.paths(PathSelectors.any()).build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("Feign多参数传递问题").description("Feign多参数传递问题")
.contact("[email protected]").version("1.0").build();
}
}
<!-- R6-1-Consumer 工程 自定义拦截器类-->
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Autowired
private ObjectMapper objectMapper;
@Override
public void apply(RequestTemplate template) {
// feign 不支持 GET 方法传 POJO, json body转query
if (template.method().equals("GET") && template.body() != null) {
try {
JsonNode jsonNode = objectMapper.readTree(template.body());
template.body(null);
Map<String, Collection<String>> queries = new HashMap<>();
buildQuery(jsonNode, "", queries);
template.queries(queries);
} catch (IOException e) {
//提示:根据实践项目情况处理此处异常,这里不做扩展。
e.printStackTrace();
}
}
}
}
<!-- R6-1-Consumer 工程 Controller层-->
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserFeignService userFeignService;
/*** 用于演示Feign的Get请求多参数传递 * @param user * @return */
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String addUser( @RequestBody
@ApiParam(name="用户",value="传入json格式",required=true) User user){
return userFeignService.addUser(user);
}
/** * 用于演示Feign的Post请求多参数传递 * @param user * @return*/
@RequestMapping(value = "/update", method = RequestMethod.POST)
public String updateUser( @RequestBody
@ApiParam(name="用户",value="传入json格式",required=true) User user){
return userFeignService.updateUser(user);
}
}
<!-- R6-1-Consumer 工程 Feign远程调用服务层-->
@FeignClient(name = "R6-1-Provider ")
public interface UserFeignService {
@RequestMapping(value = "/user/add", method = RequestMethod.GET)
public String addUser(User user);
@RequestMapping(value = "/user/update", method = RequestMethod.POST)
public String updateUser(@RequestBody User user);
}
<!-- R6-1-Consumer 工程 POJO实体类-->
public class User {
private Long id;
private String name;
private int age;
public Long getId() {
return id;}
public void setId(Long id) {
this.id = id;}
public String getName() {
return name;}
public void setName(String name) {
his.name = name;}
public int getAge() {
return age;{
}
public void setAge(int age) {
this.age = age;}
}
<!-- R6-1-Provider 工程 application.yml-->
server:
port: 8012
spring:
application:
name: r6-1-provider
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#eureka.instance.prefer-ip-address 表示将自己的IP注册到Eureka Server上,
#如果不配置,会将当前服务提供者所在的主机名注册到Eureka Server上。
instance:
prefer-ip-address: true
<!-- R6-1-Provider 工程 Controller层-->
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String addUser(User user , HttpServletRequest request){
String token=request.getHeader("oauthToken");
return "hello,"+user.getName();
}
@RequestMapping(value = "/update", method = RequestMethod.POST)
public String updateUser( @RequestBody User user){
return "hello,"+user.getName();
}
}
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcatartifactId>
<groupId>org.springframework.bootgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-undertowartifactId>
dependency>
<dependency>
<groupId>io.undertowgroupId>
<artifactId>undertow-servletartifactId>
dependency>
<!-- R6-1-Eureka-Server 工程 bootstrap.yml -->
server:
port: 8761
eureka:
instance:
hostname: localhost
server :
enable-self-preservation: false
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${
eureka.instance.hostname}:${
server.port}/eureka/
工程名 | 端口 | 描述 |
---|---|---|
R7-1-Eureka-Server | 8761 | 注册中心(之前上面自己找) |
R7-1-Feign-File-Server | 8012 | 模拟文件服务器,服务提供者 |
R7-1-Feign-Upload-Client | 8011 | 模拟文件表单上传,通过FeignClient发送文件到文件服务器(集成了Swagger 上面代码有) |
<dependency>
<groupId>io.github.openfeign.formgroupId>
<artifactId>feign-formartifactId>
<version>3.0.3version>
dependency>
<dependency>
<groupId>io.github.openfeign.formgroupId>
<artifactId>feign-form-springartifactId>
<version>3.0.3version>
dependency>
<!-- R7-1-Feign-Upload-Client 工程 Service层-->
@FeignClient(value = "feign-file-server",
configuration = FeignMultipartSupportConfig.class)
public interface FileUploadFeignService {
/**** 1.produces,consumes必填
* 2.注意区分@RequestPart和RequestParam,不要将
* * @RequestPart(value = "file") 写成@RequestParam(value = "file")
* * @param file* @return*/
@RequestMapping(method = RequestMethod.POST, value = "/uploadFile/server",
produces = {
MediaType.APPLICATION_JSON_UTF8_VALUE},
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String fileUpload(@RequestPart(value = "file") MultipartFile file);
}
<!-- R7-1-Feign-Upload-Client 工程 Controller层-->
@RestController
@Api(value="文件上传")
@RequestMapping("/feign")
public class FeignUploadController {
@Autowired
private FileUploadFeignService fileUploadFeignService;
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiOperation(value = "文件上传", notes = "请选择文件上传" )
public String imageUpload(@ApiParam(value="文件上传",required = true)
MultipartFile file ) throws Exception{
return fileUploadFeignService.fileUpload(file);
}
}
<!-- R7-1-Feign-Upload-Client 工程 Config层-->
/*** Feign文件上传Configuration*/
@Configuration
public class FeignMultipartSupportConfig {
@Bean
@Primary
@Scope("prototype")
public Encoder multipartFormEncoder() {
return new SpringFormEncoder();
}
}
-1 在进行认证鉴权的时候,不管是JWT认证 3 ,还是 Spring Security(安全性) 4 验证。
-2 当使用Feign时就会发现外部请求到A服务的时候,A服务是可以拿到Token的,
-3 然而当服务使用Feign调用是,Token就会小时,从而认证失败。
-4 解决方案:需要做当就是Feign调用时候,向Header(请求头)添加需要传递的Toten。
<!-- 首先需要实现Feign提供的一个接口RequestInterceptor,假设我们在验证权限的时候
放在请求头里面的key为oauthToken,先获取当前请求中的key为oauthToken的TOken,
然后放到Feign的请求Header上。-->
/*** Feign统一Token拦截器*/
@Component
public class FeignTokenInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
if(null==getHttpServletRequest()){
//此处省略日志记录
return;
}
//将获取Token对应的值往下面传
requestTemplate.header("oauthToken",
getHeaders(getHttpServletRequest()).get("oauthToken"));
}
private HttpServletRequest getHttpServletRequest() {
try {
return ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
} catch (Exception e) {
return null;
}
}
/** * Feign拦截器拦截请求获取Token对应的值
* @param request * @return */
private Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
}
小结:哼~ 哼~ 哼 ! ! ! 总算将它‘码’完了。乐意为‘码’同志 解答噢 !!!