SpringCloud带给我们的便利是有目共睹的,它能快速的帮助中小型企业构建微服务的程序架构。其背后有SpringBoot这员大将做底层支持,同时得力于Spring在java界的影响力和近几年micro service的流行,因此SpringCloud从2016年渐渐的走进人们的视野。可是便捷这个东西确实是一把双刃剑,我们在享受便捷的同时也要做好难以排错的准备,同时在版本升级的时候一定慎之又慎。
相关源码浅析
对于我们的feign项目,如果加上了spring-cloud-starter-netflix-hystrix,那么feign自动会将所有的方法用hystrix进行包装,这是怎么实现的呢?答案就是代理模式。
HystrixFeign
我们先来看看这个API文档注释:
Allows Feign interfaces to return HystrixCommand or rx.Observable or rx.Single s. Also decorates normal Feign methods with circuit breakers, but calls {@ HystrixCommand#execute()} directly.
根据API文档解释,这个类允许Feign接口返回HystrixCommand或者rx.Observable或者rx.Single s,同时将Feign的method通过断路器进行包装
public final class HystrixFeign { public static Builder builder() { return new Builder(); } public static final class Builder extends Feign.Builder { private Contract contract = new Contract.Default(); //设置HystrixCommand的Setter属性工厂 private SetterFactory setterFactory = new SetterFactory.Default(); /** 允许覆盖Hystrix的一些属性比如说threadpool和commandkey等 * Allows you to override hystrix properties such as thread pools and command keys. */ public Builder setterFactory(SetterFactory setterFactory) { this.setterFactory = setterFactory; return this; } //...省略其他代码 @Override public Feign build() { return build(null); } /** Configures components needed for hystrix integration. */ Feign build(final FallbackFactory<?> nullableFallbackFactory) { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) { //实际创建的是HystrixInvocatonHandler代理对象 return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory); } }); super.contract(new HystrixDelegatingContract(contract)); return super.build(); } 根据上述代码,我们可以看到在build方法里实际创建的是HystrixInvocationHandler对象。
HystrixInvocationHandler
/** 实现InvocationHandler接口,通过代理模式实现*/final class HystrixInvocationHandler implements InvocationHandler { private final Target<?> target; private final Map<Method, MethodHandler> dispatch; private final FallbackFactory<?> fallbackFactory; // Nullable private final Map<Method, Method> fallbackMethodMap; private final Map<Method, Setter> setterMethodMap; HystrixInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch, SetterFactory setterFactory, FallbackFactory<?> fallbackFactory) { this.target = checkNotNull(target, "target"); this.dispatch = checkNotNull(dispatch, "dispatch"); this.fallbackFactory = fallbackFactory; this.fallbackMethodMap = toFallbackMethod(dispatch); this.setterMethodMap = toSetters(setterFactory, target, dispatch.keySet()); } //...省略部分代码 /** * 通过代码可知:每个method方法都对应独立的setter配置 * Process all methods in the target so that appropriate setters are created. */ static Map<Method, Setter> toSetters(SetterFactory setterFactory, Target<?> target, Set<Method> methods) { Map<Method, Setter> result = new edHashMap<Method, Setter>(); for (Method method : methods) { method.setAccessible(true); result.put(method, setterFactory.create(target, method)); } return result; } @Override public invoke(final proxy, final Method method, final [] args) throws Throwable { //解决 方法中 ReflectiveFeign.FeignInvocationHandler中的方法冲突问题 // early exit if the invoked method is from java.lang. // code is the same as ReflectiveFeign.FeignInvocationHandler if ("equals".equals(method.getName())) { try { otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if ("hashCode".equals(method.getName())) { return hashCode(); } else if ("toString".equals(method.getName())) { return toString(); } //创建HystrixCommand对象 HystrixCommand< > hystrixCommand = new HystrixCommand< >(setterMethodMap.get(method)) { @Override protected run() throws Exception { try { //代理执行,用HystrixCommand包装Feign的请求 return HystrixInvocationHandler.this.dispatch.get(method).invoke(args); } catch (Exception e) { throw e; } catch (Throwable t) { throw (Error) t; } } //重写降级方法 @Override protected getFallback() { if (fallbackFactory == null) { return super.getFallback(); } try { fallback = fallbackFactory.create(getExecutionException()); result = fallbackMethodMap.get(method).invoke(fallback, args); if (isReturnsHystrixCommand(method)) { return ((HystrixCommand) result).execute(); } else if (isReturnsObservable(method)) { // Create a cold Observable return ((Observable) result).toBlocking().first(); } else if (isReturnsSingle(method)) { // Create a cold Observable as a Single return ((Single) result).toObservable().toBlocking().first(); } else if (isReturnsCompletable(method)) { ((Completable) result).await(); return null; } else { return result; } } catch (IllegalAccessException e) { // shouldn't happen as method is public due to being an interface throw new Asserti (e); } catch (InvocationTargetException e) { // Exceptions on fallback are tossed by Hystrix throw new Asserti (e.getCause()); } } }; if (isReturnsHystrixCommand(method)) { return hystrixCommand; } else if (isReturnsObservable(method)) { // Create a cold Observable return hystrixCommand.toObservable(); } else if (isReturnsSingle(method)) { // Create a cold Observable as a Single return hystrixCommand.toObservable().toSingle(); } else if (isReturnsCompletable(method)) { return hystrixCommand.toObservable().toCompletable(); } return hystrixCommand.execute(); } //省略部分代码...}通过上述代码可以发现这里使用代理模式将Hystrix包装Feign。
SetterFactory
SetterFactory是用于生成HystrixSetter配置项的工厂:
public interface SetterFactory { /** * Returns a hystrix setter appropriate for the given target and method */ HystrixCommand.Setter create(Target<?> target, Method method); /** * Default behavior is to derive the group key from {@ Target#name()} and the command key from * {@ Feign#configKey(Class, Method)}. */ final class Default implements SetterFactory { @Override public HystrixCommand.Setter create(Target<?> target, Method method) { String groupKey = target.name(); String commandKey = Feign.configKey(target.type(), method); return HystrixCommand.Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey)) .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey)); } }}根据提示它对应的commandKey 通过Feign#configKey()生成,而groupKey是接口名
下面我们就看看Feign怎么样生成key的:
public abstract class Feign {/** 匹配基本规则:接口名#方法名(参数类型) Route53->route53.Route53 Route53#list() -> route53.Route53#list() Route53#listAt(Marker) -> route53.Route53#listAt(Marker) Route53#listByNameAndType(String, String) -> route53.Route53#listAt(String, String)*/public static String configKey(Class targetType, Method method) { StringBuilder builder = new StringBuilder(); builder.append(targetType.getSimpleName()); builder.append('#').append(method.getName()).append('('); for (Type param : method.getGenericParameterTypes()) { param = Types.resolve(targetType, targetType, param); builder.append(Types.getRawType(param).getSimpleName()).append(','); } if (method.getParameterTypes().length > 0) { builder.deleteCharAt(builder.length() - 1); } return builder.append(')').toString(); }}到此处我们可以断定Feign与Hystrix集成时配置就可以这么写:
hystrix: command: Timeout#timeout(): execution: isolation: thread: timeoutInMilliseconds: 20000对应的接口:
package com.zhshop.web;import org.spring work.cloud.openfeign.FeignClient;import org.spring work.web.bind.annotation.RequestMapping;@FeignClient("purchase-service")public interface Timeout { @RequestMapping("/feignTest") public String timeout();}Hystrix的配置类
HystrixCommandProperties基本上包含了所有Hystrix的默认配置,代码片段如下:
public abstract class HystrixCommandProperties {//传入前缀hystrix protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder) { this(key, builder, "hystrix"); } // known that we're using deprecated HystrixPropertiesChainedServoProperty until ChainedDynamicProperty exists in Archaius protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix) { this.key = key; this.circuitBreakerEnabled = getProperty(propertyPrefix, key, "circuitBreaker.enabled", builder.getCircuitBreakerEnabled(), default_circuitBreakerEnabled); this.circuitBreakerRequestVolumeThreshold = getProperty(propertyPrefix, key, "circuitBreaker.requestVolumeThreshold", builder.getCircuitBreakerRequestVolumeThreshold(), default_circuitBreakerRequestVolumeThreshold); this.circuitBreakerSleepWindowInMilliseconds = getProperty(propertyPrefix, key, "circuitBreaker.sleepWindowInMilliseconds", builder.getCircuitBreakerSleepWindowInMilliseconds(), default_circuitBreakerSleepWindowInMilliseconds); this.circuitBreakerErrorThresholdPercentage = getProperty(propertyPrefix, key, "circuitBreaker.errorThresholdPercentage", builder.getCircuitBreakerErrorThresholdPercentage(), default_circuitBreakerErrorThresholdPercentage); this.circuitBreakerForceOpen = getProperty(propertyPrefix, key, "circuitBreaker.forceOpen", builder.getCircuitBreakerForceOpen(), default_circuitBreakerForceOpen); this.circuitBreakerForceClosed = getProperty(propertyPrefix, key, "circuitBreaker.forceClosed", builder.getCircuitBreakerForceClosed(), default_circuitBreakerForceClosed); this.executionIsolationStrategy = getProperty(propertyPrefix, key, "execution.isolation.strategy", builder.getExecutionIsolationStrategy(), default_executionIsolationStrategy); //this property name is now misleading. //TODO figure out a good way to deprecate this property name this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds); this.executionTimeoutEnabled = getProperty(propertyPrefix, key, "execution.timeout.enabled", builder.getExecutionTimeoutEnabled(), default_executionTimeoutEnabled); this.executionIsolationThreadInterruptOnTimeout = getProperty(propertyPrefix, key, "execution.isolation.thread.interruptOnTimeout", builder.getExecutionIsolationThreadInterruptOnTimeout(), default_executionIsolationThreadInterruptOnTimeout); this.executionIsolationThreadInterruptOnFutureCancel = getProperty(propertyPrefix, key, "execution.isolation.thread.interruptOnFutureCancel", builder.getExecutionIsolationThreadInterruptOnFutureCancel(), default_executionIsolationThreadInterruptOnFutureCancel); this.executionIsolationSemaphoreMaxConcurrentRequests = getProperty(propertyPrefix, key, "execution.isolation.semaphore.maxConcurrentRequests", builder.getExecutionIsolationSemaphoreMaxConcurrentRequests(), default_executionIsolationSemaphoreMaxConcurrentRequests); this.fallbackIsolationSemaphoreMaxConcurrentRequests = getProperty(propertyPrefix, key, "fallback.isolation.semaphore.maxConcurrentRequests", builder.getFallbackIsolationSemaphoreMaxConcurrentRequests(), default_fallbackIsolationSemaphoreMaxConcurrentRequests); this.fallbackEnabled = getProperty(propertyPrefix, key, "fallback.enabled", builder.getFallbackEnabled(), default_fallbackEnabled); this.metricsRollingStatisticalWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingStats.timeInMilliseconds", builder.getMetricsRollingStatisticalWindowInMilliseconds(), default_metricsRollingStatisticalWindow); this.metricsRollingStatisticalWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingStats.numBuckets", builder.getMetricsRollingStatisticalWindowBuckets(), default_metricsRollingStatisticalWindowBuckets); this.metricsRollingPercentileEnabled = getProperty(propertyPrefix, key, "metrics.rollingPercentile.enabled", builder.getMetricsRollingPercentileEnabled(), default_metricsRollingPercentileEnabled); this.metricsRollingPercentileWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingPercentile.timeInMilliseconds", builder.getMetricsRollingPercentileWindowInMilliseconds(), default_metricsRollingPercentileWindow); this.metricsRollingPercentileWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingPercentile.numBuckets", builder.getMetricsRollingPercentileWindowBuckets(), default_metricsRollingPercentileWindowBuckets); this.metricsRollingPercentileBucketSize = getProperty(propertyPrefix, key, "metrics.rollingPercentile.bucketSize", builder.getMetricsRollingPercentileBucketSize(), default_metricsRollingPercentileBucketSize); this.metricsHealthSnapshotIntervalInMilliseconds = getProperty(propertyPrefix, key, "metrics.healthSnapshot.intervalInMilliseconds", builder.getMetricsHealthSnapshotIntervalInMilliseconds(), default_metricsHealthSnapshotIntervalInMilliseconds); this.requestCacheEnabled = getProperty(propertyPrefix, key, "requestCache.enabled", builder.getRequestCacheEnabled(), default_requestCacheEnabled); this.requestLogEnabled = getProperty(propertyPrefix, key, "requestLog.enabled", builder.getRequestLogEnabled(), default_requestLogEnabled); // threadpool doesn't have a global override, only instance level makes sense this.executionIsolationThreadPoolKeyOverride = forString().add(propertyPrefix + ".command." + key.name() + ".threadPoolKeyOverride", null).build(); } //处理布尔类型的值 private static HystrixProperty<Boolean> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Boolean builderOverrideValue, Boolean defaultValue) { return forBoolean() .add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue) .add(propertyPrefix + ".command.default." + instanceProperty, defaultValue) .build(); } //处理Integer类型的值 private static HystrixProperty<Integer> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Integer builderOverrideValue, Integer defaultValue) { return forInteger() .add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue) .add(propertyPrefix + ".command.default." + instanceProperty, defaultValue) .build(); } //省略部分代码..... }在这里我们可以看到默认情况下会传入前缀:hystrix,然后拼接command+default|key的方式,这里的key通常情况下传的是
Feign对应Feign#configKey()的方法zuul通常情况下是serviceId即服务名称
那么最终Hystrix的默认配置为:
hystrix: command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 1000FeignClientsProperties
根据官网描述,我们可以通过配置的方式来配置Feign的客户端了:
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 那么与之对应的是在FeignClientProperties这个类里
package org.spring work.cloud.openfeign;import feign.Contract;import feign.Logger;import feign.RequestInterceptor;import feign.Retryer;import feign.codec.Decoder;import feign.codec.Encoder;import feign.codec.ErrorDecoder;import org.spring work.boot.context.properties.ConfigurationProperties;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util. s;/** * @author Eko Kurniawan Khannedy */@ConfigurationProperties("feign.client")public class FeignClientProperties { private boolean defaultToProperties = true; //默认名称就是default private String defaultConfig = "default";// feign.client.config.default.xxx的配置映射此处 这里的String通常是客户端名(@FeignClient(名称)) private Map<String, FeignClientConfiguration> config = new HashMap<>(); public boolean isDefaultToProperties() { return defaultToProperties; } public void setDefaultToProperties(boolean defaultToProperties) { this.defaultToProperties = defaultToProperties; } public String getDefaultConfig() { return defaultConfig; } public void setDefaultConfig(String defaultConfig) { this.defaultConfig = defaultConfig; } public Map<String, FeignClientConfiguration> getConfig() { return config; } public void setConfig(Map<String, FeignClientConfiguration> config) { this.config = config; } @Override public boolean equals( o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FeignClientProperties that = (FeignClientProperties) o; return defaultToProperties == that.defaultToProperties && s.equals(defaultConfig, that.defaultConfig) && s.equals(config, that.config); } @Override public int hashCode() { return s.hash(defaultToProperties, defaultConfig, config); } //最终的配置映射到该类上 public static class FeignClientConfiguration { private Logger.Level loggerLevel; private Integer connectTimeout; private Integer readTimeout; private Class<Retryer> retryer; private Class<ErrorDecoder> errorDecoder; private List<Class<RequestInterceptor>> requestInterceptors; private Boolean decode404; private Class<Decoder> decoder; private Class<Encoder> encoder; private Class<Contract> contract; public Logger.Level getLoggerLevel() { return loggerLevel; } public void setLoggerLevel(Logger.Level loggerLevel) { this.loggerLevel = loggerLevel; } public Integer getConnectTimeout() { return connectTimeout; } public void setConnectTimeout(Integer connectTimeout) { this.connectTimeout = connectTimeout; } public Integer getReadTimeout() { return readTimeout; } public void setReadTimeout(Integer readTimeout) { this.readTimeout = readTimeout; } public Class<Retryer> getRetryer() { return retryer; } public void setRetryer(Class<Retryer> retryer) { this.retryer = retryer; } public Class<ErrorDecoder> getErrorDecoder() { return errorDecoder; } public void setErrorDecoder(Class<ErrorDecoder> errorDecoder) { this.errorDecoder = errorDecoder; } public List<Class<RequestInterceptor>> getRequestInterceptors() { return requestInterceptors; } public void setRequestInterceptors(List<Class<RequestInterceptor>> requestInterceptors) { this.requestInterceptors = requestInterceptors; } public Boolean getDecode404() { return decode404; } public void setDecode404(Boolean decode404) { this.decode404 = decode404; } public Class<Decoder> getDecoder() { return decoder; } public void setDecoder(Class<Decoder> decoder) { this.decoder = decoder; } public Class<Encoder> getEncoder() { return encoder; } public void setEncoder(Class<Encoder> encoder) { this.encoder = encoder; } public Class<Contract> getContract() { return contract; } public void setContract(Class<Contract> contract) { this.contract = contract; } //省略部分代码...}FeignClientFactoryBean
FeignClient最终是被FeignClientFactoryBean创建,我们可以看一下它的get ()方法:
@Override public get () throws Exception { FeignContext context = applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); if (!StringUtils.hasText(this.url)) { String url; if (!this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } url += cleanPath(); return loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not lod balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); return targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); }在这里Targeter接口是获取Feign的目标地址对象的,同时这里还会创建具备负载均衡能力的Feign ,而Feign要与Hystrix集成,HystrixTargeter起到了关键作用:
/* * Copyright 2013-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */package org.spring work.cloud.openfeign;import org.spring work.util.Assert;import feign.Feign;import feign.Target;import feign.hystrix.FallbackFactory;import feign.hystrix.HystrixFeign;import feign.hystrix.SetterFactory;/** * @author Spencer Gibb * @author Erik Kringen */@SuppressWarnings("unchecked")class HystrixTargeter implements Targeter { @Override public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, Target.HardCodedTarget<T> target) { if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { return feign.target(target); } feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; SetterFactory setterFactory = getOptional(factory.getName(), context, SetterFactory.class); if (setterFactory != null) { builder.setterFactory(setterFactory); } Class<?> fallback = factory.getFallback(); if (fallback != void.class) { return targetWithFallback(factory.getName(), context, target, builder, fallback); } Class<?> fallbackFactory = factory.getFallbackFactory(); if (fallbackFactory != void.class) { return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory); } return feign.target(target); } private <T> T targetWithFallback(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target, HystrixFeign.Builder builder, Class<?> fallback) { //.... } private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target, HystrixFeign.Builder builder, Class<?> fallbackFactoryClass) { //... }} 继续阅读与本文标签相同的文章
redis系列:主从复制
不懂代码也想学会深度学习?这本书告诉你真的很简单
-
Oracle数据库分区表清理总结)
2026-05-26栏目: 教程
-
Java基础
2026-05-26栏目: 教程
-
Java基础1.1
2026-05-26栏目: 教程
-
Java基础1.2
2026-05-26栏目: 教程
-
RocketMQ 平滑升级到主从切换(实战篇)
2026-05-26栏目: 教程
