目錄
① EurekaInstanceConfigBean的源碼:
源碼:初始化創建EurekaInstanceConfigBean
源碼:HostInfo類 findFirstNonLoopbackHostInfo()方法
源碼:網絡工具類屬性類 InetUtilsProperties
源碼及解析圖
①實例配置過程
②創建實例過程
圖中已有簡要解析Eureka Instance 配置流程:
- 通過查看源碼可知,創建Eureka Instance對象,首先使用網絡工具類獲取的配置信息來創建Eureka Instance實例對象
,然後把從application文件中掃描到的配置信息,通過set方法的方式設置進Eureka Instance實例對象中去.
- new EurekaInstanceConfigBean(inetUtils),通過有參構造函數初始化屬性值
-
public EurekaInstanceConfigBean(InetUtils inetUtils) { this.inetUtils = inetUtils; this.hostInfo = this.inetUtils.findFirstNonLoopbackHostInfo(); this.ipAddress = this.hostInfo.getIpAddress(); this.hostname = this.hostInfo.getHostname(); }
- 調用inetUtils.findFirstNonLoopbackHostInfo(),和HostInfo的getIpAddress()和getHostname()方法
//獲取第一個非迴環主機信息 或者返回HostInfo("localhost","127.0.0.1")或迴環地址
public HostInfo findFirstNonLoopbackHostInfo() {
//
//獲取第一個非迴環地址,若result爲空==>
// 返回本地主機的地址如:192.168.0.103,並檢測是否連通,否,則返回表示環回地址的inetaddress
InetAddress address = findFirstNonLoopbackAddress();
if (address != null) {
//用1秒檢索主機名是否可用,返回主機信息(Hostname,IpAddress),
//否,則返回localhost,127.0.0.1
return convertAddress(address);
}
//②address爲空時
HostInfo hostInfo = new HostInfo();
//默認defaultHostname = "localhost"
hostInfo.setHostname(this.properties.getDefaultHostname());
//默認defaultIpAddress = "127.0.0.1"
hostInfo.setIpAddress(this.properties.getDefaultIpAddress());
return hostInfo;
}
//獲取第一個非迴環地址,若result爲空==>
// 返回本地主機的地址,並檢測是否連通,否,則返回表示環回地址的inetaddress
public InetAddress findFirstNonLoopbackAddress() {
//省略...
}
- 其中調用方法過程圖中及下面給出的源碼解析中都有深入詳細的分析
- 如果在application文件中配置了信息,在創建完實例時,springboot會把這些配置信息提取出並設置進實例對象中,
//EurekaInstanceConfigBean的getHostName方法
@Override
public String getHostName(boolean refresh) {
if (refresh && !this.hostInfo.override) {
this.ipAddress = this.hostInfo.getIpAddress();
this.hostname = this.hostInfo.getHostname();
}
// 以下簡代三元運算 ① ? ② : ③
return this.preferIpAddress ? this.ipAddress : this.hostname;
} 在application文件中①默認false ,②③默認無,
②③填寫this.hostInfo.override=true,不進入if語句
由上可知①false返回③hostname,①true返回②ipAddress
- 由上圖可知一般refresh=false,不走if語句體
- 可以看出從EurekaInstanceConfigBean中獲取信息,需要調用EurekaInstanceConfigBean的getHostname()方法,而getHostname()又調用了重寫的getHostName(false)方法
-
通過get方法獲取hostname,ipAddress的值
1. preferIpAddress =false時(默認值爲false,注意版本問題,最好顯性配置代替默認值)
1.1 hostname值:application配置過,則就是配置的hostname;application沒有配置過,則就是
通過網絡工具類獲取的hostname;
1.2 ipAddress的值同理,配置過取配置,否則取通過網絡工具類獲取ipAddress值2. preferIpAddress =true時, ipAddress和hostname值是相同的都是ip地址:簡稱爲非主機名註冊
2.1 hostname的值:當ipAddress在application配置過,hostname=ipAddress,
當ipAddress沒有在application文件中配置過,hostname值爲通過網絡工具類獲取的ipAddress
2.2 ipAddress的值與1.2同理 -
又因爲網絡工具類返回主機信息爲: 獲取第一個非迴環主機信息 或者返回HostInfo("localhost","127.0.0.1")或迴環地址
因此preferIpAddress =true時,同時要配置ipAddress的具體值
-
=========================================================
源碼: 解析及方法調用使用的的類和方法
① EurekaInstanceConfigBean的源碼:
//CloudEurekaInstanceConfig extends EurekaInstanceConfig
@ConfigurationProperties("eureka.instance")
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {private static final String UNKNOWN = "unknown";
//主機信息配置類
private HostInfo hostInfo;
//網絡工具類
private InetUtils inetUtils;
//執行器終結點的默認前綴
private String actuatorPrefix = "/actuator";
//獲取要在Eureka註冊的應用程序的名稱。
private String appname = UNKNOWN;private String appGroupName;
//是否應啓用實例以便在向Eureka註冊後立即獲取流量。有時,應用程序可能需要做一些預處理,然後纔可以接受流量。
private boolean instanceEnabledOnit;
private int nonSecurePort = 80;
private int securePort = 443;
private boolean nonSecurePortEnabled = true;//是否啓用安全端口傳輸
private boolean securePortEnabled;/*指示Eureka客戶機向Eureka服務器發送心跳以指示其仍處於活動狀態的頻率(秒)。如果在
LeaseExpirationDurationInseconds中指定的時間段內未收到心跳信號,則Eureka服務器將
從其視圖中刪除該實例,因爲不允許訪問該實例。注意,如果實例實現healthcheckcallback,
然後決定使其自身不可用,那麼它仍然不能接受通信。*/private int leaseRenewalIntervalInSeconds = 30;
/*指示Eureka客戶機向Eureka服務器發送心跳以指示其仍處於活動狀態的頻率(秒)。
如果在LeaseExpirationDurationInseconds中指定的時間段內未收到心跳信號,則Eureka服務器將
從其視圖中刪除該實例,因爲不允許訪問該實例。注意,如果實例實現healthcheckcallback,
然後決定使其自身不可用,那麼它仍然不能接受通信。*/
private int leaseExpirationDurationInSeconds = 90;/**獲取爲此實例定義的虛擬主機名。這通常是其他實例使用虛擬主機名查找此實例的方式。
請將其視爲類似於完全限定的域名,您的服務的用戶將需要查找此實例。
*
*/
private String virtualHostName = UNKNOWN;/**
* 獲取要在Eureka中註冊的此實例的唯一ID(在AppName範圍內)。
*/
private String instanceId;/**
* 獲取爲此實例定義的安全虛擬主機名。這通常是其他實例使用安全虛擬主機名查找此實例的方式。
請將其視爲類似於完全限定的域名,您的服務的用戶將需要找到此實例。
*/
private String secureVirtualHostName = UNKNOWN;/**
* 獲取與此實例關聯的AWS自動縮放組名。此信息在AWS環境中專門用於在實例啓動後自動將其退出服務,
並且該實例已被禁用以進行通信。
*/
private String aSGName;/**
* 獲取與此實例關聯的元數據名稱/值對。此信息將發送到Eureka服務器,並可供其他實例使用。
*/
private Map<String, String> metadataMap = new HashMap<>();/**
* 返回部署此實例的數據中心。如果實例部署在AWS中,則此信息用於獲取一些特定於AWS的實例信息。
*/
private DataCenterInfo dataCenterInfo = new MyDataCenterInfo(
DataCenterInfo.Name.MyOwn);/**
* 獲取實例的IP地址。此信息僅用於學術目的,因爲來自其他實例的通信主要使用@link gethostname(boolean)中提供的信息進行。
*/
private String ipAddress;
private String statusPageUrlPath = actuatorPrefix + "/info";
private String statusPageUrl;
private String homePageUrlPath = "/";
private String homePageUrl;
private String healthCheckUrlPath = actuatorPrefix + "/health";
private String healthCheckUrl;private String secureHealthCheckUrl;
/**
* 獲取用於查找屬性的命名空間。在spring cloud中被忽略。
*/
private String namespace = "eureka";/**
* 如果可以在配置時確定主機名(否則將從操作系統猜測)
*/
private String hostname;/**
* 猜測主機名時,應使用服務器的IP地址來引用操作系統報告的主機名。
*/
private boolean preferIpAddress = false;/**
* 註冊到rmeote eureka服務器的初始狀態。
*/
private InstanceStatus initialStatus = InstanceStatus.UP;
//默認地址解析順序爲取第一個
private String[] defaultAddressResolutionOrder = new String[0];
private Environment environment;//獲取主機名時,又調用getHoseName(false)方法
public String getHostname() {
return getHostName(false);
}@SuppressWarnings("unused")
private EurekaInstanceConfigBean() {
}
//==>使用有參構造函數
public EurekaInstanceConfigBean(InetUtils inetUtils) {
this.inetUtils = inetUtils;
this.hostInfo = this.inetUtils.findFirstNonLoopbackHostInfo();
this.ipAddress = this.hostInfo.getIpAddress();
this.hostname = this.hostInfo.getHostname();
}@Override
public String getInstanceId() {
if (this.instanceId == null && this.metadataMap != null) {
return this.metadataMap.get("instanceId");
}
return this.instanceId;
}@Override
public boolean getSecurePortEnabled() {
return this.securePortEnabled;
}
//配置hostname時,同時設置this.hostInfo.override = true
public void setHostname(String hostname) {
this.hostname = hostname;
this.hostInfo.override = true;
}
//配置ipAddress時,同時設置this.hostInfo.override = true
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
this.hostInfo.override = true;
}@Override
public String getHostName(boolean refresh) {
if (refresh && !this.hostInfo.override) {
this.ipAddress = this.hostInfo.getIpAddress();
this.hostname = this.hostInfo.getHostname();
}
return this.preferIpAddress ? this.ipAddress : this.hostname;
}@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
// set some defaults from the environment, but allow the defaults to use relaxed binding
String springAppName = this.environment.getProperty("spring.application.name", "");
if(StringUtils.hasText(springAppName)) {
setAppname(springAppName);
setVirtualHostName(springAppName);
setSecureVirtualHostName(springAppName);
}
}/**getter and setter **注意:getHostname()獲取主機名時,又調用getHoseName(false)方法來實現
**/
@Override
public boolean equals(Object o) {
//省略...
}@Override
public int hashCode() {
//省略..
}@Override
public String toString() {
//省略...
}}
源碼:初始化創建EurekaInstanceConfigBean
@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
ManagementMetadataProvider managementMetadataProvider) {
//從application文件中獲取hostname
String hostname = getProperty("eureka.instance.hostname");
//從application文件中獲取preferIpAddress
boolean preferIpAddress = Boolean.parseBoolean(getProperty("eureka.instance.prefer-ip-address"));
//從application文件中獲取ipAddress
String ipAddress = getProperty("eureka.instance.ip-address");
boolean isSecurePortEnabled = Boolean.parseBoolean(getProperty("eureka.instance.secure-port-enabled"));String serverContextPath = env.getProperty("server.context-path", "/");
int serverPort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080")));Integer managementPort = env.getProperty("management.server.port", Integer.class);// nullable. should be wrapped into optional
String managementContextPath = env.getProperty("management.server.servlet.context-path");// nullable. should be wrapped into optional
Integer jmxPort = env.getProperty("com.sun.management.jmxremote.port", Integer.class);//nullable
//通過EurekaInstanceConfigBean來創建實例
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);instance.setNonSecurePort(serverPort);
instance.setInstanceId(getDefaultInstanceId(env));
instance.setPreferIpAddress(preferIpAddress);
instance.setSecurePortEnabled(isSecurePortEnabled);
if (StringUtils.hasText(ipAddress)) {
instance.setIpAddress(ipAddress);
}if(isSecurePortEnabled) {
instance.setSecurePort(serverPort);
}if (StringUtils.hasText(hostname)) {
instance.setHostname(hostname);
}
String statusPageUrlPath = getProperty("eureka.instance.status-page-url-path");
String healthCheckUrlPath = getProperty("eureka.instance.health-check-url-path");if (StringUtils.hasText(statusPageUrlPath)) {
instance.setStatusPageUrlPath(statusPageUrlPath);
}
if (StringUtils.hasText(healthCheckUrlPath)) {
instance.setHealthCheckUrlPath(healthCheckUrlPath);
}ManagementMetadata metadata = managementMetadataProvider.get(instance, serverPort,
serverContextPath, managementContextPath, managementPort);if(metadata != null) {
instance.setStatusPageUrl(metadata.getStatusPageUrl());
instance.setHealthCheckUrl(metadata.getHealthCheckUrl());
if(instance.isSecurePortEnabled()) {
instance.setSecureHealthCheckUrl(metadata.getSecureHealthCheckUrl());
}
Map<String, String> metadataMap = instance.getMetadataMap();
if (metadataMap.get("management.port") == null) {
metadataMap.put("management.port", String.valueOf(metadata.getManagementPort()));
}
} else {
//without the metadata the status and health check URLs will not be set
//and the status page and health check url paths will not include the
//context path so set them here
if(StringUtils.hasText(managementContextPath)) {
instance.setHealthCheckUrlPath(managementContextPath + instance.getHealthCheckUrlPath());
instance.setStatusPageUrlPath(managementContextPath + instance.getStatusPageUrlPath());
}
}setupJmxPort(instance, jmxPort);
return instance;
}
源碼:HostInfo類 findFirstNonLoopbackHostInfo()方法
//獲取第一個非迴環主機信息
public HostInfo findFirstNonLoopbackHostInfo() {
//
InetAddress address = findFirstNonLoopbackAddress();
if (address != null) {
return convertAddress(address);
}
HostInfo hostInfo = new HostInfo();
hostInfo.setHostname(this.properties.getDefaultHostname());
hostInfo.setIpAddress(this.properties.getDefaultIpAddress());
return hostInfo;
}
//獲取第一個非迴環地址,若result爲空==> 返回本地主機的地址,並檢測是否連通,否則返回表示環回地址的inetaddress
public InetAddress findFirstNonLoopbackAddress() {
InetAddress result = null;
try {
int lowest = Integer.MAX_VALUE;//255
//for(①;②;③)循環遍歷網卡信息
for (Enumeration<NetworkInterface> nics = NetworkInterface
.getNetworkInterfaces(); nics.hasMoreElements();) {
NetworkInterface ifc = nics.nextElement();
if (ifc.isUp()) {//如果狀態爲工作的註冊上了
log.trace("Testing interface: " + ifc.getDisplayName());
if (ifc.getIndex() < lowest || result == null) {
lowest = ifc.getIndex();
}
else if (result != null) {
continue;
}// @formatter:off
if (!ignoreInterface(ifc.getDisplayName())) {
for (Enumeration<InetAddress> addrs = ifc
.getInetAddresses(); addrs.hasMoreElements();) {
InetAddress address = addrs.nextElement();
//是否爲ip4的非迴環地址,是賦值result = address
if (address instanceof Inet4Address //ip4地址
&& !address.isLoopbackAddress() //非迴環地址
&& isPreferredAddress(address)) { //是否首選地址
log.trace("Found non-loopback interface: "
+ ifc.getDisplayName());
result = address;
}
}
}
// @formatter:on
}
}
}
catch (IOException ex) {
log.error("Cannot get first non-loopback address", ex);
}
//result有值 返回非迴環地址
if (result != null) {
return result;
}try { //result爲空==> 返回本地主機的地址,並檢測是否連通,否則返回表示環回地址的inetaddress
//返回本地主機的地址。這是通過從系統中檢索主機的名稱,然後將該名稱解析爲 inetaddress來實現的。
//注意:解析的地址可能會被緩存一小段時間。如果存在安全管理器,則使用本地主機名調用其checkconnect方法
// checkConnect(String host, int port){如果指定主機和端口號的套接字連接不了,則拋出SecurityException},
//並使用@code-1作爲參數查看是否允許該操作。如果不允許該操作,則返回表示環回地址的inetaddress。
return InetAddress.getLocalHost();
}
catch (UnknownHostException e) {
log.warn("Unable to retrieve localhost");
}return null;
}
源碼:網絡工具類InetUtils
網絡工具類 InetUtils==>
public class InetUtils implements Closeable {
// TODO: maybe shutdown the thread pool if it isn't being used?
private final ExecutorService executorService;
private final InetUtilsProperties properties;private final Log log = LogFactory.getLog(InetUtils.class);
//初始化網絡工具類屬性及線程後臺運行
public InetUtils(final InetUtilsProperties properties) {
this.properties = properties;
this.executorService = Executors
.newSingleThreadExecutor(r -> {
Thread thread = new Thread(r);
thread.setName(InetUtilsProperties.PREFIX);
thread.setDaemon(true);
return thread;
});
}@Override
public void close() {
executorService.shutdown();
}
//獲取第一個非迴環主機信息
public HostInfo findFirstNonLoopbackHostInfo() {
InetAddress address = findFirstNonLoopbackAddress();
if (address != null) {
return convertAddress(address);
}
HostInfo hostInfo = new HostInfo();
hostInfo.setHostname(this.properties.getDefaultHostname());
hostInfo.setIpAddress(this.properties.getDefaultIpAddress());
return hostInfo;
}
//獲取第一個非迴環地址
public InetAddress findFirstNonLoopbackAddress() {
InetAddress result = null;
try {
int lowest = Integer.MAX_VALUE;
for (Enumeration<NetworkInterface> nics = NetworkInterface
.getNetworkInterfaces(); nics.hasMoreElements();) {
NetworkInterface ifc = nics.nextElement();
if (ifc.isUp()) {
log.trace("Testing interface: " + ifc.getDisplayName());
if (ifc.getIndex() < lowest || result == null) {
lowest = ifc.getIndex();
}
else if (result != null) {
continue;
}// @formatter:off
if (!ignoreInterface(ifc.getDisplayName())) {
for (Enumeration<InetAddress> addrs = ifc
.getInetAddresses(); addrs.hasMoreElements();) {
InetAddress address = addrs.nextElement();
if (address instanceof Inet4Address
&& !address.isLoopbackAddress()
&& isPreferredAddress(address)) {
log.trace("Found non-loopback interface: "
+ ifc.getDisplayName());
result = address;
}
}
}
// @formatter:on
}
}
}
catch (IOException ex) {
log.error("Cannot get first non-loopback address", ex);
}if (result != null) {
return result;
}try {
return InetAddress.getLocalHost();
}
catch (UnknownHostException e) {
log.warn("Unable to retrieve localhost");
}return null;
}
//測試網絡地址是否爲引用地址
/** for testing */ boolean isPreferredAddress(InetAddress address) {if (this.properties.isUseOnlySiteLocalInterfaces()) {
final boolean siteLocalAddress = address.isSiteLocalAddress();
if (!siteLocalAddress) {
log.trace("Ignoring address: " + address.getHostAddress());
}
return siteLocalAddress;
}
final List<String> preferredNetworks = this.properties.getPreferredNetworks();
if (preferredNetworks.isEmpty()) {
return true;
}
for (String regex : preferredNetworks) {
final String hostAddress = address.getHostAddress();
if (hostAddress.matches(regex) || hostAddress.startsWith(regex)) {
return true;
}
}
log.trace("Ignoring address: " + address.getHostAddress());
return false;
}
//測試接口是否爲忽略接口
/** for testing */ boolean ignoreInterface(String interfaceName) {
for (String regex : this.properties.getIgnoredInterfaces()) {
if (interfaceName.matches(regex)) {
log.trace("Ignoring interface: " + interfaceName);
return true;
}
}
return false;
}
//根據網絡地址轉譯成主機ip地址和主機名
public HostInfo convertAddress(final InetAddress address) {
HostInfo hostInfo = new HostInfo();
Future<String> result = executorService.submit(address::getHostName);String hostname;
try {
hostname = result.get(this.properties.getTimeoutSeconds(), TimeUnit.SECONDS);
}
catch (Exception e) {
log.info("Cannot determine local hostname");
hostname = "localhost";
}
hostInfo.setHostname(hostname);
hostInfo.setIpAddress(address.getHostAddress());
return hostInfo;
}public static class HostInfo {
public boolean override;
private String ipAddress;
private String hostname;public HostInfo(String hostname) {
this.hostname = hostname;
}public HostInfo() {
}public int getIpAddressAsInt() {
InetAddress inetAddress = null;
String host = this.ipAddress;
if (host == null) {
host = this.hostname;
}
try {
inetAddress = InetAddress.getByName(host);
}
catch (final UnknownHostException e) {
throw new IllegalArgumentException(e);
}
return ByteBuffer.wrap(inetAddress.getAddress()).getInt();
}
/**getter and setter **/}
}
源碼:內部靜態類 HostInfo
public static class HostInfo {
public boolean override;
private String ipAddress;
private String hostname;public HostInfo(String hostname) {
this.hostname = hostname;
}public HostInfo() {
}public int getIpAddressAsInt() {
InetAddress inetAddress = null;
String host = this.ipAddress;
if (host == null) {
host = this.hostname;
}
try {
inetAddress = InetAddress.getByName(host);
}
catch (final UnknownHostException e) {
throw new IllegalArgumentException(e);
}
return ByteBuffer.wrap(inetAddress.getAddress()).getInt();
}
/**getter and setter **/}
源碼:網絡工具類屬性類 InetUtilsProperties
網絡工具類屬性類:defaultHostname = "localhost"和defaultIpAddress = "127.0.0.1";
@ConfigurationProperties(InetUtilsProperties.PREFIX)
public class InetUtilsProperties {
public static final String PREFIX = "spring.cloud.inetutils";/**
* The default hostname. Used in case of errors.
*/
private String defaultHostname = "localhost";/**
* The default ipaddress. Used in case of errors.
*/
private String defaultIpAddress = "127.0.0.1";/**
* Timeout in seconds for calculating hostname.
*/
@Value("${spring.util.timeout.sec:${SPRING_UTIL_TIMEOUT_SEC:1}}")
private int timeoutSeconds = 1;/**
* List of Java regex expressions for network interfaces that will be ignored.
*/
private List<String> ignoredInterfaces = new ArrayList<>();
/**
* Use only interfaces with site local addresses. See {@link InetAddress#isSiteLocalAddress()} for more details.
*/
private boolean useOnlySiteLocalInterfaces = false;
/**
* List of Java regex expressions for network addresses that will be preferred.
*/
private List<String> preferredNetworks = new ArrayList<>();
/**getter and setter **/
}
源碼:網卡地址接口 NetworkInterface
NetworkInterface
表示由名稱和分配給此接口的IP地址列表組成的網絡接口。
它用於標識加入多播組的本地接口。接口通常由名稱(如“le0”)表示。public final class NetworkInterface {//理解爲網卡地址接口
private String name;
private String displayName;
private int index;//0~~255
private InetAddress addrs[];
private InterfaceAddress bindings[];
private NetworkInterface childs[];
private NetworkInterface parent = null;
private boolean virtual = false;
private static final NetworkInterface defaultInterface;
private static final int defaultIndex; /* index of defaultInterface */
//省略...
}