上一篇文章介绍了springcloud的负载均衡组件ribbon,这篇文章继续介绍负载均衡的第二种组件fegin,OK 开始fegin的学习路程吧!
Fegin是什么?
Feign : Declarative REST clients。
Feign 是一个声明web服务客户端,这便得编写web服务客户端更容易,使用Feign 创建一个接口并对它进行注解,它具有可插拔的注解支持包括Feign注解与JAX-RS注解,Feign还支持可插拔的编码器与解码器,Spring Cloud 增加了对 Spring MVC的注解,Spring Web 默认使用了HttpMessageConverters
, Spring Cloud 集成 Ribbon 和 Eureka 提供的负载均衡的HTTP客户端 Feign
Fegin功能使用
& 基础fegin功能演示
继续在上一个项目当中新建立一个模块叫看一下整体项目的结构:
首先看一下pom.xml的文件内容,
<?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>server_fegion</artifactId>
<groupId>com.suning.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user_server_consumer_fegion</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!--引入这个包才能使用fegin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-core</artifactId>
<version>1.3.6.RELEASE</version>
</dependency>
</dependencies>
</project>
红色标注的就是springcloud集成feign的依赖组件...,接下来需要定义一个fegin接口,代码如下
package com.server.fegin.fegin;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.*;
/**
* @Author 18011618
* @Description 定义一个fegin
* @Date 20:53 2018/7/10
* @Modify By
*/
@FeignClient("service-provider-user")
public interface FeginClient {
@RequestMapping(value = "/getInfo",method = RequestMethod.GET)
String showInfo(@RequestParam(value ="name") String name);
}
我这里就是为了演示一个简单的负载均衡,所以代码功能很简单,但是生产环境中有些坑是要避免的,这点和ribbon是不一样的
> 这里不能使用GetMapping,PostMapping这种复合mapping,而要使用RequestMapping
> @PathVariable,@RequestParam必须要给value进行赋值
>如果方法里面的参数是复杂对象,尽快指定是post,但是fegin默认还是以post方法进行请求
>这里定义的只能是接口,且不需要实现,但是有一个要求就是这么的@RequestMapping中的value要和服务提供者里面的方法是一致的,这样才能调得通,方法名可以不一样.
:这个注解代表开启负载均衡机制,后面的value是服务提供者的名称。接下来再看一下对应的controller代码
package com.server.fegin.controller;
import com.server.fegin.fegin.FeginClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author 18011618
* @Description
* @Date 21:15 2018/7/10
* @Modify By
*/
@RestController
public class FeginController {
@Autowired
private FeginClient feginClient;
//Required String parameter 'message' is not present
@RequestMapping(value = "/getInfo",method = RequestMethod.GET)
public String showInfo(@RequestParam String name){
return feginClient.showInfo(name);
}
}
很简单把刚刚定义的FeginClient接口注入进来,调用对应的方法接口,好了就剩一个main方法的类
package com.server.fegin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
/**
* @Author 18011618
* @Description
* @Date 21:24 2018/7/10
* @Modify By
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ServerFeginApplication {
public static void main(String[] args) {
SpringApplication.run(ServerFeginApplication.class,args);
}
}
相比ribbon多了一个新注解,代表开启Fegin负载均衡,好了最好看一下配置文件,几乎没啥变化
spring:
application:
name: server-fegin
server:
port: 8013
eureka:
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://admin:admin@localhost:8080/eureka
instance:
prefer-ip-address: true
上面就把消费者的代码写完了,然后我们修改上一章节服务提供者的controller里面的方法:
对应的controller代码如下所示:
package com.suning.provider.controller;
import com.suning.provider.bean.User;
import com.suning.provider.respoitory.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author 18011618
* @Description 用户提供服务的controller
* @Date 9:51 2018/7/9
* @Modify By
*/
@RestController
public class UserController {
@Value("${server.port}")
String port;
@Autowired
private UserRepository userRepository; //数据访问层接口
@GetMapping("/findUser/{id}")
public User findUserById(@PathVariable long id){
return this.userRepository.findOne(id);
}
@GetMapping("/getInfo")
public String getServerPort(@RequestParam String name){
System.out.println(name+",this from server port is:"+port);
return name+",this from server port is:"+port;
}
}
然后修改对应的配置文件端口号为9001和9002,来启动两个服务提供者的实例,然后在浏览器端输入
http://localhost:8013/getInfo?name=i am jhp ,i am do test fegin,连续刷新浏览器会出现如下的截图
和
这里就可以很明显的看出来 一个请求访问的是9001端口,一个请求访问的是9002端口..
& 自定义配置覆盖fegin的默认配置
有时候可能业务需求,需要能够自定义配置功能,这个fegin和ribbon都是支持的,看一下官网的介绍
在Spring Cloud中,Feign的默认配置类是FeignClientsConfiguration,该类定义了Feign默认使用的编码器、解码器、所使用的契约等。
Spring Cloud允许通过注解@FeignClient的configuration属性自定义Feign的配置,自定义配置的优先级比FeignClientsConfiguration要高。
在Spring Cloud的文档中可以看到以下段落,描述了Spring Cloud提供的默认配置。
也就是说我们自定义fegin可以包含上面的6个部分,下面就演示其中一到两个的配置:
为了便于演示,还是再新建立一个模块叫,看一下项目结构
首先看一下自定义的配置
package com.server.fegin.custom.config;
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.Contract;
import feign.Logger;
/**
* @Author 18011618
* @Date 0:45 2018/7/11
* @Function 覆盖默认fegin的配置
*/
@Configuration
public class FeginConfiguration {
/**
* @Author 18011618
* @Date 0:45 2018/7/11
* @Function 覆盖springmvc的默认注解 使用fegin自身的注解
*/
@Bean
public Contract feignContract() {
return new Contract.Default();
}
/**
* @Author 18011618
* @Date 0:45 2018/7/11
* @Function 改变日志等级
*/
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
/**
* @Author 18011618
* @Date 0:49 2018/7/11
* @Function 覆盖默认的HystrixFegin.Builder
*/
// @Bean
// Feign.Builder feignBuilder(){
// return null;
// }
}
自定义FeginClient,代码如下
package com.server.fegin.custom.fegin;
import com.server.fegin.custom.config.FeginConfiguration;
import feign.Param;
import feign.RequestLine;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @Author 18011618
* @Description 定义一个fegin
* @Date 20:53 2018/7/10
* @Modify By
*/
@FeignClient(name = "service-provider-user",configuration = FeginConfiguration.class)
public interface UserFeginClient {
//使用fegin自带的注解 不能使用springmvc 因为启动不了
@RequestLine("GET /getInfo")
String showInfo(@Param(value = "name") String name);
}
这里要在引入新自定义的配置类,然后红色标注对的注解也改了,只能使用fegin原生的注解(这个可以去netfilx的github去看fegin的源码),然后main方法的主类
package com.server.fegin.custom;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
/**
* @Author 18011618
* @Description 实现覆盖fegin的默认配置功能
* @Date 0:28 2018/7/11
* @Modify By
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeginCustomConfigApplication {
public static void main(String[] args) {
SpringApplication.run(FeginCustomConfigApplication.class,args);
}
}
没有任何变化,对应的controller也没有任何变化,只是换了个FeginClient而已
package com.server.fegin.custom.controller;
import com.server.fegin.custom.fegin.UserFeginClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @Author 18011618
* @Date 16:12 2018/7/9
* @Function
*/
@RestController
public class UserInfoController {
@Autowired
private UserFeginClient feginClient;
//Required String parameter 'message' is not present
@RequestMapping(value = "/getInfo",method = RequestMethod.GET)
public String showInfo(@RequestParam String name){
return feginClient.showInfo(name);
}
}
配置文件也没有什么变化,只是为了区分,所以改一个端口号
spring:
application:
name: server-fegin-custom
server:
port: 8014
eureka:
client:
healthcheck:
enabled: true
serviceUrl:
defaultZone: http://admin:admin@localhost:8080/eureka
instance:
prefer-ip-address: true
这个时候启动main方法的类,然后浏览器端访问http://localhost:8014?name = i am jhp ,i doing test fegin,会发现效果和上面是一致的,这个就代表了我们自定义的config生效了,和fegin相关的负载均衡功能就讲完了,接下来简单介绍一下相关源码.
& fegin的源码分析
入口处
在@EnableFeignClients标签中,import了一个FeignClientsRegistrar类,那就看一下这个类,仔细阅读源码会发现一个很重要的方法
方法里面有两个子方法,分别是加载默认配置和fegin的,
这个方法是用来加载默认配置的,看一下这个方法的具体实现:
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
如果用户没有配置configuration就使用默认的,然后它会注册配置:registerClientConfiguration:
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
配置加载好了,就会注册feginclient客户端:继续看,
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);//真正执行注册的方法
}
}
}
}
再看一下...这个方法...
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = name + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
这个方法就是把所有能够配置的参数都进行了注册,到这里fegin自身源码就看完了,那么还剩下一个关键问题,就是spring是如何初始化它(在哪里调用它?)跟着Spring的源码走下去,看过源码的人都会直接看到AbstractApplicationContext#refresh()方法,整体整理一下代码:其实这个方法非常重要,因为spring的其他很多的组件也是在这里进行处理的,比如spring io,di,aop等
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// 这里调用了FeignClientsRegistrar
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
其它的方法就不细说了
版权声明:本文为博主原创文章,未经博主允许不得转载:https://blog.csdn.net/qq_18603599/article/details/80941683