EurekaInstanceConfigBean源码深入解析 eureka实例配置过程分析

目录

源码及解析图

图中已有简要解析Eureka Instance 配置流程:

源码: 解析及方法调用使用的的类和方法

① EurekaInstanceConfigBean的源码:

源码:初始化创建EurekaInstanceConfigBean

源码:HostInfo类 findFirstNonLoopbackHostInfo()方法

 源码:网络工具类InetUtils

源码:内部静态类 HostInfo

源码:网络工具类属性类 InetUtilsProperties

源码:网卡地址接口 NetworkInterface


 


源码及解析图

①实例配置过程

②创建实例过程

图中已有简要解析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 */
    //省略...
}

发布了20 篇原创文章 · 获赞 24 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/u010565545/article/details/99501496