【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
Configuration的获取以及初始化
在上一篇文章中介绍了LoggerContext,这个类一个主要的作用就是根据配置文件将配置文件的结构转化为Java对象,即Appender,Logger等对象,其中配置主要是由Configuration类进行管理。这是一个基类,有几个重要的子类,每个子类对应一种配置文件。分别为:
JSONConfiguration
PropertiesConfiguration
YAMLConfiguration
XMLConfiguration
每一种Configuration由其对应的ConfigurationFactory进行管理,ConfigurationFactory负责管理每种类型Configuration配置所包含的文件名后缀,以及负责生成对应的Configuration实例。因此在LoggerContext的reconfigure方法中使用如下的调用来获取Configuration。
ConfigurationFactory.getInstance().getConfiguration()
这些初始的ConfigurationFactory类信息均存在META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat文件中,在getInstance方法中通过PluginManager的collectPlugins方法进行初始化,然后经过,合并,排序等操作,统一放到基类ConfigurationFactory的factories列表中。
getInstance方法我们上篇文章已经介绍过,这里不赘述,本文主要看getConguration方法。
public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) {
if (!isActive()) {
return null;
}
if (loader == null) {
return getConfiguration(name, configLocation);
}
if (isClassLoaderUri(configLocation)) {
final String path = extractClassLoaderUriPath(configLocation);
final ConfigurationSource source = getInputFromResource(path, loader);
if (source != null) {
final Configuration configuration = getConfiguration(source);
if (configuration != null) {
return configuration;
}
}
}
return getConfiguration(name, configLocation);
}
由于现在没有classLoader,因此跳到了这个方法,这个方法是ConfigurationFactory的静态内部类Factory类的方法
public Configuration getConfiguration(final String name, final URI configLocation) {
if (configLocation == null) {
final String configLocationStr = this.substitutor.replace(PropertiesUtil.getProperties()
.getStringProperty(CONFIGURATION_FILE_PROPERTY));
if (configLocationStr != null) {
final String[] sources = configLocationStr.split(",");
if (sources.length > 1) {
final List<AbstractConfiguration> configs = new ArrayList<>();
for (final String sourceLocation : sources) {
final Configuration config = getConfiguration(sourceLocation.trim());
if (config != null && config instanceof AbstractConfiguration) {
configs.add((AbstractConfiguration) config);
} else {
LOGGER.error("Failed to created configuration at {}", sourceLocation);
return null;
}
}
return new CompositeConfiguration(configs);
}
return getConfiguration(configLocationStr);
}
for (final ConfigurationFactory factory : getFactories()) {
final String[] types = factory.getSupportedTypes();
if (types != null) {
for (final String type : types) {
if (type.equals("*")) {
final Configuration config = factory.getConfiguration(name, configLocation);
if (config != null) {
return config;
}
}
}
}
}
} else {
// configLocation != null
final String configLocationStr = configLocation.toString();
for (final ConfigurationFactory factory : getFactories()) {
final String[] types = factory.getSupportedTypes();
if (types != null) {
for (final String type : types) {
if (type.equals("*") || configLocationStr.endsWith(type)) {
final Configuration config = factory.getConfiguration(name, configLocation);
if (config != null) {
return config;
}
}
}
}
}
}
Configuration config = getConfiguration(true, name);
if (config == null) {
config = getConfiguration(true, null);
if (config == null) {
config = getConfiguration(false, name);
if (config == null) {
config = getConfiguration(false, null);
}
}
}
if (config != null) {
return config;
}
LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
return new DefaultConfiguration();
}
代码很长,实际的功能比较明确,就是根据各个ConfigurationFactory中的文件后缀的顺序匹配对应的文件,然后返回对应的Configuration实例。匹配的顺序如下所示:
1、log4j.configurationFile---ConfigurationFactory
2、log4j2-test.properties---PropertiesConfigurationFactory
3、log4j2-test.yaml/log4j2-test.yml---YAMLConfigurationFactory
4、log4j2-test.json/log4j2-test.jsn---JsonConfigurationFactory
5、log4j2-test.xml---XMLConfigurationFactory
6、log4j2.properties---PropertiesConfigurationFactory
7、log4j2.yaml/log4j2.yml---YAMLConfigurationFactory
8、log4j2.json/log4j2.jsn---JSONConfigurationFactory
9、log4j2.xml---XMLConfigurationFactory
10、console控制台---DefaultConfiguration
本文中以xml文件为例,我们将配置文件放在对应的log4j2.xml,配置文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="info">
<appenders>
<Console name="Console" target="SYSTEM_OUT" >
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %level %logger{36} - %msg%n"/>
</Console>
<File name="File" fileName="logs/test.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %level %logger{36} - %msg%n"/>
</File>
</appenders>
<loggers>
<!--定义一个名为"test"的Appender,类别为Console-->
<logger name="test" additivity="false">
<appender-ref ref="Console"/>
</logger>
<root level="info">
<appender-ref ref="File"/>
</root>
</loggers>
</configuration>
简要分析一下里面的结构,其中有一个根配置,以configuration标签开头,有个status属性为info,随后定义了两个appender,两个appender分别是Console和File其中,ConsoleAppender中有一个target是SYSTEM_OUT,还有一个PatternLayout定义了日志的格式,FileAppender有一个fileName,还包含一个PatternLayout。最后定义了两个logger,一个是rootLogger,定义了一个Appender的引用,引用的是FileAppender,还有一个logger命名为test,additivity属性为false,随后引用了一个Appender,是前面定义的ConsoleAppender。根据这个配置文件的内容,Configuration会自动解析,下面我们来继续看代码。
在上面的配置匹配中,我们会得到一个XMLConfigurationFactory,其中里面包含文件名,文件内容等,随后在setConfiguration中会调用config的start方法,对于这个方法,首先会调用AbstractConfiguration的start方法,代码如下:
public void start() {
// Preserve the prior behavior of initializing during start if not initialized.
if (getState().equals(State.INITIALIZING)) {
initialize();
}
LOGGER.debug("Starting configuration {}", this);
this.setStarting();
if (watchManager.getIntervalSeconds() > 0) {
watchManager.start();
}
if (hasAsyncLoggers()) {
asyncLoggerConfigDisruptor.start();
}
final Set<LoggerConfig> alreadyStarted = new HashSet<>();
for (final LoggerConfig logger : loggerConfigs.values()) {
logger.start();
alreadyStarted.add(logger);
}
for (final Appender appender : appenders.values()) {
appender.start();
}
if (!alreadyStarted.contains(root)) { // LOG4J2-392
root.start(); // LOG4J2-336
}
super.start();
LOGGER.debug("Started configuration {} OK.", this);
}
这里面主要包含下面几个关键步骤:
1、初始化
2、设置生命周期状态
3、启动所有的logger
4、启动所有的appender
在初始化过程中就会将配置文件中所有的元素转化为对应的Logger或Appender对象,然后启动他们,我们首先进入初始化方法
public void initialize() {
LOGGER.debug("Initializing configuration {}", this);
subst.setConfiguration(this);
scriptManager = new ScriptManager(watchManager);
pluginManager.collectPlugins(pluginPackages);
final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);
levelPlugins.collectPlugins(pluginPackages);
final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins();
if (plugins != null) {
for (final PluginType<?> type : plugins.values()) {
try {
// Cause the class to be initialized if it isn't already.
Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader());
} catch (final Exception e) {
LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass()
.getSimpleName(), e);
}
}
}
setup();
setupAdvertisement();
doConfigure();
setState(State.INITIALIZED);
LOGGER.debug("Configuration {} initialized", this);
}
这个方法首先实现寻找category为level的插件,默认并没有插件,所以进入setup方法,setup中的核心是根据传入的xml dom树解析配置文件,解析成的对象为Node对象,其中解析的方法为constructHierarchy(rootNode, rootElement);具体方法如下:
private void constructHierarchy(final Node node, final Element element) {
processAttributes(node, element);
final StringBuilder buffer = new StringBuilder();
final NodeList list = element.getChildNodes();
final List<Node> children = node.getChildren();
for (int i = 0; i < list.getLength(); i++) {
final org.w3c.dom.Node w3cNode = list.item(i);
if (w3cNode instanceof Element) {
final Element child = (Element) w3cNode;
final String name = getType(child);
final PluginType<?> type = pluginManager.getPluginType(name);
final Node childNode = new Node(node, name, type);
constructHierarchy(childNode, child);
if (type == null) {
final String value = childNode.getValue();
if (!childNode.hasChildren() && value != null) {
node.getAttributes().put(name, value);
} else {
status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
}
} else {
children.add(childNode);
}
} else if (w3cNode instanceof Text) {
final Text data = (Text) w3cNode;
buffer.append(data.getData());
}
}
final String text = buffer.toString().trim();
if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
node.setValue(text);
}
}
在介绍这个解析方法之前,我们首先来看Node类的定义
public class Node {
public static final String CATEGORY = "Core";
private final Node parent;
private final String name;
private String value;
private final PluginType<?> type;
private final Map<String, String> attributes = new HashMap<>();
private final List<Node> children = new ArrayList<>();
private Object object;
}
从Node的定义来看可以看出这是一个典型的属性结构,每个Node都有一个parent节点,有一个children的节点列表,通过getParent和getChildren可以方便的操作整颗Node树
再来说这个方法,可以很清晰的看出,这是一个深度优先遍历整个xml dom并构建Node树的过程,主要是设置attribute,type,children,parent等一系列信息,其中type是后续要载入的java对象的全限定符。经过该方法,我们上面的配置文件被解析成了如下的一棵Node树,如下图所示。
完成了Node树的构造,就需要根据这颗树来构造对应的Appender和Logger,这个方法由doConfigure方法完成,具体的定义如下:
protected void doConfigure() {
preConfigure(rootNode);
configurationScheduler.start();
if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) {
final Node first = rootNode.getChildren().get(0);
createConfiguration(first, null);
if (first.getObject() != null) {
subst.setVariableResolver((StrLookup) first.getObject());
}
} else {
final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES);
final StrLookup lookup = map == null ? null : new MapLookup(map);
subst.setVariableResolver(new Interpolator(lookup, pluginPackages));
}
boolean setLoggers = false;
boolean setRoot = false;
for (final Node child : rootNode.getChildren()) {
if (child.getName().equalsIgnoreCase("Properties")) {
if (tempLookup == subst.getVariableResolver()) {
LOGGER.error("Properties declaration must be the first element in the configuration");
}
continue;
}
createConfiguration(child, null);
if (child.getObject() == null) {
continue;
}
if (child.getName().equalsIgnoreCase("Scripts")) {
for (final AbstractScript script : child.getObject(AbstractScript[].class)) {
if (script instanceof ScriptRef) {
LOGGER.error("Script reference to {} not added. Scripts definition cannot contain script references",
script.getName());
} else {
scriptManager.addScript(script);
}
}
} else if (child.getName().equalsIgnoreCase("Appenders")) {
appenders = child.getObject();
} else if (child.isInstanceOf(Filter.class)) {
addFilter(child.getObject(Filter.class));
} else if (child.getName().equalsIgnoreCase("Loggers")) {
final Loggers l = child.getObject();
loggerConfigs = l.getMap();
setLoggers = true;
if (l.getRoot() != null) {
root = l.getRoot();
setRoot = true;
}
} else if (child.getName().equalsIgnoreCase("CustomLevels")) {
customLevels = child.getObject(CustomLevels.class).getCustomLevels();
} else if (child.isInstanceOf(CustomLevelConfig.class)) {
final List<CustomLevelConfig> copy = new ArrayList<>(customLevels);
copy.add(child.getObject(CustomLevelConfig.class));
customLevels = copy;
} else {
final List<String> expected = Arrays.asList("\"Appenders\"", "\"Loggers\"", "\"Properties\"",
"\"Scripts\"", "\"CustomLevels\"");
LOGGER.error("Unknown object \"{}\" of type {} is ignored: try nesting it inside one of: {}.",
child.getName(), child.getObject().getClass().getName(), expected);
}
}
if (!setLoggers) {
LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?");
setToDefault();
return;
} else if (!setRoot) {
LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender");
setToDefault();
// return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers
}
for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) {
final LoggerConfig loggerConfig = entry.getValue();
for (final AppenderRef ref : loggerConfig.getAppenderRefs()) {
final Appender app = appenders.get(ref.getRef());
if (app != null) {
loggerConfig.addAppender(app, ref.getLevel(), ref.getFilter());
} else {
LOGGER.error("Unable to locate appender \"{}\" for logger config \"{}\"", ref.getRef(),
loggerConfig);
}
}
}
setParents();
}
该方法的主要工作就是根据上面的Node依赖树,自动为Node的object字段来赋值,同时,将所有的LogConfig构建为一个列表,可以按名称获取相应的LogConfig,在LogConfig中维护了每个logger对应的appender,layout等实例的对应关系,并同时启动了所有的logger和appender。对于每个logger,都会维护一个自己的LogConfig,但进行类一定的封装,变为了PrivateConfig类的实例。下一篇文章中,我将会介绍如何根据name创建对应的logger,即getLogger方法的大致流程。
来源:oschina
链接:https://my.oschina.net/xiaominmin/blog/3154947