Spring Cloud: openFegin使用

一、OpenFeign简介

OpenFeign 利用 Ribbon 维护了远程服务的列表信息,以实现客户端侧的负载均衡。

二、Springboot集成OpenFeign

1、引入依赖

<!--openfeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2、@EnableFeignClients注解

(1)应用

在使用openfegin时需要再启动类上加上改注解,以启用 OpenFeign。

@SpringBootApplication
@EnableFeignClients
public class Application {

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

(2)属性解析

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
	String[] value() default {};
	
	String[] basePackages() default {};
	
	Class<?>[] basePackageClasses() default {};
	
	Class<?>[] defaultConfiguration() default {};
	
	Class<?>[] clients() default {};
}

  • basePackages 或 value
    这两个属性是等价的,用于指定扫描 Feign 客户端接口的包路径。Spring Cloud 会自动扫描这些包下的接口,并为它们创建代理实现,这些代理会实现声明的 Feign 客户端接口。
@EnableFeignClients(basePackages = "com.example.demo.client")

@EnableFeignClients(value = "com.example.demo.client")
  • defaultConfiguration
    这个属性允许你指定一个默认的配置类,该类会应用于所有 Feign 客户端,除非它们有自己特定的配置。
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
  • clients
    这个属性允许你明确地指定哪些接口应该作为 Feign 客户端来创建。这在你不想扫描整个包,而只想为特定的接口创建 Feign 客户端时非常有用。
@EnableFeignClients(clients = {MyServiceClient.class, AnotherServiceClient.class})
  • basePackageClasses
    @EnableFeignClients 注解中的 basePackageClasses 属性是一个替代 basePackages 或 value 的方式,用于指定扫描 Feign 客户端接口的基准类。Spring Cloud 会扫描这些基准类所在的包及其子包,以查找标有 @FeignClient 注解的接口,并为这些接口创建代理实现。

使用 basePackageClasses 而不是直接指定包路径的好处是,它提供了类型安全的方式来定义扫描的包。你只需提供一个位于所需包中的类的 Class 对象,Spring Cloud 就会自动确定该类的包路径,并扫描该包及其子包。

下面是一个使用 basePackageClasses 的例子:

@SpringBootApplication  
@EnableFeignClients(basePackageClasses = MyFeignClient.class)  
public class DemoApplication {  
  
    public static void main(String[] args) {  
        SpringApplication.run(DemoApplication.class, args);  
    }  
  
}

在这个例子中,假设 MyFeignClient 接口位于 com.example.demo.client 包中。通过指定 MyFeignClient.class 作为 basePackageClasses 的值,Spring Cloud 将扫描 com.example.demo.client 包及其所有子包,查找带有 @FeignClient 注解的接口。

这样做的好处是,即使你的包结构发生变化(例如,重命名包),只要 MyFeignClient 接口仍然位于相同的包中,你就不需要更新 @EnableFeignClients 注解。因此,它提供了一种更灵活的方式来指定扫描的包,减少了由于包路径更改而导致的维护工作量。

需要注意的是,basePackageClasses 和 basePackages/value 是互斥的,你不应该同时指定它们。你应该选择其中一种方式来定义扫描的包。通常,如果你的应用结构较为简单,直接指定包路径可能更直接。而在更复杂的场景中,使用 basePackageClasses 可能会更加灵活和易于维护。

3、 @FeignClient

(1)应用

定义一个接口,并使用 @FeignClient 注解指定远程服务的名称。

import feign.Response;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.validation.constraints.NotBlank;
import java.util.List;
@FeignClient(name = "xxx", contextId = "FileServiceAPI", path = url)
@Validated
public interface FileServiceAPI {

    /**
     * 获取文件详情接口
     * @param code
     * @return
     */
    @GetMapping
    BizBaseResponse<FileInfoVo> getFileDetail(@NotBlank @RequestParam(name = "code") String code);

    @GetMapping("files")
    BizBaseResponse<List<FileInfoVo>> getFileDetails( @RequestParam(name = "codes") List<String> codes);

    /**
     * 文件下载接口
     * @param code
     */
    @GetMapping("/download")
    Response download(@RequestParam(name = "code") String code);

(2)属性解析

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {

	// name和value属性用于标注客户端名称,也可以用${propertyKey}获取配置属性
	@AliasFor("name")
	String value() default "";

	// 该类的Bean名称
	String contextId() default "";

	// name和value属性用于标注客户端名称,也可以用${propertyKey}获取配置属性
	@AliasFor("value")
	String name() default "";

	// 弃用 被qualifiers()替代。
	@Deprecated
	String qualifier() default "";

	// 模拟客户端的@Qualifiers值。如果qualifier()和qualifiers()都存在,我们将使用后者,除非qualifier()返回的数组为空或只包含空值或空白值,在这种情况下,我们将首先退回到qualifier(),如果也不存在,则使用default = contextId + "FeignClient"。
	String[] qualifiers() default {};

	// 绝对URL或可解析主机名
	String url() default "";

	// 是否应该解码404而不是抛出FeignExceptions
	boolean decode404() default false;

	// 用于模拟客户端的自定义配置类。可以包含组成客户端部分的覆盖@Bean定义,默认配置都在FeignClientsConfiguration类中,可以指定FeignClientsConfiguration类中所有的配置
	Class<?>[] configuration() default {};

	// 指定失败回调类
	Class<?> fallback() default void.class;

	// 为指定的假客户端接口定义一个fallback工厂。fallback工厂必须生成fallback类的实例,这些实例实现了由FeignClient注释的接口。
	Class<?> fallbackFactory() default void.class;

	// 所有方法级映射使用的路径前缀
	String path() default "";

	// 是否将虚拟代理标记为主bean。默认为true。
	boolean primary() default true;
}

(3)向Fegin客户端提供URL的几种方式

  • 使用@FeignClient注解的url属性
    这是最直接的方式。你可以在@FeignClient注解中直接指定URL。这样,Feign客户端就会使用这个URL来进行请求。
@FeignClient(name = "myServiceClient", url = "http://example.com/api")  
public interface MyServiceClient {  
    // 定义你的方法  
}

在上面的例子中,MyServiceClient接口将使用http://example.com/api作为请求的基础URL。没有负载均衡能力,只能定向发送。

  • 使用配置文件:
    你可以在应用的配置文件(如application.yml或application.properties)中设置Feign客户端的URL。然后,你可以通过@Value注解或者@ConfigurationProperties来注入这个URL到Feign客户端的配置中。
# application.yml  
my-service-client:  
  url: http://example.com/api
@FeignClient(name = "myServiceClient", configuration = MyServiceClientConfiguration.class)  
public interface MyServiceClient {  
    // 定义你的方法  
}  

@Configuration  
public class MyServiceClientConfiguration {  

    @Value("${my-service-client.url}")  
    private String url;  

    @Bean  
    public Request.Options options() {  
        return new Request.Options(connectTimeoutMillis(), readTimeoutMillis());  
    }  

    private int connectTimeoutMillis() {  
        return 10 * 1000;  
    }  

    private int readTimeoutMillis() {  
        return 60 * 1000;  
    }  
}

在这个例子中,MyServiceClientConfiguration类从配置文件中读取URL,并可能还配置了其他的Feign请求选项。没有负载均衡能力,只能定向发送,优先使用注解中的url。

  • 使用服务发现
    如果你的应用运行在Spring Cloud环境中,并使用了服务发现(如Eureka或Consul),那么通常不需要在Feign客户端中直接指定URL。相反,你可以通过服务名称来引用远程服务,Spring Cloud会自动解析服务名称到实际的URL。
@FeignClient(name = "my-service")  
public interface MyServiceClient {  
    // 定义你的方法  
}

在这个例子中,my-service是注册在服务发现中的服务名称。当发起请求时,Spring Cloud会查询服务发现来找到my-service的实例,并使用它们的URL进行请求。

三、openFegin应用

1、使用openfegin下载文件

(1)下载文件Controler实现

import org.springframework.core.io.FileSystemResource;  
import org.springframework.core.io.Resource;  
import org.springframework.http.HttpHeaders;  
import org.springframework.http.MediaType;  
import org.springframework.http.ResponseEntity;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  
  
import javax.servlet.http.HttpServletResponse;  
import java.io.IOException;  
import java.io.InputStream;  
import java.nio.file.Files;  
import java.nio.file.Path;  
import java.nio.file.Paths;  
  
@RestController  
public class FileDownloadController {  
  
    @GetMapping("/download")  
    public void downloadFile(HttpServletResponse response) throws IOException {  
        // 文件路径  
        String filePath = "path/to/your/file.txt";  
  
        // 创建文件路径  
        Path path = Paths.get(filePath);  
        Resource resource = new FileSystemResource(path);  
  
        // 获取文件名  
        String filename = resource.getFilename();  
  
        // 设置响应头  
        response.reset();
       	response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);  
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"");  
  
        // 读取文件内容并写入响应输出流  
        try (InputStream inputStream = Files.newInputStream(path);  
             OutputStream outputStream = response.getOutputStream()) {  
  
            byte[] buffer = new byte[1024];  
            int bytesRead;  
            while ((bytesRead = inputStream.read(buffer)) != -1) {  
                outputStream.write(buffer, 0, bytesRead);  
            }  
  
            outputStream.flush();  
        }  
    }  
}

(2)openfegin配置

import feign.Response;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.validation.constraints.NotBlank;
import java.util.List;
@FeignClient(name = "xxx", contextId = "FileServiceAPI", path = url)
@Validated
public interface FileServiceAPI {
    /**
     * 文件下载接口
     * @param code
     */
    @GetMapping("/download")
    Response download();

其中Response引用的是:feign.Response

(3)调用openfegin接口方法

 public File downFileByCode() {
        String path = PROJECT_PATH;
        Response response = fileServiceAPI.download(ossFileCode);
        mkDirs(path);
        File file = new File(path);
        if (response.status() == 200) {
            try (InputStream inputStream = response.body().asInputStream();
                 FileOutputStream outputStream = new FileOutputStream(file)) {
                // 将响应体中的字节写入目标文件
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            } catch(Exception e) {
                LOGGER.error(e.getMessage(), e);
                throw new RunTimeException(e.getMessage());
            }
        }
        return file;
    }
    
    public static String mkDirs(String filePath) {
        if (StringUtils.isBlank(filePath)) {
            throw new RuntimeException("file path is null");
        }
        if (!filePath.endsWith(File.separator)) {
            LOGGER.info("mkdir , file path is {}", filePath);
            filePath = filePath.substring(0, filePath.lastIndexOf(File.separator));
        }

        //如果目录不存在,自动创建文件夹
        File dir = new File(filePath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return dir.getAbsolutePath();
    }
  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玉成226

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值