优质博文:IT-BLOG-CN
我们这次项目主要从RestTemplate 和 Feign 进行选型分析。
一、Spring Cloud Feign分析
Feign是另外一种客户端负载均衡实现。
我在该模块写了Feign Client的示例代码。
【1】spring-cloud-web-demo-api为服务的sdk模块
【2】spring-cloud-web-demo-service为提供接口服务的模块
【3】spring-cloud-web-demo-client为模拟调用服务的模块
首先在spring-cloud-web-demo-api模块,定义Feign API。spring-cloud-web-demo为spring-cloud-web-demo-service暴露的服务名。
@FeignClient(value = "spring-cloud-web-demo")
public interface UserFeign {
@GetMapping(value = "/user/getUserById", produces = "application/json;charset=utf-8")
Object getUserById(@RequestParam(value = "id", required = false) Long id);
//省略
}
然后通过ClientAutoConfiguration自动装配。(client直接引入api包就可以使用,不需要再EnableFeignClients)
@Configuration
@EnableFeignClients("net.teaho.demo.spring.cloud.web.api")
public class ClientAutoConfiguration {
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
net.teaho.demo.spring.cloud.web.api.config.ClientAutoConfiguration
在service模块如以往Spring MVC般实现api模块接口即可。
@RestController
public class UserController implements UserFeign {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@Override
public Object getUserById(Long id) {
return "{\"id\":1, \"name\": \"test\"}";
}
//省略
}
在Client模块,注入bean后直接调用。
@Component
@Slf4j
public class TestService {
@Autowired
private RestTemplate restTemplate;
public Object getOneUser(){
return userController.getUserById(1L);
}
}
二、RestTemplate分析
写了具有客户端负载均衡能力的RestTemplate的请求代码。
类似这样定义:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
RestTemplate究竟是如何利用注册中心实现客户端负载均衡的呢?
实现方式: 就是将上面所说的LoadBalancerInterceptor负载均衡拦截器加到标注了@LoadBalanced的RestTemplate实例中。 LoadBalancerInterceptor拦截器会在执行过程中获取并设置适合的目标请求实例,重新构造请求URI。
// 将配置中标注了@LoadBalanced的RestTemplate注入到这里
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
//将注册的RestTemplateCustomizer(RestTemplate自定义器)集合处理上面的restTemplates集合
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
三、技术选型
最终选择使用OpenFeign,下面说说原因。
和RestTemplate比起来,OpenFeign显得更适合Spring Boot微服务。
Open Feign相当于(HTTP)RPC,相比起RestTemplate,它直接显式将API声明以JAVA接口形式标识出来。 并且因为底层用的动态代理,它还可以(无感知地)替换底层实现。比如,github上就有替换底层逻辑的repo – Open Feign+Dubbo的RPC实现。
通过sdk包的形式,方便了调用,不需要像RestTemplate一样,客户端自行拼接上一串请求参数。在代码编写上也清晰。
要使用就必须知道OpenFeign是怎么实现的呢?
四、OpenFeign 初始化分析
流程图如下:
看看前面例子里我们引入的OpenFeign的东西
【1】@EnableFeignClients(“net.teaho.demo.spring.cloud.web.api”)
【2】@FeignClient(value = “spring-cloud-web-demo”) 还有自动装配引入的
【3】FeignRibbonClientAutoConfiguration
【4】FeignClientsConfiguration
我们就从这两个注解开始分析源码。
【1】首先看@FeignClient注解。
//给接口标注成一个REST调用方
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
//服务名,可以带协议前缀,也可以用${property.name}关联一个配置值。
@AliasFor("name")
String value() default "";
@Deprecated
String serviceId() default "";
//bean name
String contextId() default "";
@AliasFor("value")
String name() default "";
/**
* Sets the <code>@Qualifier</code> value for the feign client.
*/
String qualifier() default "";
//直接指定一个地址,比如http://localhost:12345,一般用于调试
String url() default "";
boolean decode404() default false;
/**
* A custom <code>@Configuration</code> for the feign client. Can contain override
* <code>@Bean</code> definition for the pieces that make up the client, for instance
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
*
* @see FeignClientsConfiguration for the defaults
*/
//可用于覆盖FeignClient默认设置
Class<?>[] configuration() default {};
//回滚类,像我的例子中定义的回滚类必须实现UserFeign接口,看https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/single/spring-cloud.html#spring-cloud-feign-hystrix-fallback
Class<?> fallback() default void.class;
//如果需要对异常做诊断可用此属性,https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/single/spring-cloud.html#spring-cloud-feign-hystrix-fallback
Class<?> fallbackFactory() default void.class;
//路径前缀
String path() default "";
//标记bean是否为primary
boolean primary() default true;
}
【2】接下来重点关注@EnableFeignClients注解是如何扫描FeignClient接口的。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
//省略
}
嗯,发现没有,就是FeignClientsRegistrar做处理的。来分析下重点方法registerFeignClients和registerFeignClient
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
//classPath扫描器
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
//ClassPathScanningCandidateComponentProvider扫描的basePackage集合
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
//扫描器用于扫描标注了@FeignClient类的拦截器
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
//clients属性为空,以@EnableFeignClients的value、basePackage等为根包扫描
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
//@EnableFeignClients的clients属性不为空,解析clients的类和根包
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)));
}
//1.根据basePackage找到目标@FeignClient接口
//2.检查是否为接口
//3.将找到的接口注册为FeignClientFactoryBean
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 String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("contextId");
if (!StringUtils.hasText(value)) {
value = (String) client.get("value");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("serviceId");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
+ FeignClient.class.getSimpleName());
}
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);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
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 = contextId + "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);
}
}
可以看到最后注册beanDefinition时,我们看到注册了FeignClientFactoryBean这一FactoryBean。 我们看看工厂bean FeignClientFactoryBean是如何构造对象的。
class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
ApplicationContextAware {
//省略
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
//1.获取FeignContext,在FeignAutoConfiguration声明
FeignContext context = applicationContext.getBean(FeignContext.class);
//2.构造Feign builder
Feign.Builder builder = feign(context);
//3.如果没有设置url参数
if (!StringUtils.hasText(this.url)) {
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
//4.设置path
url += cleanPath();
//5.获取Client(用于执行最终HTTP/HTTPS请求,比如LoadBalancerFeignClient),
//构造反射实例
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
//存在url参数,构造非loadBalance的请求实例(target)
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 load 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 (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
//在FeignContext中获取一些在FeignClientsConfiguration中声明,Feign需要用到的组件
protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
//获取Client
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
//从Context获取Targeter,Targeter用于生成最终target实例(对应我的例子是被调用的通过反射生成的UserFeign实例)
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}
//省略
}
在非调试情况下(即我们没设置url参数), 我们来看看targeter.target(this, builder, context, target)做了什么。
Targeter接口是构造被请求的代理bean的类。有两个实现类HystrixTargeter、DefaultTargeter。
HystrixTargeter会比默认的多设置一些回滚措施,用到Feign的Contract属性, 我会先从DefaultTargeter说起。
DefaultTargeter会通过Feign.Builder#target(Target target)生成实例。我们来看看代码。
public abstract class Feign {
//省略
public static class Builder {
private final List<RequestInterceptor> requestInterceptors =
new ArrayList<RequestInterceptor>();
private Logger.Level logLevel = Logger.Level.NONE;
private Contract contract = new Contract.Default();
private Client client = new Client.Default(null, null);
private Retryer retryer = new Retryer.Default();
private Logger logger = new NoOpLogger();
private Encoder encoder = new Encoder.Default();
private Decoder decoder = new Decoder.Default();
private QueryMapEncoder queryMapEncoder = new QueryMapEncoder.Default();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
private boolean decode404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
//省略
public <T> T target(Class<T> apiType, String url) {
return target(new HardCodedTarget<T>(apiType, url));
}
public <T> T target(Target<T> target) {
return build().newInstance(target);
}
//默认实现就是创建一个ReflectiveFeign实例
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
}
//省略
}
在解读ReflectiveFeign前介绍几个概念:
1、InvocationHandlerFactory 是控制反射方法分发的接口,create方法返回InvocationHandler。
2、InvocationHandlerFactory.MethodHandler 最终将对代理类方法调用转换成HTTP请求的地方,请看实现类SynchronousMethodHandler
3、InvocationHandlerFactory.Default 默认实现,作为构造参数传入ReflectiveFeign,create方法创建的是new ReflectiveFeign.FeignInvocationHandler(target, dispatch)。
4、ReflectiveFeign.ParseHandlersByName 作为构造参数传入ReflectiveFeign,核心方法apply(Target key)先将标注了@FeignClient的接口的方法解析出待处理的元数据List, 然后创建出方法名和方法处理器的map映射Map<String, MethodHandler>String是方法名,方法处理器通过SynchronousMethodHandler.Factory#create创建。
5、FeignInvocationHandler 为处理一般方法的处理器
6、DefaultMethodHandler 为处理接口默认方法的处理器
有了以上介绍,接下来简单分析ReflectiveFeign的newInstance方法。
public class ReflectiveFeign extends Feign {
private final ParseHandlersByName targetToHandlersByName;
private final InvocationHandlerFactory factory;
private final QueryMapEncoder queryMapEncoder;
ReflectiveFeign(ParseHandlersByName targetToHandlersByName, InvocationHandlerFactory factory,
QueryMapEncoder queryMapEncoder) {
this.targetToHandlersByName = targetToHandlersByName;
this.factory = factory;
this.queryMapEncoder = queryMapEncoder;
}
..
/**
* creates an api binding to the {@code target}. As this invokes reflection, care should be taken
* to cache the result.
*/
@SuppressWarnings("unchecked")
@Override
public <T> T newInstance(Target<T> target) {
//创建方法名和方法处理器的map映射
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) {
continue;
//判断是否为接口的默认方法,DefaultMethodHandler的处理逻辑是直接调用会原接口的default方法
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
//方法处理map
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
//jdk动态代理创建对象
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[] {target.type()}, handler);
//将默认方法处理器也绑定到代理对象上
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
//省略
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//自定义的equals、hashCode和toString的处理
if ("equals".equals(method.getName())) {
try {
Object 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();
}
//分发调用到对应方法的InvocationHandlerFactory.MethodHandler
return dispatch.get(method).invoke(args);
}
//省略
}
初始化完成。
五、OpenFeign 执行分析
上图是OpenFeign构造的代理对象被调用时的时序图。
1、代理对象被执行
2、找到对应SynchronousMethodHandler进行方法调用。
3、构造RequestTemplate
4、LoadBalancerFeignClient执行负载请求
5、FeignLoadBalancer通过ILoadBalancer选择合适Server,通过Server重组URI,通过RibbonRequest持有的Client执行实际HTTP请求包装成Response。
6、SynchronousMethodHandler通过Decoder将请求响应用Decoder解码成最终结果。
下面介绍执行过程中涉及到源码中的部分组件。
1、RequestTemplate 是一个HTTP请求内容的抽象。
2、RequestTemplate.Factory 将方法参数解析成RequestTemplate。
3、Retryer 我在上面的时序图没有标注出来,实际上它在SynchronousMethodHandler的执行中控制重试逻辑。
4、RequestInterceptor 在SynchronousMethodHandler发起执行中,会使用该拦截器对RequestTemplate进行处理。这是一个拓展点。
5、Logger 执行请求时打日志(在debug时打)。默认为Logger.Level.NONE即不打日志,可以增加bean覆盖。
- Logger.Level.NONE 不打印信息
- Logger.Level.BASIC 打印请求url和响应码。
- Logger.Level.HEADERS 打印BASIC信息外加header信息
- Logger.Level.FULL 打印所有
6、LoadBalancerFeignClient Client接口的实现类,是具有负载均衡能力的Client。Client接口为执行HTTP的接口,Client.Default是最终发出HTTP请求的类。
7、FeignLoadBalancer FeignLoadBalancer通过ILoadBalancer选择合适Server,通过Server重组URI,通过RibbonRequest持有的Client执行实际HTTP请求包装成Response。
8、LoadBalancerCommand ribbon的rxJava实现,执行负载流程逻辑的组件。
9、ILoadBalancer ribbon的负载均衡器抽象。
熔断: 在FeignClientsConfiguration中, 当配置了feign.hystrix.enabled,Feign Builder使用HystrixFeign.builder()。
所以build的时候新建HystrixInvocationHandler和HystrixDelegatingContract实例。
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch, setterFactory,
nullableFallbackFactory);
}
});
super.contract(new HystrixDelegatingContract(contract));
return super.build();
}
来看看HystrixInvocationHandler的hystrix调用代码
final class HystrixInvocationHandler implements InvocationHandler {
//省略
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
//省略
HystrixCommand<Object> hystrixCommand =
new HystrixCommand<Object>(setterMethodMap.get(method)) {
//实际执行
@Override
protected Object run() throws Exception {
try {
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
} catch (Exception e) {
throw e;
} catch (Throwable t) {
throw (Error) t;
}
}
@Override
protected Object getFallback() {
if (fallbackFactory == null) {
return super.getFallback();
}
try {
//用配置的fallbackFactory创建fallback实例
Object fallback = fallbackFactory.create(getExecutionException());
Object result = fallbackMethodMap.get(method).invoke(fallback, args);
//根据fallback对象的returntype解析包装内的结果返回
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 AssertionError(e);
} catch (InvocationTargetException e) {
// Exceptions on fallback are tossed by Hystrix
throw new AssertionError(e.getCause());
}
}
};
//根据方法的return去返回结果
if (Util.isDefault(method)) {
return hystrixCommand.execute();
} else 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();
}
//省略
}
标签:return,target,Spring,private,method,均衡器,new,class,Cloud
From: https://blog.csdn.net/zhengzhaoyang122/article/details/144647372