spring cloud 架构使用openfeign进行声明式的服务调用,之前我们已经写过了关于openfeign的简单搭建使用。
今天我们就不写使用了,我们来看一下openfeign的一些详解。
@EnableFeignClients
先看一下源码:
/*
* Copyright 2013-2018 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.springframework.cloud.openfeign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
/**
* Scans for interfaces that declare they are feign clients (via {@link FeignClient
* <code>@FeignClient</code>}). Configures component scanning directives for use with
* {@link org.springframework.context.annotation.Configuration
* <code>@Configuration</code>} classes.
*
* @author Spencer Gibb
* @author Dave Syer
* @since 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
/**
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
* declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
* {@code @ComponentScan(basePackages="org.my.pkg")}.
* @return the array of 'basePackages'.
*/
String[] value() default {};
/**
* Base packages to scan for annotated components.
* <p>
* {@link #value()} is an alias for (and mutually exclusive with) this attribute.
* <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names.
*
* @return the array of 'basePackages'.
*/
String[] basePackages() default {};
/**
* Type-safe alternative to {@link #basePackages()} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
*
* @return the array of 'basePackageClasses'.
*/
Class<?>[] basePackageClasses() default {};
/**
* A custom <code>@Configuration</code> for all feign clients. 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
*/
Class<?>[] defaultConfiguration() default {};
/**
* List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
* @return list of FeignClient classes
*/
Class<?>[] clients() default {};
}
@EnableFeignClients注解用于spring boot 的启动类上,表示启动该项目的openfeign支持。
@EnableFeignClients注解有5个属性:
basePackages:指定扫描@FeignClient注解的包。例如,@EnableFeignClients(basePackages= {"com.travelsky.consumer.test"})
value:和basePackages一样,只是简化写法。@EnableFeignClients(value = {"com.travelsky.consumer.test"})
basePackageClasses:直接指定到接口。@EnableFeignClients(basePackageClasses= {ConsumerTestService.class})
clients:和basePackageClasses作用一致。指定到类。但是如果他不空,指定的basePackages会失效。
注意的是,他们都是返回数组格式,所以可以使用数组指定多个。
defaultConfiguration:spring cloud 将所有的@feignClient集中在一起重新创建一个ApplicationContext,并使用FeignClientsConfiguration进行配置,FeignClientsConfiguration中包括一个编码器,一个解码器,还有hystrix的一些配置。配置此项可以使用自定义的feignclient配置来代替默认配置。并且优先级高于默认配置。但一把情况下使用默认即可。
@FeignClient
接口上使用@FeignClient表名该接口是一个调用外部微服务的接口。下面看一下@FeignClient代码:
/*
* 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.springframework.cloud.openfeign;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Annotation for interfaces declaring that a REST client with that interface should be
* created (e.g. for autowiring into another component). If ribbon is available it will be
* used to load balance the backend requests, and the load balancer can be configured
* using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
*
* @author Spencer Gibb
* @author Venil Noronha
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {
/**
* The name of the service with optional protocol prefix. Synonym for {@link #name()
* name}. A name must be specified for all clients, whether or not a url is provided.
* Can be specified as property key, eg: ${propertyKey}.
*/
@AliasFor("name")
String value() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*
* @deprecated use {@link #name() name} instead
*/
@Deprecated
String serviceId() default "";
/**
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
*/
@AliasFor("value")
String name() default "";
/**
* Sets the <code>@Qualifier</code> value for the feign client.
*/
String qualifier() default "";
/**
* An absolute URL or resolvable hostname (the protocol is optional).
*/
String url() default "";
/**
* Whether 404s should be decoded instead of throwing FeignExceptions
*/
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
*/
Class<?>[] configuration() default {};
/**
* Fallback class for the specified Feign client interface. The fallback class must
* implement the interface annotated by this annotation and be a valid spring bean.
*/
Class<?> fallback() default void.class;
/**
* Define a fallback factory for the specified Feign client interface. The fallback
* factory must produce instances of fallback classes that implement the interface
* annotated by {@link FeignClient}. The fallback factory must be a valid spring
* bean.
*
* @see feign.hystrix.FallbackFactory for details.
*/
Class<?> fallbackFactory() default void.class;
/**
* Path prefix to be used by all method-level mappings. Can be used with or without
* <code>@RibbonClient</code>.
*/
String path() default "";
/**
* Whether to mark the feign proxy as a primary bean. Defaults to true.
*/
boolean primary() default true;
}
下面详解I一下:
ElementType.TYPE:表示该注解用于接口,类,枚举。
属性:
name:name的值是要调用的微服务的服务名。即eureka客户端中向注册中心注册的spring.application.name的值。
@FeignClient(name="travelsky-producer-test") 意思是调用服务名为travelsky-producer-test的微服务中暴露的接口。
value:含义和name一样。
name和value必须指定一个,不然启动会抛异常。让你给@FeignClient必须指定一个name或者value。
看一下源码:
getName方法:
/* for testing */ String getName(Map<String, Object> attributes) {
String name = (String) attributes.get("serviceId");
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("name");
}
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("value");
}
name = resolve(name);
return getName(name);
}
由此可证明 name 和value的意思一致。
url:指定访问的url
@FeignClient(name="travelsky-producer-test",url="http://localhost:8051")指定访问8051端口服务下的方法
源码:
getUrl方法:
private String getUrl(Map<String, Object> attributes) {
String url = resolve((String) attributes.get("url"));
return getUrl(url);
}
static String getUrl(String url) {
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
if (!url.contains("://")) {
url = "http://" + url;
}
try {
new URL(url);
}
catch (MalformedURLException e) {
throw new IllegalArgumentException(url + " is malformed", e);
}
}
return url;
}
比较简单就不多赘述了。
serviceId:再IDE中可以看出,他已经过时,不推荐使用。在此不在赘述。
qualifier:指定别名。默认是以name+“FeignClient”获取别称。但如果存在qualifier,则使用qualifier覆盖默认别称。
这个属性感觉用处不大。
看一下源码:
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);
}
decode404:当发生404错误时,是否启用解码器。默认为false,抛出404异常。开启时效果如下图:
configuration:和上面的@EnableFeignClients得defaultConfiguration一致。不在赘述。
重点说两个属性,fallback和fallbackFactory:
fallback:他是当要使用熔断时,熔断后的处理类。这个类要求必须被spring IOC管理,所以我们要给他一个@Component注解,或者@Service注解,让他被spring管理。并且他还要实现使用了@FeignClient这个注解的接口。
简单说就是他要是这个忌口的实现类并且被spring管理。
示例:
@Service
public class ConsumerTestServiceImpl implements ConsumerTestService{
@Override
public String get1(String id) {
return id + "consumer调用producer失败";
}
}
fallbackfactory:这个是熔断后的服务降级处理工厂。要求必须实现使用了@FeignClient这个注解的接口类型的fallbackfactory工厂。这个属性和fallback不同的是可以知道抛出的异常。
示例:
@Component
public class ConsumerTestFallbackFactory implements FallbackFactory<ConsumerTestService> {
@Override
public ConsumerTestService create(Throwable cause) {
return new ConsumerTestService() {
@Override
public String get1(String id) {
return "ERROR:"+ cause.getMessage();
}
};
}
}
path:提供一个请求前缀。举栗:
服务生产者暴露接口:
@RestController
@RequestMapping("/test")
public class ProducerTestController {
@GetMapping("/get1/{id}")
public String get1(@PathVariable("id")String id) {
return id+"==调用测试生产者接口";
}
}
服务消费者可以使用path来匹配生产者的统一前缀test
@FeignClient(value="travelsky-producer-test",path="/test")
primary:这个是标记当前@FeignClient为主bean,默认为true。这个我理解就是当有多个@FeignClient客户端的情况下发生hystrix降级操作,如果每个都指定为primary,那么系统就会发生错误,不知道使用哪一个进行降级。这时候就要将其中的某一个的primary属性设置为false。避免@Autowired无法正确工作。