上一篇文章介绍了springcloud的负载均衡组件ribbon,这篇文章继续介绍负载均衡的第二种组件fegin,OK 开始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"?>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"> server_fegion com.suning.cloud 1.0-SNAPSHOT 4.0.0 user_server_consumer_fegion UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-eureka org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-feign org.springframework.cloud spring-cloud-netflix-core 1.3.6.RELEASE
红色标注的就是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, 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); SetbasePackages; Map , 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 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 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 , 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, 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