spring cloud openfeign 深入学习

本文深入解析SpringCloud中OpenFeign的使用机制,包括@EnableFeignClients与@FeignClient注解的详细属性说明,如name、url、fallback等,以及它们在微服务调用中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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无法正确工作。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值