文章目录
- 一、OpenFeign简介
- 二、Springboot集成OpenFeign
- 1、引入依赖
- 2、@EnableFeignClients注解
- (1)应用
- (2)属性解析
- 3、 @FeignClient
- (1)应用
- (2)属性解析
- (3)向Fegin客户端提供URL的几种方式
- 三、openFegin应用
- 1、使用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*/@GetMappingBizBaseResponse<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()替代。@DeprecatedString qualifier() default "";// 模拟客户端的@Qualifiers值。如果qualifier()和qualifiers()都存在,我们将使用后者,除非qualifier()返回的数组为空或只包含空值或空白值,在这种情况下,我们将首先退回到qualifier(),如果也不存在,则使用default = contextId + "FeignClient"。String[] qualifiers() default {};// 绝对URL或可解析主机名String url() default "";// 是否应该解码404而不是抛出FeignExceptionsboolean 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();}