ServletContextListener
在eureka-server這個模塊下,有一個web.xml,我們知道,它是用來給tomcat加載的。
在這個文件裏,加載了一個listener
:
<listener>
<listener-class>com.netflix.eureka.EurekaBootStrap</listener-class>
</listener>
那麼,這個EurekaBootStrap
是什麼呢?
在eureka-core中找到這個類:
打開分析:
/**
* The class that kick starts the eureka server.
*/
它啓動了eureka server。
EurekaBootStrap
實現了ServletContextListener
。
後者是個java擴展類:
Interface for receiving notification events about ServletContext lifecycle changes.
In order to receive these notification events, the implementation class must be either declared in the deployment descriptor of the web application, annotated with WebListener, or registered via one of the addListener methods defined on ServletContext.
Implementations of this interface are invoked at their contextInitialized(javax.servlet.ServletContextEvent) method in the order in which they have been declared, and at their contextDestroyed(javax.servlet.ServletContextEvent) method in reverse order.
它是用來監聽Servlet Context生命週期的變化的。
文檔說,要把ServletContextListener
的實現類放在deployment descriptor of the web application
,這就是我們web.xml
。
這個類一加載,就會調用contextInitialized
方法完成初始化。
/**
* Initializes Eureka, including syncing up with other Eureka peers and publishing the registry.
*
* @see
* javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent)
*/
@Override
public void contextInitialized(ServletContextEvent event) {
try {
initEurekaEnvironment();
initEurekaServerContext();
ServletContext sc = event.getServletContext();
sc.setAttribute(EurekaServerContext.class.getName(), serverContext);
} catch (Throwable e) {
logger.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}
eureka server啓動要幹那幾件事情呢?
初始化eureka,與其他eureka peers同步,然後發佈註冊表。
核心邏輯在這兩行代碼:
initEurekaEnvironment();
initEurekaServerContext();
一個是初始化eureka環境,一個是初始化eureka server的上下文。
initEurekaEnvironment()
/**
* Users can override to initialize the environment themselves.
*/
protected void initEurekaEnvironment() throws Exception {
logger.info("Setting the eureka configuration..");
String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
if (dataCenter == null) {
logger.info("Eureka data center value eureka.datacenter is not set, defaulting to default");
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
} else {
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
}
String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT);
if (environment == null) {
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
logger.info("Eureka environment value eureka.environment is not set, defaulting to test");
}
}
它要在這裏設置兩個配置:ARCHAIUS_DEPLOYMENT_DATACENTER
和ARCHAIUS_DEPLOYMENT_ENVIRONMENT
,部署數據中心和部署環境。
eureka.datacenter
默認是default
,你也可以將其設置爲cloud
,如此eureka便知道自己會部署到AWS上:
Eureka can be run in both AWS and non-AWS environments.
If you are running in the cloud environment, you will need to pass in the java commandline property -Deureka.datacenter=cloud so that the Eureka Client/Server knows to initialize the information specific to AWS cloud.
eureka.environment
默認是test
,也可以設置成prod
。
這些都是小事,關鍵是這行代碼:
ConfigurationManager.getConfigInstance()
/**
* Get the current system wide configuration. If there has not been set, it will return a default
* {@link ConcurrentCompositeConfiguration} which contains a SystemConfiguration from Apache Commons
* Configuration and a {@link DynamicURLConfiguration}.
*/
public static AbstractConfiguration getConfigInstance() {
if (instance == null) {
synchronized (ConfigurationManager.class) {
if (instance == null) {
instance = getConfigInstance(Boolean.getBoolean(DynamicPropertyFactory.DISABLE_DEFAULT_CONFIG));
}
}
}
return instance;
}
private static AbstractConfiguration getConfigInstance(boolean defaultConfigDisabled) {
if (instance == null && !defaultConfigDisabled) {
instance = createDefaultConfigInstance();
registerConfigBean();
}
return instance;
}
private static AbstractConfiguration createDefaultConfigInstance() {
ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
try {
DynamicURLConfiguration defaultURLConfig = new DynamicURLConfiguration();
config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);
} catch (Throwable e) {
logger.warn("Failed to create default dynamic configuration", e);
}
if (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {
SystemConfiguration sysConfig = new SystemConfiguration();
config.addConfiguration(sysConfig, SYS_CONFIG_NAME);
}
if (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {
EnvironmentConfiguration envConfig = new EnvironmentConfiguration();
config.addConfiguration(envConfig, ENV_CONFIG_NAME);
}
ConcurrentCompositeConfiguration appOverrideConfig = new ConcurrentCompositeConfiguration();
config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);
config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));
return config;
}
這裏還用到了經典的double-check單例模式。
我們最終想知道的是:默認的配置是如何生成的?
首先,我們瞅一下所有configuration的關係:
所以說,archaius是對apache Configuration
的擴展。
這裏它將所有配置加到一個組合配置ConcurrentCompositeConfiguration
中,其中:
- URL_CONFIG_NAME:通過url拉取的配置
- SYS_CONFIG_NAME: 系統屬性配置,通過
System.getProperties()
獲取的配置 - ENV_CONFIG_NAME: 環境配置,通過
System.getenv()
獲取的配置 - containerConfiguration: 容器配置,
ConfigurationManager.getConfigInstance().setProperty(key,value)
設置的配置 - APPLICATION_PROPERTIES: 調用
configurationManager.loadAppOverrideProperties
設置的配置
最終返回出去的是一個AbstractConfiguration
對象。
而AbstractConfiguration
對象受到ConfigurationManager
管理。
ConfigurationManager
是eureka的配置管理中心。
一句話,initEurekaEnvironment()
的作用是加載所有的配置。
initEurekaServerContext()
/**
* init hook for server context. Override for custom logic.
*/
protected void initEurekaServerContext() throws Exception {
EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();
// For backward compatibility
JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
logger.info("Initializing the eureka client...");
logger.info(eurekaServerConfig.getJsonCodecName());
ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig);
ApplicationInfoManager applicationInfoManager = null;
if (eurekaClient == null) {
EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext())
? new CloudInstanceConfig()
: new MyDataCenterInstanceConfig();
applicationInfoManager = new ApplicationInfoManager(
instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
} else {
applicationInfoManager = eurekaClient.getApplicationInfoManager();
}
PeerAwareInstanceRegistry registry;
if (isAws(applicationInfoManager.getInfo())) {
registry = new AwsInstanceRegistry(
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
eurekaClient
);
awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager);
awsBinder.start();
} else {
registry = new PeerAwareInstanceRegistryImpl(
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
eurekaClient
);
}
PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes(
registry,
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
applicationInfoManager
);
serverContext = new DefaultEurekaServerContext(
eurekaServerConfig,
serverCodecs,
registry,
peerEurekaNodes,
applicationInfoManager
);
EurekaServerContextHolder.initialize(serverContext);
serverContext.initialize();
logger.info("Initialized server context");
// Copy registry from neighboring eureka node
int registryCount = registry.syncUp();
registry.openForTraffic(applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}
這個方法的信息量巨大,這篇文章我們只講第一行代碼:
EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();
這個類是用來管理eureka的配置的。
它的構造器裏調用了init()
方法:
private void init() {
String env = ConfigurationManager.getConfigInstance().getString(
EUREKA_ENVIRONMENT, TEST);
ConfigurationManager.getConfigInstance().setProperty(
ARCHAIUS_DEPLOYMENT_ENVIRONMENT, env);
String eurekaPropsFile = EUREKA_PROPS_FILE.get();
try {
// ConfigurationManager
// .loadPropertiesFromResources(eurekaPropsFile);
ConfigurationManager
.loadCascadedPropertiesFromResources(eurekaPropsFile);
} catch (IOException e) {
logger.warn(
"Cannot find the properties specified : {}. This may be okay if there are other environment "
+ "specific properties or the configuration is installed with a different mechanism.",
eurekaPropsFile);
}
}
這個方法用來加載server的配置文件。
爲了瞭解細節,我們新建一個eureka-server.properties
:
並在裏面寫一個屬性:
eureka.peerEurekaNodesUpdateIntervalMs=200
並在DefaultEurekaServerConfig
中寫一個main方法:
debug一下。
進到loadCascadePropertiesFromResources
中:
然後到getPropertiesFromFile
:
一路跟下去,最後它是用Properties
來讀的:
解析properties文件過後,將裏面的值放進ConcurrentMapConfiguration
的ConcurrentHashMap中
:
然後通過
((AggregatedConfiguration) instance).addConfiguration(config, configName);
將其加入一個configList
中:
如此eureka server的配置就加載好了。