This is my eighth day of the Genwen Challenge
Spring Cloud OpenFeign is the product of the Spring Cloud team incorporating native Feign into Spring Cloud. As you can see from the above listing of native Feign usage, the annotations used are all native to Feign (see GitHub address: github.com/OpenFeign/f… MVC annotations, not very easy to call. So Spring Cloud OpenFeign extends support for Spring MVC annotations and integrates Ribbon and Eureka to provide a load-balancing HTTP client implementation.
GitHub address: github.com/spring-clou…
@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List getStores(a);
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
}
Copy the code
-
Value, name: Value has the same function as name. If the URL is not configured, the configured value is used as the service name for service discovery. Otherwise, it is only a name.
-
ServiceId: The serviceId is not used. You can use name directly.
-
ContextId: Let’s say we have a user service, but there are many interfaces in the user service. We don’t want to define all the calling interfaces in one class, for example:
Client 1:
@FeignClient(name = "optimization-user") public interface UserRemoteClient1 { @GetMapping("/user/get") User getUser(@RequestParam("id") int id); } Copy the code
Client 2:
@FeignClient(name = "optimization-user") public interface UserRemoteClient2 { @GetMapping("/user2/get") public User getUser(@RequestParam("id") int id); } Copy the code
In this case, an error is reported when the project is started, because the Bean name conflicts.
Description:The bean 'optimization-user.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled. Action:Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true Copy the code
-
Url: The URL is used to specify the address of the service. It requests the service directly. The Ribbon does not select the service.
@FeignClient(name = "optimization-user", url = "http://localhost:8085") public interface UserRemoteClient { @GetMapping("/user/get") public User getUser(@RequestParam("id") int id); } Copy the code
-
Decode404: when call request 404 error, decode404 value is true, then will execute decoder decode, otherwise throw exception, throw exception is abnormal information, if configured fallback then will execute fallback logic, decoding is to return fixed data format:
{"timestamp":"The 2020-01-05 T09: thou. 154 + 0000"."status":404."error":"Not Found"."message":"No message available"."path":"/user/get"} Copy the code
If fallback is configured, the fallback logic will be executed:
-
Feign Encoder, Decoder, LogLevel, Contract, etc. can be customized in the configuration class. Configuration defines:
public class FeignConfiguration { @Bean public Logger.Level getLoggerLevel(a) { return Logger.Level.FULL; } @Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor(a) { return new BasicAuthRequestInterceptor("user"."password"); } @Bean public CustomRequestInterceptor customRequestInterceptor(a) { return new CustomRequestInterceptor(); } // Contract,feignDecoder,feignEncoder..... } Copy the code
Used in the column
@FeignClient(value = "optimization-user", configuration = FeignConfiguration.class) public interface UserRemoteClient { @GetMapping("/user/get") public User getUser(@RequestParam("id")int id); } Copy the code
-
Fallback: defines a fault-tolerant processing class, that is, fallback logic. The fallback class must implement the Feign Client interface and cannot know the fusing exception information. Fallback defines:
@Componentpublic class UserRemoteClientFallback implements UserRemoteClient { @Override public User getUser(int id) { return new User(0."The default fallback"); }}Copy the code
@FeignClient(value = "optimization-user", fallback = UserRemoteClientFallback.class) public interface UserRemoteClient { @GetMapping("/user/get") public User getUser(@RequestParam("id")int id); } Copy the code
-
FallbackFactory: also fault tolerant processing, can know the fuse exception information, fallbackFactory definition
@Componentpublic class UserRemoteClientFallbackFactory implements FallbackFactory { private Logger logger = LoggerFactory.getLogger(UserRemoteClientFallbackFactory.class); @Override public UserRemoteClient create(Throwable cause) { return new UserRemoteClient() { @Override public User getUser(int id) { logger.error("UserRemoteClient. Abnormal getUser", cause); return new User(0."Default"); }}; }}Copy the code
@FeignClient(value = "optimization-user", fallbackFactory = UserRemoteClientFallbackFactory.class) public interface UserRemoteClient { @GetMapping("/user/get") public User getUser(@RequestParam("id")int id); } Copy the code
-
Path: Path defines the common prefix used by FeignClient to access the interface. For example, the interface address is /user/get. If you define the prefix as user, then the path on the specific method only needs to write /get.
@FeignClient(name = "optimization-user", path="user") public interface UserRemoteClient { @GetMapping("/get") public User getUser(@RequestParam("id") int id); } Copy the code
-
Primary: primary corresponds to the @primary annotation, which defaults to true for a reason. When Feign implements fallback, it means that Feign Client has multiple identical beans in the Spring container. When we inject @AutoWired, we do not know which Bean to inject, so we need to set a high priority Bean. The @primary annotation does just that.
-
Qualifier: the @qualifier annotation is used for scenarios where the @autowired annotation is used.
If our FeignClient has fallback implementation, the default @FeignClient annotation primary=true means we have no problem using @AutoWired injection and will inject your FeignClient first.
If you incorrectly set primary to false, you will get an error at @autoWired’s injection location, not knowing which object to inject.
The obvious solution is that you can set primary to true, but if for some special reason you must remove primary=true, in which case we can configure a qualifier and use the @qualifier annotation to inject, as shown below:
Feign Client definition:
@FeignClient(name = "optimization-user", path="user", qualifier="userRemoteClient") public interface UserRemoteClient { @GetMapping("/get") public User getUser(@RequestParam("id") int id); } Copy the code
Feign Client injection
@Autowired@Qualifier("userRemoteClient") private UserRemoteClient userRemoteClient; Copy the code
Solution A: You can add the following configuration to allow beanDefinitions like beanName
spring.main.allow-bean-definition-overriding=true
Copy the code
Solution B: Manually specify a different contextId for each Client so there is no conflict.
The Feign Client Configuration requires a name. The name is obtained using the getClientName method:
String name = getClientName(attributes); registerClientConfiguration(registry, name,attributes.get("configuration"));
private String getClientName(Map 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());
}
Copy the code
So you can see that if contextId is configured then contextId will be used, if not value will be used then name and serviceId. None of these are configured by default, and an error is reported when a service has multiple Feign clients.
In the FeignClient registration, contextId is used as part of the alias of the Client. If qualifier is configured, qualifier is preferred as the alias.
private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map 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);
// Concatenate aliases
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary");
// has a default, won't be
// null beanDefinition.setPrimary(primary);
Qualifier takes precedence over qualifier
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
Copy the code