1. Series of articles
- You must understand and can understand microservices series 1: service registration
- You must understand and can understand microservices series 2: service unregister
2. The preface
Now that you know about service registration and de-registration, it’s time to call the service.
Before making a service invocation, you need to assemble the request header, request body, construct the client object, and invoke the remote service from the service provider URL. Although this approach can achieve remote invocation, it requires the consumer to understand the underlying invocation, and it also causes repetitive coding problems for the developer.
How does SpringCloud’s OpenFeign implement remote calls
3. Service invocation
3.1 Enabling Feign Remote Invocation
When we need to make a remote call using feign, we simply add the @enableFeignClients annotation to the entry
3.2 Scanning interfaces marked by FeignClient Annotations
The @EnableFeignClients annotation introduces the FeignClientsRegistrar configuration class that performs the scan action for the interfaces in the project flagged by the @FeignClient annotation
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
Set<String> basePackages = getBasePackages(metadata);
for (String basePackage : basePackages) {
candidateComponents.addAll(scanner.findCandidateComponents(basePackage));
}
Copy the code
AddIncludeFilter (new AnnotationTypeFilter(feignClient.class));
3.3 Create and register FeignClientFactoryBean
Through the components scanned in 3.2, each component creates a FeignClientFactoryBean and injects it into the IOC container. The type of FeignClientFactoryBean is the interface using the @FeignClient annotation
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map
attributes)
,> {
String className = annotationMetadata.getClassName();
Class clazz = ClassUtils.resolveClassName(className, null);
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
? (ConfigurableBeanFactory) registry : null;
String contextId = getContextId(beanFactory, attributes);
String name = getName(attributes);
// 1. Create FeignClientFactoryBean
FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
// 2. Set the properties
factoryBean.setBeanFactory(beanFactory);
factoryBean.setName(name);
factoryBean.setContextId(contextId);
factoryBean.setType(clazz);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
factoryBean.setUrl(getUrl(beanFactory, attributes));
factoryBean.setPath(getPath(beanFactory, attributes));
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
Object fallback = attributes.get("fallback");
if(fallback ! =null) {
factoryBean.setFallback(fallback instanceofClass ? (Class<? >) fallback : ClassUtils.resolveClassName(fallback.toString(),null));
}
Object fallbackFactory = attributes.get("fallbackFactory");
if(fallbackFactory ! =null) {
factoryBean.setFallbackFactory(fallbackFactory instanceofClass ? (Class<? >) fallbackFactory : ClassUtils.resolveClassName(fallbackFactory.toString(),null));
}
return factoryBean.getObject();
});
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.setLazyInit(true);
validate(attributes);
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
// has a default, won't be null
boolean primary = (Boolean) attributes.get("primary");
beanDefinition.setPrimary(primary);
String[] qualifiers = getQualifiers(attributes);
if (ObjectUtils.isEmpty(qualifiers)) {
qualifiers = new String[] { contextId + "FeignClient" };
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
// 3. Inject into the IOC container
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
Copy the code
3.4 injection FeignClientFactoryBean
When we invoke a remote service interface, we often need to inject the service via the @AutoWired annotation and then invoke the corresponding method
@RestController
public class OpenfeignConsumerController {
@Autowired
private IndexClient indexClient;
@GetMapping("/index/{name}")
public String index(@PathVariable(name = "name") String name) {
returnindexClient.index(name); }}@FeignClient(value = "openfeign-provider-service")
public interface IndexClient {
@GetMapping("/index/{name}")
String index(@PathVariable(name = "name") String name);
}
Copy the code
IndexClient is annotated by the @FeignClient annotation, which you can see from Section 3.3 is actually a FeignClientFactoryBean
FeignClientFactoryBean implements the FactoryBean interface, so when injected using the @AutoWired annotation, you inject the object returned by the getObject() method in FeignClientFactoryBean
3.5 FeignContext
From the naming, we know that FeignContext is feign’s context, which has a context property of type Map
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
Copy the code
That is to say, each @ FeignClient corresponding to a AnnotationConfigApplicationContext context, the application itself also has this one AnnotationConfigApplicationContext
Hence the parent-child context
3.6 Building Feign Builder
Builder builder = this.feign(context);
Copy the code
protected Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = (FeignLoggerFactory)this.get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
Builder builder = ((Builder)this.get(context, Builder.class)).logger(logger).encoder((Encoder)this.get(context, Encoder.class)).decoder((Decoder)this.get(context, Decoder.class)).contract((Contract)this.get(context, Contract.class));
this.configureFeign(context, builder);
this.applyBuildCustomizers(context, builder);
return builder;
}
Copy the code
To build Feign Builder is to get relevant components from the container for setting, and then customize Feign Builder
3.7 Obtaining Components from containers
This. Get (context, xxxx.class)) method is frequently used in 3.6, so you need to know how to implement this method. Here we use this
protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.contextId, type);
if (instance == null) {
throw new IllegalStateException("No bean found of type " + type + " for " + this.contextId);
} else {
returninstance; }}Copy the code
FeignContext, introduced in Section 3.5, maintains a collection of subcontainers, so it first fetches the named subcontainer from the collection
Since FeignContext maintains a collection of subcontainers, it’s important to know how the subcontainers are created, right
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name)); }}}return this.contexts.get(name);
}
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for(Class<? > configuration :this.configurations.get(name).getConfiguration()) { context.register(configuration); }}for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for(Class<? > configuration : entry.getValue().getConfiguration()) { context.register(configuration); } } } context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent ! =null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
Copy the code
Every time from the collection first, if there is no corresponding collection container is created, and then let the container registered PropertyPlaceholderAutoConfiguration and enclosing defaultConfigType
Through FeignContext constructor, we can learn this. DefaultConfigType is FeignClientsConfiguration
Open the FeignClientsConfiguration can see statement inside the Encoder and Decoder, such as Contract Bean
3.8 summary
At this point you should have a general summary in mind:
@FeignClient
The correspondingFeignClientFactoryBean
- injection
@FeignClient
The interface to the annotation, which is actually injectedFeignClientFactoryBean
In thegetObject()
Object returned FeignContext
As afeign
Context for each@FeignClient
Create a child container with the required declarationsBean
- The reason for every
@FeignClient
Declare a child container, is going to let@FeignClient
with@FeignClient
Is configured for isolation
3.9 Creating an Feign Instance
You can now create Feign instances using Feign Builder after setting the related properties in the Feign Builder chain
public Feign build(a) {
Client client = Capability.enrich(this.client, capabilities);
Retryer retryer = Capability.enrich(this.retryer, capabilities);
List<RequestInterceptor> requestInterceptors = this.requestInterceptors.stream()
.map(ri -> Capability.enrich(ri, capabilities))
.collect(Collectors.toList());
Logger logger = Capability.enrich(this.logger, capabilities);
Contract contract = Capability.enrich(this.contract, capabilities);
Options options = Capability.enrich(this.options, capabilities);
Encoder encoder = Capability.enrich(this.encoder, capabilities);
Decoder decoder = Capability.enrich(this.decoder, capabilities);
/ / 1. InvocationHandlerFactory used to create InvocationHandler
InvocationHandlerFactory invocationHandlerFactory =
Capability.enrich(this.invocationHandlerFactory, capabilities);
QueryMapEncoder queryMapEncoder = Capability.enrich(this.queryMapEncoder, capabilities);
/ / 2. SynchronousMethodHandler used to create SynchronousMethodHandler
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy, forceDecoding);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
Copy the code
3.10 Creating an Agent
@Override
public <T> T newInstance(Target<T> target) {
/ / 1. Traversal @ FeignClient tagging methods on the interface, through SynchronousMethodHandler. Create MethodHandler Factory
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;
} else if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
// 2. Build a mapping between Method and MethodHandler for Method routingmethodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method))); }}// 3. Create InvocationHandler with InvocationHandlerFactory
InvocationHandler handler = factory.create(target, methodToHandler);
// 4. Generate proxy objects
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
newClass<? >[] {target.type()}, handler);for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}
Copy the code
And here we know
@Autowired private IndexClient indexClient; Copy the code
Injection is a proxy object, when call IndexClient method can callback ReflectiveFeign FeignInvocationHandler invoke method
3.11 Method Invocation
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
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();
}
return dispatch.get(method).invoke(args);
}
Copy the code
Route to the corresponding SynchronousMethodHandler according to the method above and call its invoke()
@Override
public Object invoke(Object[] argv) throws Throwable {
// 1. Build the request template
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 2. Initiate a remote call through HTTP and return the result
return executeAndDecode(template, options);
} catch (RetryableException e) {
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if(propagationPolicy == UNWRAP && cause ! =null) {
throw cause;
} else {
throwth; }}if(logLevel ! = Logger.Level.NONE) { logger.logRetry(metadata.configKey(), logLevel); }continue; }}}Copy the code
This is where the remote service call comes in, and you can see how easy it is to use Openfeign to call the remote service as if it were a local method with the relevant annotations
4. Conclusion
After this Openfeign tutorial, you can try your hand at implementing an RPC framework using HttpClient and dynamic proxies