前言

log4j2 使用插件机制加载各种组件:appender, logger .etc,本文简要分析 log4j2 插件机制实现

Plugin Annotation(注解)

Plugin 注解提供了一种便捷的方法将一个类声明成 log4j2 的插件,比如

@Plugin(name = "Console", category = "Core", elementType = "appender", printObject = true)
public final class ConsoleAppender extends
        AbstractOutputStreamAppender<OutputStreamManager> {
    ...
}
  • name,name of the plugin

  • category, category to place the plugin under

  • name of the corresponding category of elements this plugin belongs under

Plugin Registry

单例

PluginRegistry 类用来保存插件信息,暴露了一些方法从配置文件中加载(内置)插件,使用了单例设计模式


private static volatile PluginRegistry INSTANCE;

private static final Object INSTANCE_LOCK = new Object();

private PluginRegistry() {
}

public static PluginRegistry getInstance() {
    PluginRegistry result = INSTANCE;
    if (result == null) {
        synchronized(INSTANCE_LOCK) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = new PluginRegistry();
            }
        }
    }
}

线程安全的数据结构

PluginRegistry 使用了一些 Java 多线程编程的最佳实践:

  • 使用 AtomicReference 类的 CAS(compare and set)操作,避免在多线程环境下 插件配置 被多次加载

  • 使用 ConcurrentMapHash 类替代 HashMap 提供线程安全的 map

PluginType 类用来描述插件,比如插件对应的 class

    /**
     * Contains plugins found in Log4j2Plugins.dat cache files in the main CLASSPATH.
     */
    private final AtomicReference<Map<String, List<PluginType<?>>>> pluginsByCategoryRef =
        new AtomicReference<>();

    /**
     * Contains plugins found in Log4j2Plugins.dat cache files in OSGi Bundles.
     */
    private final ConcurrentMap<Long, Map<String, List<PluginType<?>>>> pluginsByCategoryByBundleId =
        new ConcurrentHashMap<>();

    /**
     * Contains plugins found by searching for annotated classes at runtime.
     */
    private final ConcurrentMap<String, Map<String, List<PluginType<?>>>> pluginsByCategoryByPackage =
        new ConcurrentHashMap<>();

log4j2Plugins.dat 是插件描述文件,内部插件描述文件位于:

log4j-core-2.5.jar
    META-INF
        org.apache.logging.log4j.core.config.plugins
            Log4j2Plugins.dat

加载插件

PluginRegistry 中和加载扫描插件相关的方法

  • loadFromMainClassLoader,加载内部插件

  • loadFromBundle,osgi相关

  • loadFromPackage,加载指定 package(包)中的插件

加载内部插件

    public Map<String, List<PluginType<?>>> loadFromMainClassLoader() {
        final Map<String, List<PluginType<?>>> existing = pluginsByCategoryRef.get();
        // 如果 get 方法返回非空,说明已经通过 main class loader 加载过插件配置
        if (existing != null) {
            // already loaded
            return existing;
        }
        // 从配置文件加载 插件配置
        final Map<String, List<PluginType<?>>> newPluginsByCategory =    
            decodeCacheFiles(Loader.getClassLoader());
        // CAS
        if (pluginsByCategoryRef.compareAndSet(null, newPluginsByCategory)) {
            return newPluginsByCategory;
        }
        return pluginsByCategoryRef.get();
    }

加载指定包中插件

public Map<String, List<PluginType<?>>> loadFromPackage(final String pkg) {
    // 参数校验
    if (Strings.isBlank(pkg)) {
        return Collections.emptyMap();
    }

    // 如果 pkg 已经被加载过直接返回
    Map<String, List<PluginType<?>>> existing = pluginByCategoryByPackage.get(pkg);
    if (existing != null) {
        return existing;
    }

    // 加载 pkg

    // 线程安全的 put if absent 操作(类似 CAS)
    existing = pluginsByCategoryByPackage.putIfAbsent(pkg, newPluginsByCategory);
    if (existing != null) {
        return existing;
    }
    return newPluginsByCategory;
}

PluginManager

PluginManager 类用来加载和管理所有的插件,每一个 category(类别)都有一个对应的 PluginManager

public PluginManager(final String category) {
    this.category = category;
}

collectPlugins 方法用于加载插件(描述信息)

public void collectPlugins(final List<String> packages) {
    ...
}

总结


xingpingz
122 声望64 粉丝

博学,审问,慎思,明辨,力行