nacos源码解析==SPI和spring.factories机制-服务注册-心跳发送-服务拉取-服务调用

news2024/11/16 8:20:08

Spring.Factories这种机制实际上是仿照java中的SPI扩展机制实现的

springboot核心基础之spring.factories机制 - 知乎

SpringBoot1==IDEA编写一个自己的starter_一个java开发的博客-CSDN博客_idea创建spring starter

======================================

spring-cloud-starter-alibaba-nacos-discovery

 将要注册到nacos中的服务使用的配置文件bootstrap.yaml

bootstrap.yaml中设置的配置项,会被扫描成对应的properties对象。

spring.factories中配置的类会被项目自动扫描注入。

com.alibaba.cloud.nacos.NacosServiceAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
public class NacosServiceAutoConfiguration {

   @Bean
   public NacosServiceManager nacosServiceManager() {
      return new NacosServiceManager();
   }

}

com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration

在这个类中,根据spring cloud提供的三个类和接口,分别进行了实现和继承。

org.springframework.cloud.client.serviceregistry.ServiceRegistry
org.springframework.cloud.client.serviceregistry.Registration
org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
		matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
		AutoServiceRegistrationAutoConfiguration.class,
		NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {

	@Bean
	public NacosServiceRegistry nacosServiceRegistry(
			NacosServiceManager nacosServiceManager,
			NacosDiscoveryProperties nacosDiscoveryProperties) {
		return new NacosServiceRegistry(nacosServiceManager, nacosDiscoveryProperties);
	}

	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosRegistration nacosRegistration(
			ObjectProvider<List<NacosRegistrationCustomizer>> registrationCustomizers,
			NacosDiscoveryProperties nacosDiscoveryProperties,
			ApplicationContext context) {
		return new NacosRegistration(registrationCustomizers.getIfAvailable(),
				nacosDiscoveryProperties, context);
	}

	@Bean
	@ConditionalOnBean(AutoServiceRegistrationProperties.class)
	public NacosAutoServiceRegistration nacosAutoServiceRegistration(
			NacosServiceRegistry registry,
			AutoServiceRegistrationProperties autoServiceRegistrationProperties,
			NacosRegistration registration) {
		return new NacosAutoServiceRegistration(registry,
				autoServiceRegistrationProperties, registration);
	}

}
@ConfigurationProperties("spring.cloud.service-registry.auto-registration")
public class AutoServiceRegistrationProperties {
@ConfigurationProperties("spring.cloud.nacos.discovery")
public class NacosDiscoveryProperties {
public class NacosRegistration implements Registration, ServiceInstance {

上面的代码已经将NacosAutoServiceRegistration注入了容器,其中有个register方法。

 com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration

NacosAutoServiceRegistration的父类AbstractAutoServiceRegistration实现了ApplicationListener接口,重写了onApplicationEvent方法,用该方法监听了WebServerInitializedEvent,这个事件有个实现类叫做ServletWebServerInitializedEvent,springboot中的tomcat在启动后就会发布一个ServletWebServerInitializedEvent,被onApplicationEvent监听到后会执行方法org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#bind,

org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#start

com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register

NacosServiceRegistry实现了接口ServiceRegistry,重写了接口的方法,同时自己定义了这些方法需要用到的一些成员变量NacosDiscoveryProperties(读取配置文件生成)和NacosServiceManager(管理NamingService)。register方法就是根据配置文件生成一个instance,再将instance交给namingService.registerInstance(serviceId, group, instance) ,

正式开始服务注册NacosNamingService

com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance) 

先看下NacosNamingService是怎么创建出来的:

com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register

com.alibaba.cloud.nacos.registry.NacosServiceRegistry#namingService

com.alibaba.cloud.nacos.NacosServiceManager#getNamingService()

com.alibaba.cloud.nacos.NacosServiceManager#buildNamingService

com.alibaba.nacos.api.NacosFactory#createNamingService(java.util.Properties)

com.alibaba.nacos.api.naming.NamingFactory#createNamingService(java.util.Properties)

    public static NamingService createNamingService(Properties properties) throws NacosException {
        try {
            Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
            Constructor constructor = driverImplClass.getConstructor(Properties.class);
            return (NamingService) constructor.newInstance(properties);
        } catch (Throwable e) {
            throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
        }
    }

com.alibaba.nacos.client.naming.NacosNamingService#NacosNamingService(java.util.Properties)

创建NacosNamingService完成后会立即执行init方法,

    private void init(Properties properties) throws NacosException {
        ValidatorUtils.checkInitParam(properties);
        this.namespace = InitUtils.initNamespaceForNaming(properties);
        InitUtils.initSerialization();
        InitUtils.initWebRootContext(properties);
        initLogName(properties);
        
        this.changeNotifier = new InstancesChangeNotifier();
        NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
        NotifyCenter.registerSubscriber(changeNotifier);
        this.serviceInfoHolder = new ServiceInfoHolder(namespace, properties);
//初始化
        this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);
    }
public NamingClientProxyDelegate(String namespace, ServiceInfoHolder serviceInfoHolder, Properties properties,
            InstancesChangeNotifier changeNotifier) throws NacosException {
//初始化服务列表拉取的定时任务
        this.serviceInfoUpdateService = new ServiceInfoUpdateService(properties, serviceInfoHolder, this,
                changeNotifier);
        this.serverListManager = new ServerListManager(properties, namespace);
        this.serviceInfoHolder = serviceInfoHolder;
        this.securityProxy = new SecurityProxy(properties, NamingHttpClientManager.getInstance().getNacosRestTemplate());
        initSecurityProxy();
//默认是瞬时对象,如果是瞬时对象,走这里会去初始化心跳管理的定时任务,用的GRPC
        this.httpClientProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, properties,
                serviceInfoHolder);
//如果不是瞬时对象,走这里会去初始化心跳管理的定时任务
        this.grpcClientProxy = new NamingGrpcClientProxy(namespace, securityProxy, serverListManager, properties,
                serviceInfoHolder);
    }

会创建一个服务列表拉取更新的对象(去nacos服务端拉取可用的服务列表),涉及一个ScheduledThreadPoolExecutor线程池的创建,核心线程数为CPU核数除以2 。老版本的这里应该是要创建一个HostReactor对象,新版变了。

public ServiceInfoUpdateService(Properties properties, ServiceInfoHolder serviceInfoHolder,
            NamingClientProxy namingClientProxy, InstancesChangeNotifier changeNotifier) {
//定时拉取服务列表的线程池
        this.executor = new ScheduledThreadPoolExecutor(initPollingThreadCount(properties),
                new NameThreadFactory("com.alibaba.nacos.client.naming.updater"));
        this.serviceInfoHolder = serviceInfoHolder;
        this.namingClientProxy = namingClientProxy;
        this.changeNotifier = changeNotifier;
    }

这个线程池什么时候使用的呢?

是在spring.factories中配置的下一个注入的对象中使用

com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class,
		CommonsClientAutoConfiguration.class })
@AutoConfigureAfter(NacosDiscoveryAutoConfiguration.class)
public class NacosDiscoveryClientConfiguration {

	@Bean
	public DiscoveryClient nacosDiscoveryClient(
			NacosServiceDiscovery nacosServiceDiscovery) {
		return new NacosDiscoveryClient(nacosServiceDiscovery);
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled", matchIfMissing = true)
//NacosWatch 这里会去nacos拉取服务列表
	public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager,
			NacosDiscoveryProperties nacosDiscoveryProperties) {
		return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties);
	}

}

因为NacosWatch实现了SmartLifecycle接口,所以在启动完Tomcat后会调用NacosWatch的start()方法。

com.alibaba.cloud.nacos.discovery.NacosWatch#start

com.alibaba.nacos.api.naming.NamingService#subscribe(java.lang.String, java.lang.String, java.util.List<java.lang.String>, com.alibaba.nacos.api.naming.listener.EventListener)

com.alibaba.nacos.client.naming.NacosNamingService#subscribe(java.lang.String, java.lang.String, java.util.List<java.lang.String>, com.alibaba.nacos.api.naming.listener.EventListener)

@Override
public void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener)
            throws NacosException {
        if (null == listener) {
            return;
        }
        String clusterString = StringUtils.join(clusters, ",");
//监听?监听啥
        changeNotifier.registerListener(groupName, serviceName, clusterString, listener);
//定时拉取
        clientProxy.subscribe(serviceName, groupName, clusterString);
    }

com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate#subscribe

com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService#scheduleUpdateIfAbsent

com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService#addTask

 executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS);

执行的线程对象为com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService.UpdateTask.run

 默认是瞬时对象,所以通过GRPC调用nacos的服务端,如果不是就走http的。老版本没有引入GRPC,只有HTTP.

com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy#queryInstancesOfService

在run方法的finally里可以为下次拉取设置时间,60秒拉取一次

} finally {
                if (!isCancel) {
                    executor.schedule(this, Math.min(delayTime << failCount, DEFAULT_DELAY * 60),
                            TimeUnit.MILLISECONDS);
                }
            }

会创建心跳管理对象,涉及一个ScheduledThreadPoolExecutor线程池的创建,核心线程数为CPU核数除以2 。

com.alibaba.nacos.client.naming.NacosNamingService#init

    public NamingHttpClientProxy(String namespaceId, SecurityProxy securityProxy, ServerListManager serverListManager,
            Properties properties, ServiceInfoHolder serviceInfoHolder) {
        super(securityProxy, properties);
        this.serverListManager = serverListManager;
        this.setServerPort(DEFAULT_SERVER_PORT);
        this.namespaceId = namespaceId;
//心跳管理
        this.beatReactor = new BeatReactor(this, properties);
        this.pushReceiver = new PushReceiver(serviceInfoHolder);
        this.maxRetry = ConvertUtils.toInt(properties.getProperty(PropertyKeyConst.NAMING_REQUEST_DOMAIN_RETRY_COUNT,
                String.valueOf(UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT)));
    }

    public BeatReactor(NamingHttpClientProxy serverProxy, Properties properties) {
        this.serverProxy = serverProxy;
        int threadCount = initClientBeatThreadCount(properties);
//线程池管理心跳
        this.executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setDaemon(true);
                thread.setName("com.alibaba.nacos.naming.beat.sender");
                return thread;
            }
        });
    }
    public static final int DEFAULT_CLIENT_BEAT_THREAD_COUNT =
            ThreadUtils.getSuitableThreadCount(1) > 1 ? ThreadUtils.getSuitableThreadCount(1) / 2 : 1;
    

再回到服务注册方法

com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance) 

默认是瞬时对象是GRPC调用,

com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy#registerService

    @Override
    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
                instance);
        redoService.cacheInstanceForRedo(serviceName, groupName, instance);
        doRegisterService(serviceName, groupName, instance);
    }
    
    /**
     * Execute register operation.
     *
     * @param serviceName name of service
     * @param groupName   group of service
     * @param instance    instance to register
     * @throws NacosException nacos exception
     */
    public void doRegisterService(String serviceName, String groupName, Instance instance) throws NacosException {
        InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
                NamingRemoteConstants.REGISTER_INSTANCE, instance);
//请求服务器进行注册
        requestToServer(request, Response.class);
        redoService.instanceRegistered(serviceName, groupName);
    }

 可以看到request对象中包含了该服务需要存到nacos服务端的信息,IP地址、端口之类的

 可以看到访问的是服务端的9848端口,转门提供给GRPC用的

com.alibaba.nacos.common.remote.client.RpcClient#request(com.alibaba.nacos.api.remote.request.Request, long)

端口与主端口的偏移量描述
98481000客户端gRPC请求服务端端口,用于客户端向服务端发起连接和请求
9849  1001服务端gRPC请求服务端端口,用于服务间同步等

在这个request方法里打上断点,可以发现在启动过程中,先发了4个请求去服务端获取该服务的配置文件: application-dev.yml                ruoyi-file        ruoyi-file.yml      ruoyi-file-dev.yml    

 

 实际能读到两个配置文件

如果不是瞬时对象的注册

com.alibaba.nacos.client.naming.remote.http.NamingHttpClientProxy#registerService

@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        
        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
                instance);
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
        if (instance.isEphemeral()) {
//心跳信息构建,默认值是15S一次检测
            BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
//将心跳检测任务BeatTask对象放入线程池
            beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
        final Map<String, String> params = new HashMap<String, String>(32);
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, groupedServiceName);
        params.put(CommonParams.GROUP_NAME, groupName);
        params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
        params.put(IP_PARAM, instance.getIp());
        params.put(PORT_PARAM, String.valueOf(instance.getPort()));
        params.put(WEIGHT_PARAM, String.valueOf(instance.getWeight()));
        params.put("enable", String.valueOf(instance.isEnabled()));
        params.put(HEALTHY_PARAM, String.valueOf(instance.isHealthy()));
        params.put(EPHEMERAL_PARAM, String.valueOf(instance.isEphemeral()));
        params.put(META_PARAM, JacksonUtils.toJson(instance.getMetadata()));
        
//将服务信息注册到nacos服务器  nacosUrlInstance=/nacos/v1/ns/instance
        reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
        
    }
//15S一次心跳检测
public static final long DEFAULT_HEART_BEAT_TIMEOUT = TimeUnit.SECONDS.toMillis(15);

发出心跳请求com.alibaba.nacos.client.naming.beat.BeatReactor.BeatTask#run

JsonNode result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);

===========================================

发送心跳请求

JsonNode result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);
 @Override
        public void run() {
            if (beatInfo.isStopped()) {
                return;
            }
            long nextTime = beatInfo.getPeriod();
            try {
//发送心跳
                JsonNode result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);
//服务端返回的心跳间隔时间,下次心跳以这个间隔为准
                long interval = result.get(CLIENT_BEAT_INTERVAL_FIELD).asLong();
                boolean lightBeatEnabled = false;
                if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) {
                    lightBeatEnabled = result.get(CommonParams.LIGHT_BEAT_ENABLED).asBoolean();
                }
                BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
                if (interval > 0) {
                    nextTime = interval;
                }
                int code = NamingResponseCode.OK;
                if (result.has(CommonParams.CODE)) {
                    code = result.get(CommonParams.CODE).asInt();
                }
                if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {
                    Instance instance = new Instance();
                    instance.setPort(beatInfo.getPort());
                    instance.setIp(beatInfo.getIp());
                    instance.setWeight(beatInfo.getWeight());
                    instance.setMetadata(beatInfo.getMetadata());
                    instance.setClusterName(beatInfo.getCluster());
                    instance.setServiceName(beatInfo.getServiceName());
                    instance.setInstanceId(instance.getInstanceId());
                    instance.setEphemeral(true);
                    try {
                        serverProxy.registerService(beatInfo.getServiceName(),
                                NamingUtils.getGroupName(beatInfo.getServiceName()), instance);
                    } catch (Exception ignore) {
                    }
                }
            } catch (NacosException ex) {
                NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}",
                        JacksonUtils.toJson(beatInfo), ex.getErrCode(), ex.getErrMsg());
    
            } catch (Exception unknownEx) {
                NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, unknown exception msg: {}",
                        JacksonUtils.toJson(beatInfo), unknownEx.getMessage(), unknownEx);
            } finally {
//每次都更新间隔时间
                executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
            }
        }

==================================================

服务管理,去服务端拉取最新的服务列表

com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService#scheduleUpdateIfAbsent

com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService#addTask

 executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS);

执行的线程对象为com.alibaba.nacos.client.naming.core.ServiceInfoUpdateService.UpdateTask.run

@Override
        public void run() {
//每次更新下次拉取的时间,默认1秒一次
            long delayTime = DEFAULT_DELAY;
            
            try {
                if (!changeNotifier.isSubscribed(groupName, serviceName, clusters) && !futureMap.containsKey(
                        serviceKey)) {
                    NAMING_LOGGER.info("update task is stopped, service:{}, clusters:{}", groupedServiceName, clusters);
                    isCancel = true;
                    return;
                }
                
                ServiceInfo serviceObj = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
                if (serviceObj == null) {
                    serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
                    serviceInfoHolder.processServiceInfo(serviceObj);
                    lastRefTime = serviceObj.getLastRefTime();
                    return;
                }
                
                if (serviceObj.getLastRefTime() <= lastRefTime) {
                    serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);
                    serviceInfoHolder.processServiceInfo(serviceObj);
                }
                lastRefTime = serviceObj.getLastRefTime();
                if (CollectionUtils.isEmpty(serviceObj.getHosts())) {
                    incFailCount();
                    return;
                }
                // TODO multiple time can be configured.
                delayTime = serviceObj.getCacheMillis() * DEFAULT_UPDATE_CACHE_TIME_MULTIPLE;
                resetFailCount();
            } catch (Throwable e) {
                incFailCount();
                NAMING_LOGGER.warn("[NA] failed to update serviceName: {}", groupedServiceName, e);
            } finally {
                if (!isCancel) {
//每次更新下次拉取的时间,默认1秒一次
                    executor.schedule(this, Math.min(delayTime << failCount, DEFAULT_DELAY * 60),
                            TimeUnit.MILLISECONDS);
                }
            }
        }

FailOver

开启的话定时备份服务信息

===================================================

服务调用A调用B

ruoyi cloud用的是openfeign+okhttp,

被调用者没特别的,就是调用者使用了openfeign+okhttp,

A启动的时候因为启动类上的@EnableFeignClients,该注解import了FeignClientsRegistrar,FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口,这个接口是spring提供的扩展接口,重写接口的registerBeanDefinitions方法,可以往IOC容器中放入bean。

org.springframework.cloud.openfeign.FeignClientsRegistrar#registerBeanDefinitions

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		registerDefaultConfiguration(metadata, registry);
//去扫描带有FeignClient注解的接口
		registerFeignClients(metadata, registry);
	}

org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient

比如这里扫描到了接口com.ruoyi.system.api.RemoteFileService, 生成JDK动态代理对象HardCodedTarget(type=RemoteFileService, name=ruoyi-file, url=http://ruoyi-file)

org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject

org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget

org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance

org.springframework.cloud.openfeign.DefaultTargeter#target

feign.Feign.Builder#target(feign.Target<T>)

feign.ReflectiveFeign#newInstance 可以看到给使用FeignClient标注的接口生成了对应的代理对象,接口中的每个方法都有对应的代理对象处理,代理对象的类为SynchronousMethodHandler。

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> 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);
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
		factoryBean.setRefreshableClient(isClientRefreshEnabled());
		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 instanceof Class ? (Class<?>) fallback
						: ClassUtils.resolveClassName(fallback.toString(), null));
			}
			Object fallbackFactory = attributes.get("fallbackFactory");
			if (fallbackFactory != null) {
				factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
						: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
			}
//当bean被加载的时候会使用这里的加载方法,生成代理对象
			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);
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

		registerOptionsBeanDefinition(registry, contextId);
	}

 接口中每个方法对应一个代理对象。

后面一旦通过这个接口实现调用,就用进入到代理对象SynchronousMethodHandler的invoke方法:

  @Override
  public Object invoke(Object[] argv) throws Throwable {
//生成请求的模板
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
//发起请求
        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 {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
   //发送前执行下切面,可以实现自定义AOP切面,在这一步会被执行,比如加上一些header
    Request request = targetRequest(template);

    if (logLevel != Logger.Level.NONE) {
      logger.logRequest(metadata.configKey(), logLevel, request);
    }

    Response response;
    long start = System.nanoTime();
    try {
//发送请求
      response = client.execute(request, options);
      // ensure the request is set. TODO: remove in Feign 12
      response = response.toBuilder()
          .request(request)
          .requestTemplate(template)
          .build();
    } catch (IOException e) {
      if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
      }
      throw errorExecuting(request, e);
    }
    long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

    if (decoder != null) {
      return responseInterceptor
          .aroundDecode(new InvocationContext(decoder, metadata.returnType(), response));
    }

    CompletableFuture<Object> resultFuture = new CompletableFuture<>();
    asyncResponseHandler.handleResponse(resultFuture, metadata.configKey(), response,
        metadata.returnType(), elapsedTime);

    try {
      if (!resultFuture.isDone())
        throw new IllegalStateException("Response handling not done");
      return resultFuture.join();
    } catch (CompletionException e) {
      Throwable cause = e.getCause();
      if (cause != null)
        throw cause;
      throw e;
    }
  }

 org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute

在这利用负载均衡选择一个本次请求使用的被调用者的IP和端口等生成newRequest 。这里和ribbon是怎么结合的呢?

默认是RoundRobinLoadBalancer
还有个nacosLoadBalancer 但是貌似不起作用,过程应该是:
要回到spring.factories,
其中的com.alibaba.cloud.nacos.loadbalancer.LoadBalancerNacosAutoConfiguration注入了NacosLoadBalancerClientConfiguration 。
其中又注入了nacosLoadBalancer。

com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancerClientConfiguration#nacosLoadBalancer 。

org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient#execute

org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient#choose(java.lang.String, org.springframework.cloud.client.loadbalancer.Request<T>)

org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer#choose(org.springframework.cloud.client.loadbalancer.Request)

com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer#choose
 

 org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils#executeWithLoadBalancerLifecycleProcessing 

feign.Client.Default#execute 默认使用这个,但是可以换成okhttp或者apache httpClient

feign.Client.Default#convertAndSend

发送成功请求到达被调用方的controller~

================================================

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/175181.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

know sth. new 大话C#的进阶必知点解析第1章 第5节 名贵中药材程序WPF显示图片报错,找不到资源? 什么原因

1 Ui布局代码&#xff1b; 布局方面&#xff0c;主要还是继承了原先的布局方式。包括图片的展示&#xff0c;也是用了最外层border边框的方式&#xff0c;边框加入背景颜色方式的图片展示&#xff1b; 去把目标图片进行显示出来&#xff0c;这个没有太多技术含量。 至于图片的…

Spring Boot操作数据库学习之整合Druid

文章目录一 Druid 简介二 配置数据源创建项目步骤及数据库内容三 整合操作3.1 添加Druid数据源依赖3.2 编写配置文件3.3 测试3.4 自定义绑定数据源设置3.5 导入Log4j的依赖&配置日志输出3.6 添加DruidDataSource组件3.7 测试3.8 配置 Druid 数据源监控3.9 配置过滤器一 Dru…

SAPIEN PowerShell Studio 介绍

PowerShell Studio是一款优秀的基于PowerShell研发的脚本编辑器&#xff0c;它拥有全新的代码分析、智能预选、xaml支持功能&#xff0c;能够给用户提供一套完整的软件开发环境&#xff0c;让用户能够更加轻松的工作&#xff0c;这样一来大家开发项目的效率就会大大提升。创建模…

Day866.binlogredoLog -MySQL实战

日志系统 Hi&#xff0c;我是阿昌&#xff0c;今天学习的是关于MySql的binlog&redoLog的内容。 一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块&#xff0c;最后到达存储引擎。 那么&#xff0c;一条更新语句的执行流程又是怎样的呢&#x…

人大金仓数据库的用户与角色

创建用户 create user 用户名 授予用户创建数据库权限 alter user 用户名 要给的权限 然后查看用户信息 \du 用户名 设置用户密码 没有口令不能登录 alter user 用户名 password ‘kingbase’; 修改用户的并发连接数 alter user 用户 connection limit 要设置的连接数; 修改…

as-if-serialhappens-before

一、as-if-serialas-if-serial语义的意思是&#xff1a;不管怎么重排序&#xff08;编译器和处理器为了提高并行度&#xff09;&#xff0c;&#xff08;单线程&#xff09;程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。 为了遵守as-if-seri…

java类成员/final/static都涉及到了2023025

类成员&#xff1a; 在Java类里只能包含成员变量、方法、构造器、初始化块、内部类&#xff08;包括接口、枚举&#xff09;这5种成员&#xff0c;目前已经介绍了前面4种&#xff0c;其中static可以修饰成员变量、方法、初始化块、内部类&#xff08;包括接口&#xff0c;枚举&…

显示器的相关知识

目录 显示器的作用 显示器的尺寸 人眼的可视角度 显示器的分辨率 显示器的刷新率 显示器的灰阶响应时间 显示器的色域 显示器的色深 显示器的色准 显示器的HDR参数 显示器的面板 画面撕裂 前言 导致画面撕裂的原因 防画面撕裂技术 视频的码率 显示器的作用 把…

Golang 多模块开发

Golang 多模块开发 今天学习下Golang中多模块的基础知识&#xff0c;学习多模块的运行原理&#xff0c;使用多模块的方式&#xff0c;可以让开发者的代码在其他多个模块中构建、运行。提高代码的复用&#xff0c;从而提高开发效率。 在今天的学习中&#xff0c;将在工作工作空…

bfs入门教程(广度优先搜索)(含图解)

源自《啊哈算法》 目录 bfs正文 题目 思路 完整代码1 完整代码2 再解炸弹人 题目 思路 完整代码1 完整代码2 总结 bfs正文 第四章--深度优先搜索中&#xff0c;我们用dfs找到了寻找小哈的最短路径 接下来&#xff0c;我们要用bfs&#xff08;Breadth First Sear…

Zookeeper的本地安装部署和分布式安装部署

文章目录一. 本地模式安装部署1&#xff09;安装前准备2&#xff09;配置修改3&#xff09;操作Zookeeper1.2 配置参数解读二. 分布式安装部署1&#xff09;集群规划2&#xff09;解压安装3&#xff09;配置服务器编号4&#xff09;配置zoo.cfg文件5&#xff09;集群操作客户端…

Leetcode.126 单词接龙 II

题目链接 Leetcode.126 单词接龙 II 题目描述 按字典 wordList完成从单词 beginWord到单词 endWord转化&#xff0c;一个表示此过程的 转换序列 是形式上像 beginWord -> s1 -> s2 -> ... -> sk这样的单词序列&#xff0c;并满足&#xff1a; 每对相邻的单词之间…

《高效能团队模式》读书笔记2

如果我们将团队类型的数量缩减为四类基本团队拓扑&#xff0c;这个问题就迎刃而解了。 流动式团队 赋能团队 复杂子系统团队 平台团队只要使用得当&#xff0c;这四类团队拓扑能够满足构建和运行现代软件系统的需要。结合有效的软件边界&#xff08;第6章&#xff09;和团队交互…

Java注解,元注解,自定义注解的使用

Java注解&#xff0c;元注解&#xff0c;自定义注解的使用Java注解基本的注解1.Override2.Deprecated3.SuppressWarnings4.SafeVarargs5.FunctionalInterfaceJava提供的元注解1.Retention2.Target3.Documented4.Inherited自定义注解自定义注解的使用Java注解 从JDK5开始,Java增…

从旺店通·企业奇门到用友U8通过接口集成数据

接入系统&#xff1a;旺店通企业奇门慧策&#xff08;原旺店通&#xff09;是一家技术驱动型智能零售服务商&#xff0c;基于云计算PaaS、SaaS模式&#xff0c;以一体化智能零售解决方案&#xff0c;帮助零售企业数字化智能化升级&#xff0c;实现企业规模化发展。对接系统&…

本地数仓项目(四)—— 即席查询

1 背景 本文描述本地数仓项目即席查询相关内容&#xff0c;主要涉及即席查询工具包括Presto、Druid、Kylin。 本文基于文章《本地数据仓库项目(一) —— 本地数仓搭建详细流程》 和《本地数仓项目(二)——搭建系统业务数仓详细流程》以及《本地数仓项目(三&#xff09;—— 数…

金蝶云星辰和旺店通企业版奇门单据接口集成

金蝶云星辰V1和旺店通企业奇门单据接口集成对接源平台:旺店通企业奇门慧策&#xff08;原旺店通&#xff09;是一家技术驱动型智能零售服务商&#xff0c;基于云计算PaaS、SaaS模式&#xff0c;以一体化智能零售解决方案&#xff0c;帮助零售企业数字化智能化升级&#xff0c;实…

图的基本概念以及表示方法(链式前向星重点理解,简单易懂版)

图表示一个集合中元素之间存在的多对多关系的一种数据结构。 图的一些定义 &#xff1a; 1.图由顶点和连接顶点的边构成&#xff0c;即G ( V , E ) &#xff0c;其中V为顶点集合&#xff0c;E为边的集合。2.边表示两个顶点之间存在某种关系&#xff0c;边表示为&#xff08;…

Elasticsearch7.8.0版本高级查询—— 单字段排序文档

目录一、初始化文档数据二、单字段排序文档2.1、概述2.2、示例一、初始化文档数据 在 Postman 中&#xff0c;向 ES 服务器发 POST 请求 &#xff1a;http://localhost:9200/user/_doc/1&#xff0c;请求体内容为&#xff1a; { "name":"zhangsan", "…

可笑 在网页上复制点东西 还需要money?进来看~

前言 哈喽 大家好&#xff01; 我是木易巷&#xff0c;我回来啦&#xff01;&#xff01;&#xff01; 现在好多平台都变成了不开会员不能复制这样的情况。士可杀不可辱&#xff01;作为一个优秀的复制粘贴工程师&#xff0c;在网页上复制点东西&#xff0c;还需要我掏钱&#…