Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。
在Spring Cloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
Spring Cloud对Feign进行了增强,使Feign支持了Spring MVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。
Spring Cloud Feign是基于Netflix feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义的方式。
Spring Cloud Feign帮助我们定义和实现依赖服务接口的定义。在Spring Cloud feign的实现下,只需要创建一个接口并用注解方式配置它,即可完成服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。
Spring Cloud Feign具备可插拔的注解支持,支持Feign注解、JAX-RS注解和Spring MVC的注解。
声明式:接口声明、Annonation驱动
Web服务:HTTP的方式作为通讯协议
客户端:用于服务调用的存根。
Feign:原生并不是Spring Web MVC的实现,基于JAX-RS(Java REST规范)实现。Spring Cloud封装了Feign,使用支持Spring Web MVC。RestTemplate、HttpMessageConverter
RestTemplate以及Spring Web MVC可以显式的自定义HttpMessageConverter实现。
假设有一个Java接口PersonService,Feign可以将其声明它是以HTTP接口来实现接口的。
以前我们的简单架构如下:
那么下面我们将写一个简单的示例,服务配置如下。
需要的服务组件(SOA)
我们一步一步来,摒弃之前的eureka server项目,全部重新创建一下。server单独一个工程,Feign客户端+Feign服务+Feign声明接口放另一个工程。
(1)首先,还是去start.spring.io构建Eureka server项目,如下:
(2)然后创建feign工程。
(3)分别导入两个项目,其中先配置eureka server的application.properties.
spring.application.name=spring-cloud-eureka-server
server.port=12345
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
增加springboot安全依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
关闭安全。创建config包,包下类SecurityConfig.java。
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**");
}
}
启动类加上@EnableEurekaServer注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class SpringcloudEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudEurekaServerApplication.class, args);
}
}
启动好eureka server,先丢到一旁。
(4)下面导入feign项目,更改jar为pom,删掉原本的src。创建子模块person-api,创建包com.gupao.feign.api.domain,下面创建Peson类,这里采用lombok的@Data注解简化代码。
import lombok.Data;
/**
* @ClassName
* @Describe 人的模型
* @Author 66477
* @Date 2020/6/822:14
* @Version 1.0
*/
@Data
public class Person {
private Long id;
private String name;
}
(5)创建service包,包下创建interface接口PersonService。
import com.gupao.feign.api.domain.Person;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.Collection;
/**
* @ClassName
* @Describe {@link Person}服务
* @Author 66477
* @Date 2020/6/822:25
* @Version 1.0
*/
@FeignClient(value="person-service")//服务提供方应用的名称
public interface PersonService {
/**
* 保存
* @param person {@link Person}
* @return 如果成功,<code>true</code>
*/
@PostMapping(value = "/person/save")
boolean save(@RequestBody Person person);
/**
* 查找所有的服务
* @return
*/
@GetMapping(value="/person/findAll")
Collection<Person> findAll();
}
(6)然后创建feign客户端,模块名称:person-client。随后在该模块中创建com.gupao.feign.client.controller包,与包同级创建启动类PersonClientApplication.java。内容如下:
import com.gupao.feign.api.service.PersonService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @ClassName
* @Describe TODO
* @Author 66477
* @Date 2020/6/822:40
* @Version 1.0
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(clients= PersonService.class)
public class PersonClientApplication {
public static void main(String[] args) {
SpringApplication.run(PersonClientApplication.class,args);
}
}
然后在controller下创建PersonClientController类,内容如下:
import com.gupao.feign.api.domain.Person;
import com.gupao.feign.api.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Collection;
/**
* @ClassName
* @Describe {@link PersonClientController}实现{@link PersonService}
* @Author 66477
* @Date 2020/6/822:42
* @Version 1.0
*/
@RestController
public class PersonClientController implements PersonService {
private final PersonService personService;
@Autowired
public PersonClientController(PersonService personService) {
this.personService = personService;
}
/**
* 保存
* @param person {@link Person}
* @return 如果成功,<code>true</code>
*/
public boolean save(@RequestBody Person person){
return personService.save(person);
}
@Override
public Collection<Person> findAll() {
return personService.findAll();
}
}
再定义application.properties文件。
spring.application.name=person-client
server.port=8080
eureka.client.service-url.defaultZone=http://localhost:12345/eureka
pom文件记得依赖person-api
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud-feign</artifactId>
<groupId>com.example</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>person-client</artifactId>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>person-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
</project>
打开spring boot security。同样的,创建config包,然后创建SecurityConfig类。
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**");
}
}
(7)接下来,feign服务端,创建包com.gupao.feign.person.service.provider。包下创建PersonServiceProviderAppplication启动类。
import com.gupao.feign.api.service.PersonService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @ClassName
* @Describe {@link PersonService}提供者应用
* @Author 66477
* @Date 2020/6/823:00
* @Version 1.0
*/
@SpringBootApplication
@EnableEurekaClient
public class PersonServiceProviderAppplication {
public static void main(String[] args) {
SpringApplication.run(PersonServiceProviderAppplication.class,args);
}
}
创建包controller,创建服务端控制器PersonServiceProviderController类。
import com.gupao.feign.api.domain.Person;
import com.gupao.feign.api.service.PersonService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName
* @Describe {@link PersonService} 提供者控制器(可以实现{@link PersonService}接口 )
* @Author 66477
* @Date 2020/6/823:01
* @Version 1.0
*/
@RestController
public class PersonServiceProviderController {
private Map<Long,Person> persons = new ConcurrentHashMap<>();
/**
* 保存
* @param person {@link Person}
* @return 如果成功,<code>true</code>
*/
@PostMapping(value = "/person/save")
public boolean save(@RequestBody Person person){
return persons.put(person.getId(),person) == null;
}
/**
* 查找所有的服务
* @return
*/
@GetMapping(value="/person/findAll")
public Collection<Person> findAll(){
return persons.values();
}
}
引入security依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
关闭springboot security。
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**");
}
}
配置application.properties.
#这个名称需要和@FeignClient(value="person-service")对应
spring.application.name=person-service
server.port=9090
eureka.client.service-url.defaultZone=http://localhost:12345/eureka
(8)最终启动feign服务端,feign客户端。结果:
(9)我们开始用Postman测试peson-client->person-service。
person-api定义了@FeignClients(value="person-service"),person-service实际上是一个服务器提供方的应用名称
person-client和person-service两个应用注册到了Eureka Server。
person-client可以感知person-serivce应用存在的,并且Spring Cloud帮助解析PersonService中声明的应用名称:"person-service",因此person-client在调用person-service服务时,实际就路由到person-service的URL。
浏览器访问http://localhost:8080/person/findAll
官方文档https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.0.RELEASE/reference/html/#spring-cloud-ribbon
(1)调整person-client,关闭Eureka(这个貌似没用)
#整合ribbon-关闭Eureka
ribbon.eureka.enabled=false
我直接采用的下面这个方式直接关闭eureka注册
#整合ribbon-去除eureka注册
eureka.client.register-with-eureka=false
# 不获取注册列表信息, 是否从eureka服务器获取注册信息 , false = 不获取,true = 获取
eureka.client.fetch-registry: false
(2)定义person-client服务ribbon的服务列表(服务名称:person-service)
#整合ribbon-配置“person-service"的负载均衡的服务器列表(写两遍是为了证明它是可以多配置的)
person-service.ribbon.listOfServers:http://localhost:9090《http://localhost:9090
注意:要想完全取消Eureka注册,只需要将person-clien启动类上的@EnableEurekaClient注释掉。(这种视频中讲到,但是我本地没有实现,不是道是不是这个版本是否有升级变动)
(1)比如,我们举例第一个自定义策略。首先,我们进入RandomRule源码,发现它继承的是 AbstractLoadBalancerRule。那我们也在person-client下创建一个ribbon包,包里创建一个类FirstServerForverRule(意思为永远获取第一台服务器)继承 AbstractLoadBalancerRule抽象类。重写它的方法。
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
/**
* @ClassName
* @Describe 自定义实现 {@link com.netflix.loadbalancer.IRule}
* @Author 66477
* @Date 2020/6/922:39
* @Version 1.0
*/
public class FirstServerForverRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
Server server = null;
ILoadBalancer loadBalancer = getLoadBalancer();
List<Server> allServers = loadBalancer.getAllServers();
return allServers.get(0);
}
}
(2)person-client启动类中暴露自定义实现为Spring Bean
@Bean
public FirstServerForverRule firstServerForverRule(){
return new FirstServerForverRule();
}
(3)激活这个配置,启动类上加上@RibbonClient(value = "person-service",configuration = FirstServerForverRule.class)
(4)在下面位置打上断点,debug启动client。
当我postman随便访问两个接口之一,发现确实走到里面的,allServers变量值为
并且两个都是application.properties文件里配置的服务端server。那么显而易见,下面一句代码就是永远获取第一个服务。这就是这个自定义策略的实现。
1.新增依赖至父项目pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.调整feign-api接口PersonService.增加fallback
import com.gupao.feign.api.domain.Person;
import com.gupao.feign.api.hystrix.PersonServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.Collection;
/**
* @ClassName
* @Describe {@link Person}服务
* @Author 66477
* @Date 2020/6/822:25
* @Version 1.0
*/
@FeignClient(value="person-service",fallback = PersonServiceFallback.class)//服务提供方应用的名称
public interface PersonService {
/**
* 保存
* @param person {@link Person}
* @return 如果成功,<code>true</code>
*/
@PostMapping(value = "/person/save")
boolean save(@RequestBody Person person);
/**
* 查找所有的服务
* @return
*/
@GetMapping(value="/person/findAll")
Collection<Person> findAll();
}
3.创建一个包hystrix,创建一个类实现PersonService.
import com.gupao.feign.api.service.PersonService;
import org.springframework.cloud.openfeign.FeignClient;
import java.util.Collection;
import java.util.Collections;
/**
* @ClassName
* @Describe TODO
* @Author 66477
* @Date 2020/6/1020:42
* @Version 1.0
*/
public class PersonServiceFallback implements PersonService {
@Override
public boolean save(Person person) {
return false;
}
@Override
public Collection<Person> findAll() {
return Collections.emptyList();
}
}
4.调整客户端person-client,激活Hystrix:启动类增加@EnableHystrix
1.能跟dubbo一样,消费端像调用本地接口方法一下调用服务端提供的服务吗?还有就是远程调用方法参数对象不用实现序列化接口吗?
解答:FeignClient类似Dubbo,不过需要增加一下@Annotation,和调用本地接口类似
2.Feign通过注释驱动弱化了调用Serivce细节,但是Feign的API设定会暴露service地址,那么还有实际使用价值吗?
解答:实际价值是存在的,Feign API暴露URI,比如:“/person/save"
3.整合ribbon不是一定要关闭注册中心吧?
解答:Rbbbon对于Eureka是不强依赖,不给过也不排除。
4.生成环境上也都是feign?
解答:据我所知,不少公司在用,需要Spring Cloud更多整合:
Feign作为客户端
Ribbon作为负载均衡
Eureka作为注册中心
Zuul作为网关
Security作为安全OAuth2认证
5.Ribbon直接配置在启动类上是作用所有的Controller,那如果想作用在某个类呢?
解答:Ribbon是控制全局的负载均衡,主要作用于客户端Feign,Controller是调用Feign接口,可能让人感觉直接作用了ontroller。
6.其实Eureka也有ribbon中简单的负载均衡吧?
解答:Eureka也要Ribbon的实现,可以参考com.netflix.ribbon:ribbon-eureka
7.如果服务提供方没有接口,我客户端一般怎么处理?要根据服务信息,自建feign接口?
解答:当然可以,Feign的接口定义要求强制实现。
8.无法连接注册中心的老服务,如何额调用cloud服务?
解答:可以通过域名的配置Ribbon服务白名单。
9.eureka有时监控不到宕机的服务,正确的启动方式是什么?
解答:需要其他设备来监控,PNIG eureka服务器是否活着,活着采用高可用方式管理Eureka。这可以调整心跳检测的频率。
原文:https://www.cnblogs.com/jmy520/p/13090011.html