1. 程式人生 > >log4j2如何根據配置的配置檔案選取配置檔案處理類的(ConfigurationFactory原始碼分析)

log4j2如何根據配置的配置檔案選取配置檔案處理類的(ConfigurationFactory原始碼分析)

一、環境

log4j-core-2.2.jar

二、背景知識

我們知道log4j2(2.2版本)支援的配置檔案型別有以下幾種:

XML、JSON、YAML

當然配置的實現由多種方式:官方文件中有介紹

Configuration of Log4j 2 can be accomplished in 1 of 4 ways:
1. Through a configuration file written in XML, JSON, YAML, or properties format.
2. Programmatically, by creating a ConfigurationFactory and Configuration implementation.
3. Programmatically, by calling the APIs exposed in the Configuration interface to add components
to the default configuration.
4. Programmatically, by calling methods on the internal Logger class.


Log4j has the ability to automatically configure itself during initialization. When Log4j starts it
will locate all the ConfigurationFactory plugins and arrange them in weighted order from highest to
lowest. As delivered, Log4j contains four ConfigurationFactory implementations: one for JSON, one
for YAML, one for properties, and one for XML.


在初始化階段Log4j2有自動完成配置的能力,其中上述提到的四種ConfigurationFactory,for properties在本版本並不支援(但是從2.4版本開始支援,並且最新的2.7版本也支援)。上一篇也說到如果沒有配置配置檔案,則採用系統預設的配置DefaultConfiguration。

Log4j will provide a default configuration if it cannot locate a configuration file. The default
configuration, provided in the DefaultConfiguration class, will set up:
• AConsoleAppenderattached to the root logger.
• APatternLayoutset to the pattern "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg
%n" attached to the ConsoleAppender
Note that by default Log4j assigns the root logger toLevel.ERROR.


三、原始碼分析

LoggerContext.java
<pre class="java" name="code">/**
     * Reconfigure the context.
     */
    public synchronized void reconfigure() {
        final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
        LOGGER.debug("Reconfiguration started for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
            configLocation, this, cl);
        final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(name, configLocation, cl);
        setConfiguration(instance);
        /*
         * instance.start(); Configuration old = setConfiguration(instance);
         * updateLoggers(); if (old != null) { old.stop(); }
         */

        LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({}) with optional ClassLoader: {}", name,
            configLocation, this, cl);
    }

</pre><span style="color:#000000;font-family:; font-size: 11pt; font-style: normal; font-variant: normal;"></span><pre class="java" code_snippet_id="1937793" snippet_file_name="blog_20161019_4_7185684" name="code">

1. 首先看getInstance()

**
     * Returns the ConfigurationFactory.
     * @return the ConfigurationFactory.
     */
    public static ConfigurationFactory getInstance() {
        // volatile works in Java 1.6+, so double-checked locking also works properly
        //noinspection DoubleCheckedLocking
        if (factories == null) {
            LOCK.lock();
            try {
                if (factories == null) {
                    final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>();
                    final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
                    if (factoryClass != null) {
                        addFactory(list, factoryClass);
                    }
                    final PluginManager manager = new PluginManager(CATEGORY);
                    manager.collectPlugins();   //獲取所有以@Plugin註解的類
                    final Map<String, PluginType<?>> plugins = manager.getPlugins();
                    final List<Class<? extends ConfigurationFactory>> ordered =
                        new ArrayList<Class<? extends ConfigurationFactory>>(plugins.size());
                    for (final PluginType<?> type : plugins.values()) {
                        try {//將繼承ConfigurationFactory的@Plugin註解的類新增
                            ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));
                        } catch (final Exception ex) {
                            LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);
                        }
                    }
                    Collections.sort(ordered, OrderComparator.getInstance());  //按照order排序
                    for (final Class<? extends ConfigurationFactory> clazz : ordered) {
                        addFactory(list, clazz);
                    }
                    // see above comments about double-checked locking
                    //noinspection NonThreadSafeLazyInitialization
                    factories = Collections.unmodifiableList(list);   //factories無法修改的list,final修飾
                }
            } finally {
                LOCK.unlock();
            }
        }

        LOGGER.debug("Using configurationFactory {}", configFactory);
        return configFactory;
    }

該方法有兩個作用,獲取所有的ConfigurationFactory新增到factories中(包括:YamlConfigurationFactory、JsonConfigurationFactory、XmlConfigurationFactory),以及返回configFactory為Factory

private static ConfigurationFactory configFactory = new Factory();

2.看獲取ConfigurationFactory的過程

log4j2是支援plugin方式的,以@Plugin方式定義

**
     * Locates all the plugins including search of specific packages. Warns about name collisions.
     *
     * @param packages the list of packages to scan for plugins
     * @since 2.1
     */
    public void collectPlugins(final List<String> packages) {
        final String categoryLowerCase = category.toLowerCase();
        final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<String, PluginType<?>>();

        // First, iterate the Log4j2Plugin.dat files found in the main CLASSPATH
        Map<String, List<PluginType<?>>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader();
        if (builtInPlugins.isEmpty()) {
            // If we didn't find any plugins above, someone must have messed with the log4j-core.jar.
            // Search the standard package in the hopes we can find our core plugins.
            builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES);
        }
        mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase));

        // Next, iterate any Log4j2Plugin.dat files from OSGi Bundles
        for (final Map<String, List<PluginType<?>>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) {
            mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase));
        }

        // Next iterate any packages passed to the static addPackage method.
        for (final String pkg : PACKAGES) {
            mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
        }
        // Finally iterate any packages provided in the configuration (note these can be changed at runtime).
        if (packages != null) {
            for (final String pkg : packages) {
                mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
            }
        }

        LOGGER.debug("PluginManager '{}' found {} plugins", category, newPlugins.size());

        plugins = newPlugins;
    }

從上面的英文註釋可知其獲取過程,看一下2.7中的官方文件:

In Log4j 2 a plugin is declared by adding a@Pluginannotation to the class declaration. During
initialization theConfigurationwill invoke the PluginManager to load the built-in Log4j plugins as
well as any custom plugins. ThePluginManagerlocates plugins by looking in five places:
1. Serialized plugin listing files on the classpath. These files are generated automatically during the
build (more details below).
2. (OSGi only) Serialized plugin listing files in each active OSGi bundle. ABundleListeneris
added on activation to continue checking new bundles afterlog4j-corehas started.
3. A comma-separated list of packages specified by thelog4j.plugin.packagessystem
property.
4. Packages passed to the staticPluginManager.addPackagesmethod (before Log4j
configuration occurs).
5. Thepackagesdeclared in your log4j2 configuration file.


2.2版本中是從Log4j2Plugin.dat檔案中獲取的,不看其過程,重點看下loadFromPackage的過程

 builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES);
private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";


3. loadFromPackage

 public Map<String, List<PluginType<?>>> loadFromPackage(final String pkg) {
        if (Strings.isBlank(pkg)) {
            // happens when splitting an empty string
            return Collections.emptyMap();
        }
        Map<String, List<PluginType<?>>> existing = pluginsByCategoryByPackage.get(pkg);
        if (existing != null) {
            // already loaded this package
            return existing;
        }

        final long startTime = System.nanoTime();
        final ResolverUtil resolver = new ResolverUtil();
        final ClassLoader classLoader = Loader.getClassLoader();
        if (classLoader != null) {
            resolver.setClassLoader(classLoader);
        }
        resolver.findInPackage(new PluginTest(), pkg);

        final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<String, List<PluginType<?>>>();
        for (final Class<?> clazz : resolver.getClasses()) {
            final Plugin plugin = clazz.getAnnotation(Plugin.class);
            final String categoryLowerCase = plugin.category().toLowerCase();
            List<PluginType<?>> list = newPluginsByCategory.get(categoryLowerCase);
            if (list == null) {
                newPluginsByCategory.put(categoryLowerCase, list = new ArrayList<PluginType<?>>());
            }
            final PluginEntry mainEntry = new PluginEntry();
            final String mainElementName = plugin.elementType().equals(
                Plugin.EMPTY) ? plugin.name() : plugin.elementType();
            mainEntry.setKey(plugin.name().toLowerCase());
            mainEntry.setName(plugin.name());
            mainEntry.setCategory(plugin.category());
            mainEntry.setClassName(clazz.getName());
            mainEntry.setPrintable(plugin.printObject());
            mainEntry.setDefer(plugin.deferChildren());
            @SuppressWarnings({"unchecked","rawtypes"})
            final PluginType<?> mainType = new PluginType(mainEntry, clazz, mainElementName);
            list.add(mainType);
            final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class);
            if (pluginAliases != null) {
                for (final String alias : pluginAliases.value()) {
                    final PluginEntry aliasEntry = new PluginEntry();
                    final String aliasElementName = plugin.elementType().equals(
                        Plugin.EMPTY) ? alias.trim() : plugin.elementType();
                    aliasEntry.setKey(alias.trim().toLowerCase());
                    aliasEntry.setName(plugin.name());
                    aliasEntry.setCategory(plugin.category());
                    aliasEntry.setClassName(clazz.getName());
                    aliasEntry.setPrintable(plugin.printObject());
                    aliasEntry.setDefer(plugin.deferChildren());
                    @SuppressWarnings({"unchecked","rawtypes"})
                    final PluginType<?> aliasType = new PluginType(aliasEntry, clazz, aliasElementName);
                    list.add(aliasType);
                }
            }
        }

        final long endTime = System.nanoTime();
        final DecimalFormat numFormat = new DecimalFormat("#0.000000");
        final double seconds = (endTime - startTime) * 1e-9;
        LOGGER.debug("Took {} seconds to load {} plugins from package {}",
            numFormat.format(seconds), resolver.getClasses().size(), pkg);

        // Note multiple threads could be calling this method concurrently. Both will do the work,
        // but only one will be allowed to store the result in the outer map.
        // Return the inner map produced by whichever thread won the race, so all callers will get the same result.
        existing = pluginsByCategoryByPackage.putIfAbsent(pkg, newPluginsByCategory);
        if (existing != null) {
            return existing;
        }
        return newPluginsByCategory;
    }



看一下從package中尋找的過程

public void findInPackage(final Test test, String packageName) {
        packageName = packageName.replace('.', '/');
        final ClassLoader loader = getClassLoader();
        Enumeration<URL> urls;

        try {
            urls = loader.getResources(packageName);  //獲取包含該packageName的資源
        } catch (final IOException ioe) {
            LOGGER.warn("Could not read package: " + packageName, ioe);
            return;
        }

        while (urls.hasMoreElements()) {
            try {
                final URL url = urls.nextElement();
                final String urlPath = extractPath(url);

                LOGGER.info("Scanning for classes in [" + urlPath + "] matching criteria: " + test);
                // Check for a jar in a war in JBoss
                if (VFSZIP.equals(url.getProtocol())) {
                    final String path = urlPath.substring(0, urlPath.length() - packageName.length() - 2);
                    final URL newURL = new URL(url.getProtocol(), url.getHost(), path);
                    @SuppressWarnings("resource")
                    final JarInputStream stream = new JarInputStream(newURL.openStream());
                    try {
                        loadImplementationsInJar(test, packageName, path, stream);
                    } finally {
                        close(stream, newURL);
                    }
                } else if (BUNDLE_RESOURCE.equals(url.getProtocol())) {
                    loadImplementationsInBundle(test, packageName);
                } else {
                    final File file = new File(urlPath);
                    if (file.isDirectory()) {
                        loadImplementationsInDirectory(test, packageName, file);
                    } else {
                        loadImplementationsInJar(test, packageName, file);   //這裡獲取的是jar包
                    }
                }
            } catch (final IOException ioe) {
                LOGGER.warn("could not read entries", ioe);
            }
        }
    }


loadImplementationsInJar

private void loadImplementationsInJar(final Test test, final String parent, final File jarFile) {
        @SuppressWarnings("resource")
        JarInputStream jarStream = null;
        try {
            jarStream = new JarInputStream(new FileInputStream(jarFile));
            loadImplementationsInJar(test, parent, jarFile.getPath(), jarStream);
        } catch (final FileNotFoundException ex) {
            LOGGER.error("Could not search jar file '" + jarFile + "' for classes matching criteria: " + test
                    + " file not found", ex);
        } catch (final IOException ioe) {
            LOGGER.error("Could not search jar file '" + jarFile + "' for classes matching criteria: " + test
                    + " due to an IOException", ioe);
        } finally {
            close(jarStream, jarFile);
        }
    }

獲取的方式也很簡單,一層層的找,將以"org/apache/logging/log4j/core"開頭的,以.class結尾的加入

private void loadImplementationsInJar(final Test test, final String parent, final String path,
                                          final JarInputStream stream) {

        try {
            JarEntry entry;

            while ((entry = stream.getNextJarEntry()) != null) {
                final String name = entry.getName();
                if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) {
                    addIfMatching(test, name);  //
                }
            }
        } catch (final IOException ioe) {
            LOGGER.error("Could not search jar file '" + path + "' for classes matching criteria: " +
                test + " due to an IOException", ioe);
        }
    }

 protected void addIfMatching(final Test test, final String fqn) {
        try {
            final ClassLoader loader = getClassLoader();
            if (test.doesMatchClass()) {
                final String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Checking to see if class " + externalName + " matches criteria [" + test + ']');
                }

                final Class<?> type = loader.loadClass(externalName);   //根據名稱獲取類
                if (test.matches(type)) {   /只將@Plugin註解的類加入
                    classMatches.add(type);
                }
            }
            if (test.doesMatchResource()) {
                URL url = loader.getResource(fqn);
                if (url == null) {
                    url = loader.getResource(fqn.substring(1));
                }
                if (url != null && test.matches(url.toURI())) {
                    resourceMatches.add(url.toURI());
                }
            }
        } catch (final Throwable t) {
            LOGGER.warn("Could not examine class '" + fqn, t);
        }
    }
public boolean matches(final Class<?> type) {
            return type != null && type.isAnnotationPresent(Plugin.class);
        }


到此為止,factories中只有YamlConfigurationFactory、JsonConfigurationFactory、XmlConfigurationFactory三種ConfigurationFactory

四、如何根據配置檔案選用對應的ConfigurationFactory

正題:傳入的引數為23156357、null、null

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);
    }

呼叫的是Factory.class中的getConfiguration

/**
         * Default Factory Constructor.
         * @param name The configuration name.
         * @param configLocation The configuration location.
         * @return The Configuration.
         */
        @Override
        public Configuration getConfiguration(final String name, final URI configLocation) {

            if (configLocation == null) {
                final String config = this.substitutor.replace(
                    PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));
                if (config != null) {  //沒有配置log4j.configurationFile,config為null
                    ConfigurationSource source = null;
                    try {
                        source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config));
                    } catch (final Exception ex) {
                        // Ignore the error and try as a String.
                        LOGGER.catching(Level.DEBUG, ex);
                    }
                    if (source == null) {
                        final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
                        source = getInputFromString(config, loader);
                    }
                    if (source != null) {
                        for (final ConfigurationFactory factory : factories) {
                            final String[] types = factory.getSupportedTypes();
                            if (types != null) {
                                for (final String type : types) {
                                    if (type.equals("*") || config.endsWith(type)) {
                                        final Configuration c = factory.getConfiguration(source);
                                        if (c != null) {
                                            return c;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } else {
                for (final ConfigurationFactory factory : factories) {
                    final String[] types = factory.getSupportedTypes();
                    if (types != null) {
                        for (final String type : types) {
                            if (type.equals("*") || configLocation.toString().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();
        }

沒有配置log4j.configurationFile

接著看

private Configuration getConfiguration(final boolean isTest, final String name) {
            final boolean named = name != null && name.length() > 0;
            final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
            for (final ConfigurationFactory factory : factories) {
                String configName;
                final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
                final String [] types = factory.getSupportedTypes(); 獲取XmlConfigurationFactory支援型別
                if (types == null) {
                    continue;
                }

                for (final String suffix : types) {
                    if (suffix.equals("*")) {
                        continue;
                    }
                    configName = named ? prefix + name + suffix : prefix + suffix;

                    final ConfigurationSource source = getInputFromResource(configName, loader);
                    if (source != null) {
                        return factory.getConfiguration(source);
                    }
                }
            }
            return null;
        }

上面是根據配置檔案獲取ConfigurationFactory重要的一步。2.7版本官方文件這樣說明的

1. Log4j will inspect the"log4j.configurationFile"system property and, if set, will attempt
to load the configuration using theConfigurationFactorythat matches the file extension.
2. If no system property is set the properties ConfigurationFactory will look forlog4j2-
test.propertiesin the classpath.
3. If no such file is found the YAML ConfigurationFactory will look forlog4j2-test.yamlor
log4j2-test.ymlin the classpath.
4. If no such file is found the JSON ConfigurationFactory will look forlog4j2-test.jsonor
log4j2-test.jsnin the classpath.
5. If no such file is found the XML ConfigurationFactory will look forlog4j2-test.xmlin the
classpath.
6. If a test file cannot be located the properties ConfigurationFactory will look for
log4j2.propertieson the classpath.
7. If a properties file cannot be located the YAML ConfigurationFactory will look for
log4j2.yamlorlog4j2.ymlon the classpath.
8. If a YAML file cannot be located the JSON ConfigurationFactory will look forlog4j2.jsonor
log4j2.jsnon the classpath.
9. If a JSON file cannot be located the XML ConfigurationFactory will try to locatelog4j2.xml
on the classpath.
10.If no configuration file could be located theDefaultConfigurationwill be used. This will
cause logging output to go to the console.


例如,專案中配置的是log4j2.xml,則獲取的ConfigurationFactory為XmlConfigurationFactory

/**
     * File name prefix for standard configurations.
     */
    protected static final String DEFAULT_PREFIX = "log4j2";

 /**
     * Returns the file suffixes for XML files.
     * @return An array of File extensions.
     */
    @Override
    public String[] getSupportedTypes() {
        return SUFFIXES;
    }

/**
     * Valid file extensions for XML files.
     */
    public static final String[] SUFFIXES = new String[] {".xml", "*"};


總結,整體過程是將所有的ConfigurationFactory都獲取到,然後根據配置的配置檔案選用哪個ConfigurationFactory。

五、使用到的設計模式

1.單例設計模式:

ConfigurationFactory.getInstance()

 採用的是類變數的方式實現的,可參考Java開發中的23種設計模式詳解(轉) ,圖解Java單例模式記憶體分配

 private static ConfigurationFactory configFactory = new Factory();

Factory是繼承ConfigurationFactory的內部類,採用靜態變數configFactory引用。


2.工廠模式:

選用ConfigurationFactory及Configuration時採用的是工廠模式,比較複雜,看下面的類圖