Feign
Feign是声明式Web Service客户端,支持REST和SOAP Service,简化了Web Service客户端开发。
为使用Feign仅需创建一个接口并添加注解。
调用GitHub REST Service
public interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List contributors(@Param("owner") String owner, @Param("repo") String repo);
@RequestLine("POST /repos/{owner}/{repo}/issues")
void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);
}
@Data
public class Contributor {
private String login;
private int contributions;
}
@Data
public class Issue {
private String title;
private String body;
private List assignees;
private int milestone;
private List labels;
}
public class FeignExample {
public static void main(String... args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
// Fetch and print a list of the contributors to this library.
List contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.getLogin() + " (" + contributor.getContributions() + ")");
}
}
}
调用SOAP Service
public interface MyApi {
@RequestLine("POST /getObject")
@Headers({
"SOAPAction: getObject",
"Content-Type: text/xml"
})
MyJaxbObjectResponse getObject(MyJaxbObjectRequest request);
}
public class SoapExample {
public static void main(String[] args) {
JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
.withMarshallerJAXBEncoding("UTF-8")
.withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
.build();
MyApi api = Feign.builder()
.encoder(new SOAPEncoder(jaxbFactory))
.decoder(new SOAPDecoder(jaxbFactory))
.errorDecoder(new SOAPErrorDecoder())
.target(MyApi.class, "http://api");
try {
api.getObject(new MyJaxbObjectRequest());
} catch (SOAPFaultException faultException) {
log.info(faultException.getFault().getFaultString());
}
}
}
Feign支持Feign和JAX-RS注解。
Feign Annotations
Annotation | Interface Target | Usage |
---|---|---|
@RequestLine | Method | 定义HttpMethod和UriTemplate,用大括号 {expression} 引用@Param参数 |
@Param | Parameter | 定义template变量 |
@Headers | Method, Type | 定义HeaderTemplate |
@QueryMap | Parameter | 定义名值对map或POJO, 用来动态构造查询字符串 |
@HeaderMap | Parameter | 定义名值对map,用来动态构造Http Headers |
@Body | Method | 定义Template,相似于UriTemplate和HeaderTemplate,使用@Param注解解析相应的表达式 |
@QueryMap示例
public interface Api {
@RequestLine("GET /find")
V find(@QueryMap Map queryMap);
}
public interface Api {
@RequestLine("GET /find")
V find(@QueryMap CustomPojo customPojo);
}
@HeaderMap示例
public interface Api {
@RequestLine("POST /")
void post(@HeaderMap Map headerMap);
}
@Body示例
interface LoginClient {
@RequestLine("POST /")
@Headers("Content-Type: application/xml")
@Body(" ")
void xml(@Param("user_name") String user, @Param("password") String password);
@RequestLine("POST /")
@Headers("Content-Type: application/json")
// json curly braces must be escaped!
@Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
void json(@Param("user_name") String user, @Param("password") String password);
}
JAXRS Annotations
Annotation | Interface Target | Usage |
---|---|---|
@Path | Type,Method | Appends the value to Target.url(). Can have tokens corresponding to @PathParam annotations |
@HttpMethod, @GET, @POST, @PUT, @DELETE ... | Method | Sets the request method |
@Produces | Method | Adds all values into the Accept header |
@Consumes | Method | Adds the first value as the Content-Type header |
@PathParam | Parameter | Links the value of the corresponding parameter to a template variable declared in the path |
@QueryParam | Parameter | Links the value of the corresponding parameter to a query parameter |
@HeaderParam | Parameter | Links the value of the corresponding parameter to a header |
@FormParam | Parameter | Links the value of the corresponding parameter to a key passed to Encoder.Text |
示例
public class JaxRsExample {
interface GitHub {
@GET
@Path("/repos/{owner}/{repo}/contributors")
List contributors(@PathParam("owner") String owner, @PathParam("repo") String repo);
}
public static void main(String[] args) {
GitHub github = Feign.builder()
.contract(new JAXRS2Contract())
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
// Fetch and print a list of the contributors to this library.
List contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.getLogin() + " (" + contributor.getContributions() + ")");
}
}
}
Encoder & Decoder
Feign支持Gson、Jackson JSON API编/解码,支持JAXB XML API编/解码,支持Sax XML API解码。
GSON
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
}
}
Jackson
public class Example {
public static void main(String[] args) {
GitHub github = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(GitHub.class, "https://api.github.com");
}
}
JAXB
public class Example {
public static void main(String[] args) {
JAXBContextFactory jaxbFactory = new JAXBContextFactory.Builder()
.withMarshallerJAXBEncoding("UTF-8")
.withMarshallerSchemaLocation("http://apihost http://apihost/schema.xsd")
.build();
Response response = Feign.builder()
.encoder(new JAXBEncoder(jaxbFactory))
.decoder(new JAXBDecoder(jaxbFactory))
.target(Response.class, "https://apihost");
}
}
Sax
public class Example {
public static void main(String[] args) {
Api api = Feign.builder()
.decoder(SAXDecoder.builder()
.registerContentHandler(UserIdHandler.class)
.build())
.target(Api.class, "https://apihost");
}
}
Spring OpenFeign
Spring Cloud OpenFeign是Spring Cloud的子项目,集成OpenFeign声明式REST Client,集成Ribbon、Eureka、Spring Cloud LoadBalancer实现负载均衡的HTTP客户端。
说明:Spring Cloud的子项目Spring Cloud Netflix集成了Ribbon(客户端负载均衡)、Eureka(服务发现),子项目Spring Cloud Commons提供Spring Cloud LoadBalancer。OpenFeign集成Eureka的例子请看spring-cloud-samples/feign-eureka 。
Spring Boot集成
- 添加spring-cloud-starter-openfeign依赖
...
org.springframework.cloud
spring-cloud-starter-openfeign
...
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
...
- 启用OpenFeign
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OpenFeignApplication {
public static void main(String[] args) {
SpringApplication.run(OpenFeignApplication.class, args);
}
}
- 声明FeignClient
import org.itrunner.openfeign.model.Contributor;
import org.itrunner.openfeign.model.Issue;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
@FeignClient(name = "github", url = "https://api.github.com")
public interface GitHubClient {
@GetMapping("/repos/{owner}/{repo}/contributors")
List contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo);
@PostMapping("/repos/{owner}/{repo}/issues")
void createIssue(@RequestBody Issue issue, @PathVariable("owner") String owner, @PathVariable("repo") String repo);
}
- 调用FeignClient
import org.itrunner.openfeign.feign.GitHubClient;
import org.itrunner.openfeign.model.Contributor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class GitHubController {
private final GitHubClient gitHubClient;
@Autowired
public GitHubController(GitHubClient gitHubClient) {
this.gitHubClient = gitHubClient;
}
@GetMapping("/contributors")
public List contributors() {
return gitHubClient.contributors("OpenFeign", "feign");
}
}
OpenFeign配置
命名客户端
命名客户端(named client)是Spring Cloud OpenFeign的一个核心概念,使用@FeignClient注解指定。Spring Cloud为每个命名客户端创建一个ApplicationContext,包含feign.Decoder、 feign.Encoder、feign.Contract等。
默认配置
@FeignClient的configuration参数,默认是通过 FeignClientsConfiguration 类定义的,默认值如下(BeanType beanName: ClassName):
- Decoder feignDecoder: ResponseEntityDecoder
- Encoder feignEncoder: SpringEncoder
- Logger feignLogger: Slf4jLogger
- Contract feignContract: SpringMvcContract
- Feign.Builder feignBuilder: HystrixFeign.Builder
- Client feignClient: 如classpath中存在Ribbon且被启用则为LoadBalancerFeignClient,如classpath中存在Spring Cloud LoadBalancer则为FeignBlockingLoadBalancerClient,如果前两者都不存在,则为默认的feign client HttpURLConnection。
说明:spring-cloud-starter-openfeign包含optional spring-cloud-starter-netflix-ribbon和spring-cloud-starter-loadbalancer。
自定义配置
- 使用@FeignClient声明额外的configuration
@FeignClient(name = "github", configuration = GitHubConfiguration.class)
public interface GitHubClient {
//..
}
public class GitHubConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}
上例,client将组合FeignClientsConfiguration和GitHubConfiguration的配置,后者会覆盖前者。
注意:Configuration不能使用注解@Configuration,否则应将其放在@ComponentScan扫描范围外的地方。不然,该configuration将作为默认Configuration。
如果我们创建了多个同名的FeignClient,以便指向相同的server,但他们的自定义配置不同,可以使用contextId属性避免冲突:
@FeignClient(contextId = "common", name = "github")
public interface GitHubClient {
//..
}
@FeignClient(contextId = "auth", name = "github", configuration = GitHubConfiguration.class)
public interface AuthGitHubClient {
@GetMapping("/repos/{owner}/{repo}/traffic/views")
Traffic traffics(@PathVariable("owner") String owner, @PathVariable("repo") String repo, @SpringQueryMap Params params);
}
另外,可以使用@EnableFeignClients的defaultConfiguration属性定义全局默认配置。
- 自定义配置属性
application.yml
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
定义默认配置:
feign:
httpclient:
enabled: true
client:
default-config: default
default-to-properties: true
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
说明:
- 配置属性的优先级高于@Configuration bean
- 默认启用Apache HTTP Client,feign.httpclient.enabled=true
- 若要启用OkHttpClient,将feign.okhttp.enabled设为true并添加okhttp依赖:
com.squareup.okhttp3
okhttp
4.5.0-RC1
集成Ribbon
- 添加依赖
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
- 启用Ribbon
spring:
cloud:
loadbalancer:
ribbon:
enabled: true
- 配置Server
github:
ribbon:
listOfServers: https://api.github.com
- 修改FeignClient
@FeignClient注解不必定义url,ribbon会自动查找server地址。
@FeignClient(name = "github")
public interface GitHubClient {
\\..
}
集成Spring Cloud LoadBalancer
Spring Cloud Netflix Ribbon正在维护中,推荐使用Spring Cloud LoadBalancer。
- 添加依赖
org.springframework.cloud
spring-cloud-starter-loadbalancer
- 禁用Ribbon
为保持向后兼容,默认load-balancer是ribbon,为使用Spring Cloud LoadBalancer,要禁用ribbon。
spring:
cloud:
loadbalancer:
ribbon:
enabled: false
- 配置discovery client
spring:
cloud:
discovery:
client:
simple:
instances:
github[0]:
uri: https://api.github.com
- 修改FeignClient
@FeignClient注解不必定义url,LoadBalancer会自动查找Service Instance。
@FeignClient(name = "github")
public interface GitHubClient {
\\..
}
GitHub源码
参考文档
Spring Cloud OpenFeign