栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Cloud学习教程之DiscoveryClient的深入探究

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Cloud学习教程之DiscoveryClient的深入探究

前言

当我们使用@DiscoveryClient注解的时候,会不会有如下疑问:它为什么会进行注册服务的操作,它不是应该用作服务发现的吗?下面我们就来深入的探究一下其源码。

一、Springframework的LifeCycle接口

要搞明白这个问题我们需要了解一下这个重要的接口:



package org.springframework.context;


public interface Lifecycle {

 
 void start();

 
 void stop();

 
 boolean isRunning();
}

该接口定义启动/停止生命周期控制方法,当spring ioc容器启动或停止时将发送一个启动或者停止的信号通知到各个组件,因此我们可以在对应的方法里做我们想要的事情。我们可以通过类图发现我们常用的ClasspathXmlApplicationContext类就实现了该接口

下面我们来简单演示一下案例,创建类MyLifeCycle:

package org.hzgj.spring.study.context;
import org.springframework.context.SmartLifecycle;
public class MyLifeCycle implements SmartLifecycle {
 @Override
 public void start() {
 System.out.println("MyLifeCycle start ....");
 }

 @Override
 public void stop() {
 System.out.println("MyLifeCycle stop .....");
 }

 @Override
 public boolean isRunning() {
 return false;
 }

 @Override
 public boolean isAutoStartup() {
 return true;
 }

 @Override
 public void stop(Runnable callback) {

 }

 @Override
 public int getPhase() {
 System.out.println("phase");
 return 10;
 }
}

在这里我们继承SmartLifeCycle该接口继承了LifeCycle, isRunning方法用于检测当前的组件是否处在运行状态,注意只有当isRunning返回值为false才可以运行

我们把MyLifeCycle配置到spring配置文件里,通过ClassPathXmlApplicationContext运行 会得到如下结果:

另外在这里的getPhase方法,这个是定义阶段值(可以理解为优先级,值越小对应的LifeCycle越先执行)

二、DiscoveryClient源码探究

@EnableDiscoveyClient



package org.springframework.cloud.client.discovery;

import java.lang.annotation.documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.import;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@documented
@Inherited
@import(EnableDiscoveryClientimportSelector.class)
public @interface EnableDiscoveryClient {

 
 boolean autoRegister() default true;
}

请注意 @import(EnableDiscoveryClientimportSelector.class) 我们可以参考一下这个类:



package org.springframework.cloud.client.discovery;

import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.cloud.commons.util.SpringFactoryimportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.type.Annotationmetadata;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.linkedHashMap;
import java.util.List;

@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientimportSelector
 extends SpringFactoryimportSelector {
 @Override
 public String[] selectimports(Annotationmetadata metadata) {
 String[] imports = super.selectimports(metadata);

 AnnotationAttributes attributes = AnnotationAttributes.fromMap(
 metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));
 boolean autoRegister = attributes.getBoolean("autoRegister");

 if (autoRegister) {
 List importsList = new ArrayList<>(Arrays.asList(imports));
 importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
 imports = importsList.toArray(new String[0]);
 } else {
 Environment env = getEnvironment();
 if(ConfigurableEnvironment.class.isInstance(env)) {
 ConfigurableEnvironment configEnv = (ConfigurableEnvironment)env;
 linkedHashMap map = new linkedHashMap<>();
 map.put("spring.cloud.service-registry.auto-registration.enabled", false);
 MapPropertySource propertySource = new MapPropertySource(
  "springCloudDiscoveryClient", map);
 configEnv.getPropertySources().addLast(propertySource);
 }
 }
 return imports;
 }

 @Override
 protected boolean isEnabled() {
 return new RelaxedPropertyResolver(getEnvironment()).getProperty(
 "spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
 }

 @Override
 protected boolean hasDefaultFactory() {
 return true;
 }
}

这个类重写的方法来自于接口 importSelector,我们可以根据 if(autoRegister)下的代码追踪到类:org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration ,我们来看一下结构图:

我们可以得知这个类实现了Lifecycle接口,那么我们看一看start方法,此方法在它的父类AbstractDiscoveryLifecycle里:



package org.springframework.cloud.client.discovery;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import javax.annotation.PreDestroy;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;


@Deprecated
public abstract class AbstractDiscoveryLifecycle implements DiscoveryLifecycle,
 ApplicationContextAware, ApplicationListener {

 private static final Log logger = LogFactory.getLog(AbstractDiscoveryLifecycle.class);

 private boolean autoStartup = true;

 private AtomicBoolean running = new AtomicBoolean(false);

 private int order = 0;

 private ApplicationContext context;

 private Environment environment;

 private AtomicInteger port = new AtomicInteger(0);

 protected ApplicationContext getContext() {
 return context;
 }

 @Override
 public void setApplicationContext(ApplicationContext applicationContext)
 throws BeansException {
 this.context = applicationContext;
 this.environment = this.context.getEnvironment();
 }

 @Deprecated
 protected Environment getEnvironment() {
 return environment;
 }

 @Deprecated
 protected AtomicInteger getPort() {
 return port;
 }

 @Override
 public boolean isAutoStartup() {
 return this.autoStartup;
 }

 @Override
 public void stop(Runnable callback) {
 try {
 stop();
 } catch (Exception e) {
 logger.error("A problem occurred attempting to stop discovery lifecycle", e);
 }
 callback.run();
 }

 @Override
 public void start() {
 if (!isEnabled()) {
 if (logger.isDebugEnabled()) {
 logger.debug("Discovery Lifecycle disabled. Not starting");
 }
 return;
 }

 // only set the port if the nonSecurePort is 0 and this.port != 0
 if (this.port.get() != 0 && getConfiguredPort() == 0) {
 setConfiguredPort(this.port.get());
 }
 // only initialize if nonSecurePort is greater than 0 and it isn't already running
 // because of containerPortInitializer below
 if (!this.running.get() && getConfiguredPort() > 0) {
 register();
 if (shouldRegisterManagement()) {
 registerManagement();
 }
 this.context.publishEvent(new InstanceRegisteredEvent<>(this,
  getConfiguration()));
 this.running.compareAndSet(false, true);
 }
 }

 @Deprecated
 protected abstract int getConfiguredPort();
 @Deprecated
 protected abstract void setConfiguredPort(int port);

 
 protected boolean shouldRegisterManagement() {
 return getManagementPort() != null && ManagementServerPortUtils.isDifferent(this.context);
 }

 
 @Deprecated
 protected abstract Object getConfiguration();


 
 protected abstract void register();

 
 protected void registerManagement() {
 }

 
 protected abstract void deregister();

 
 protected void deregisterManagement() {
 }

 
 protected abstract boolean isEnabled();

 
 @Deprecated
 protected String getManagementServiceId() {
 // TODO: configurable management suffix
 return this.context.getId() + ":management";
 }

 
 @Deprecated
 protected String getManagementServiceName() {
 // TODO: configurable management suffix
 return getAppName() + ":management";
 }

 
 @Deprecated
 protected Integer getManagementPort() {
 return ManagementServerPortUtils.getPort(this.context);
 }

 
 @Deprecated
 protected String getAppName() {
 return this.environment.getProperty("spring.application.name", "application");
 }

 @Override
 public void stop() {
 if (this.running.compareAndSet(true, false) && isEnabled()) {
 deregister();
 if (shouldRegisterManagement()) {
 deregisterManagement();
 }
 }
 }

 @PreDestroy
 public void destroy() {
 stop();
 }

 @Override
 public boolean isRunning() {
 return this.running.get();
 }

 protected AtomicBoolean getRunning() {
 return running;
 }

 @Override
 public int getOrder() {
 return this.order;
 }

 @Override
 public int getPhase() {
 return 0;
 }
 @Override
 @Deprecated
 public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
 // TODO: take SSL into account
 // Don't register the management port as THE port
 if (!"management".equals(event.getApplicationContext().getNamespace())) {
 this.port.compareAndSet(0, event.getEmbeddedServletContainer().getPort());
 this.start();
 }
 }
}

注意在start方法里有一段这个代码:

if (!this.running.get() && getConfiguredPort() > 0) {
 register();
 if (shouldRegisterManagement()) {
 registerManagement();
 }
 this.context.publishEvent(new InstanceRegisteredEvent<>(this,
  getConfiguration()));
 this.running.compareAndSet(false, true);
 }

请注意register() 这个方法是本类里的抽象方法。那么我们回过头看一下AbstractAutoServiceRegistration类里的代码,我这里只贴出关键部分:

//.....

 protected AbstractAutoServiceRegistration(ServiceRegistry serviceRegistry, AutoServiceRegistrationProperties properties) {
 this.serviceRegistry = serviceRegistry;
 this.properties = properties;
 }

//......

 @Override
 protected void register() {
 this.serviceRegistry.register(getRegistration());
 }

我们可以发现在构造函数里传了一个ServiceRegistry类型,这个接口是SpringCloud给我们提供用于服务注册的接口。在这里EurekaServiceRegistry就是实现了此接口:



package org.springframework.cloud.netflix.eureka.serviceregistry;

import java.util.HashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;

import com.netflix.appinfo.InstanceInfo;


public class EurekaServiceRegistry implements ServiceRegistry {

 private static final Log log = LogFactory.getLog(EurekaServiceRegistry.class);

 @Override
 public void register(EurekaRegistration reg) {
 maybeInitializeClient(reg);

 if (log.isInfoEnabled()) {
 log.info("Registering application " + reg.getInstanceConfig().getAppname()
  + " with eureka with status "
  + reg.getInstanceConfig().getInitialStatus());
 }

 reg.getApplicationInfoManager()
 .setInstanceStatus(reg.getInstanceConfig().getInitialStatus());

 if (reg.getHealthCheckHandler() != null) {
 reg.getEurekaClient().registerHealthCheck(reg.getHealthCheckHandler());
 }
 }
 
 private void maybeInitializeClient(EurekaRegistration reg) {
 // force initialization of possibly scoped proxies
 reg.getApplicationInfoManager().getInfo();
 reg.getEurekaClient().getApplications();
 }
 
 @Override
 public void deregister(EurekaRegistration reg) {
 if (reg.getApplicationInfoManager().getInfo() != null) {

 if (log.isInfoEnabled()) {
 log.info("Unregistering application " + reg.getInstanceConfig().getAppname()
  + " with eureka with status DOWN");
 }

 reg.getApplicationInfoManager().setInstanceStatus(InstanceInfo.InstanceStatus.DOWN);

 //shutdown of eureka client should happen with EurekaRegistration.close()
 //auto registration will create a bean which will be properly disposed
 //manual registrations will need to call close()
 }
 }

 @Override
 public void setStatus(EurekaRegistration registration, String status) {
 InstanceInfo info = registration.getApplicationInfoManager().getInfo();

 //TODO: howto deal with delete properly?
 if ("CANCEL_OVERRIDE".equalsIgnoreCase(status)) {
 registration.getEurekaClient().cancelOverrideStatus(info);
 return;
 }

 //TODO: howto deal with status types across discovery systems?
 InstanceInfo.InstanceStatus newStatus = InstanceInfo.InstanceStatus.toEnum(status);
 registration.getEurekaClient().setStatus(newStatus, info);
 }

 @Override
 public Object getStatus(EurekaRegistration registration) {
 HashMap status = new HashMap<>();

 InstanceInfo info = registration.getApplicationInfoManager().getInfo();
 status.put("status", info.getStatus().toString());
 status.put("overriddenStatus", info.getOverriddenStatus().toString());

 return status;
 }

 public void close() {
 }
}

那么至此我们可以总结如下几点:

  1、使用@DiscoveryClient注册服务是利用了LifeCycle机制,在容器启动时会执行ServiceRegistry的register()方法。

  2、使用@DiscoveryClient要比@EnableEurekaClient与@EnableEurekaServer更灵活,因为它屏蔽了对服务注册的实现,我们甚至可以自定义注册中心。

  3、这里面还会自动去寻找DiscoveryClient接口的实现用作服务发现

三、Discoveryclient实战之redis注册中心

 下面我们实现一个基于redis为注册中心的需求,来理解一下Discoveryclient。顺便理解一下Springcloud重要的接口:ServiceRegistry,ServiceInstance,再此之前我们先添加对redis的支持:

 compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis'

1、实现Registration接口

package com.hzgj.lyrk.member;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URI;
import java.util.Enumeration;
import java.util.Map;

@Component
public class RedisRegistration implements Registration {

 @Value("${server.port}")
 private Integer port;

 @Value("${spring.application.name}")
 private String applicationName;

 private String host;

 public void setHost(String host) {
 this.host = host;
 }

 public void setPort(Integer port) {
 this.port = port;
 }

 public void setApplicationName(String applicationName) {
 this.applicationName = applicationName;
 }

 @Override
 public String getServiceId() {

 return applicationName + ":" + getHost() + ":" + getPort();
 }

 @Override
 public String getHost() {
 try {
  if (host == null)
  return getLocalHostLANAddress().getHostAddress();
  else
  return host;
 } catch (Exception e) {
  e.printStackTrace();
 }
 return null;
 }

 @Override
 public int getPort() {
 return port;
 }

 @Override
 public boolean isSecure() {
 return false;
 }

 @Override
 public URI getUri() {
 return null;
 }

 @Override
 public Map getmetadata() {
 return null;
 }


 public String getServiceName() {
 return this.applicationName;
 }


 public InetAddress getLocalHostLANAddress() throws Exception {
 try {
  InetAddress candidateAddress = null;
  // 遍历所有的网络接口
  for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) {
  NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
  // 在所有的接口下再遍历IP
  for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {
   InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();
   if (!inetAddr.isLoopbackAddress()) {// 排除loopback类型地址
   if (inetAddr.isSiteLocalAddress()) {
    // 如果是site-local地址,就是它了
    return inetAddr;
   } else if (candidateAddress == null) {
    // site-local类型的地址未被发现,先记录候选地址
    candidateAddress = inetAddr;
   }
   }
  }
  }
  if (candidateAddress != null) {
  return candidateAddress;
  }
  // 如果没有发现 non-loopback地址.只能用最次选的方案
  InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
  return jdkSuppliedAddress;
 } catch (Exception e) {
  e.printStackTrace();
 }
 return null;
 }
}

该接口继承了ServiceIntance,那么此接口最主要作用就是定义了一个服务实例的规范,比如说它的serviceId是什么,端口号是什么等

2、实现ServiceRegistry的接口

package com.hzgj.lyrk.member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.data.redis.core.StringRedisTemplate;

public class RedisServiceRegistry implements ServiceRegistry {

 @Autowired
 private StringRedisTemplate redisTemplate;

 @Override
 public void register(RedisRegistration registration) {
 String serviceId = registration.getServiceId();
 redisTemplate.opsForList().leftPush(serviceId, registration.getHost() + ":" + registration.getPort());

 }

 @Override
 public void deregister(RedisRegistration registration) {
 redisTemplate.opsForList().remove(registration.getServiceId(), 1, registration.getHost() + ":" + registration.getPort());
 }

 @Override
 public void close() {
 //redisTemplate.d
 System.out.println("closed ...");
 }

 @Override
 public void setStatus(RedisRegistration registration, String status) {

 }

 @Override
 public  T getStatus(RedisRegistration registration) {
 return null;
 }
}

该接口主要作用是定义如何进行服务注册 ,服务注销,设置与获取服务状态等操作

3、继承 AbstractAutoServiceRegistration抽象类

package com.hzgj.lyrk.member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;

public class RedisAutoServiceRegistration extends AbstractAutoServiceRegistration {

 @Autowired
 private RedisRegistration redisRegistration;

 protected RedisAutoServiceRegistration(ServiceRegistry serviceRegistry, AutoServiceRegistrationProperties properties) {
 super(serviceRegistry, properties);
 // serviceRegistry.register(getRegistration());
 }

 @Override
 protected int getConfiguredPort() {
 return redisRegistration.getPort();
 }

 @Override
 protected void setConfiguredPort(int port) {

 }

 @Override
 protected Object getConfiguration() {
 return null;
 }

 @Override
 protected boolean isEnabled() {
 return true;
 }

 @Override
 protected RedisRegistration getRegistration() {
 return redisRegistration;
 }

 @Override
 protected RedisRegistration getManagementRegistration() {
 return null;
 }
}

4、定义DiscoveryClient的实现类RedisDiscoveryClient

package com.hzgj.lyrk.member;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class RedisDiscoveryClient implements DiscoveryClient {

 @Autowired
 private StringRedisTemplate redisTemplate;

 @Override
 public String description() {
 return "redis注册中心的服务发现";
 }

 @Override
 public ServiceInstance getLocalServiceInstance() {
 return null;
 }

 @Override
 public List getInstances(String serviceId) {

 return redisTemplate.opsForList().range(serviceId, 0, -1).
  parallelStream().map((Function) s -> {
  RedisRegistration redisRegistration = new RedisRegistration();
  redisRegistration.setApplicationName(serviceId);
  String hostName = StringUtils.split(s, ":")[0];
  String port = StringUtils.split(s, ":")[1];
  redisRegistration.setHost(hostName);
  redisRegistration.setPort(Integer.parseInt(port));
  //redisRegistration
  return redisRegistration;
 }).collect(Collectors.toList());


 }

 @Override
 public List getServices() {
 List list = new ArrayList<>();
 list.addAll(redisTemplate.keys("*"));
 return list;
 }
}

该类主要是针对于redis注册中心的服务发现

5、定义自动装配的类用以创建对应的bean

package com.hzgj.lyrk.member;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
@EnableConfigurationProperties(RedisConfig.class)
@ConditionalOnProperty(value = "spring.redis.registry.enabled", matchIfMissing = true)
public class RedisRegistryAutoConfiguration {

 @Bean
 RedisServiceRegistry redisServiceRegistry(RedisConfig redisConfig) {
 System.out.println(redisConfig.getHost());

 return new RedisServiceRegistry();
 }

 @Bean
 RedisAutoServiceRegistration redisAutoServiceRegistration(RedisServiceRegistry redisServiceRegistry) {
 return new RedisAutoServiceRegistration(redisServiceRegistry, new AutoServiceRegistrationProperties());
 }

 @Bean
 @Primary
 RedisDiscoveryClient redisDiscoveryClient() {
 return new RedisDiscoveryClient();
 }
}

6、定义启动类

package com.hzgj.lyrk.member;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;


@EnableDiscoveryClient
@SpringBootApplication(exclude = {SimpleDiscoveryClientAutoConfiguration.class, CompositeDiscoveryClientAutoConfiguration.class})
public class MemberApplication {

 public static void main(String[] args) {

 ConfigurableApplicationContext applicationContext = SpringApplication.run(MemberApplication.class, args);
 DiscoveryClient discoveryClient = applicationContext.getBean(DiscoveryClient.class);
 discoveryClient.getServices().forEach(action -> {
  System.out.println(action);
 });
 }
}

这里在SpringbootApplication注解里排除DiscoveryClient的默认装配。

当我们启动成功后可以发现,控制台已经输出对应的服务名称与地址:

我们再次通过gradle打包生成jar文件并运行:

java -jar member-server-0.0.1-SNAPSHOT.jar --server.port=8800

我们可以看到redis里已经缓存的有服务注册的值了:

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对考高分网的支持。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/141117.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号