node在初始化时,会创建PluginsService对象。
this.pluginsService = new PluginsService(tmpSettings, initialEnvironment.configFile(), initialEnvironment.modulesFile(),
initialEnvironment.pluginsFile(), classpathPlugins);
参数包含modules目录,plugins目录。PluginsService构造函数中完成对modules目录、plugins目录下插件的加载 。
- 扫描插件文件下的plugin-descriptor.properties文件,加载生成PluginInfo。plugin-descriptor.properties文件有一些必须的属性,如name,?description, version, elasticsearch.version,java.version,还包含非必需属性,如extend.plugins,has.native.controller,type, classname, java.opts,licensed,?不能包含其它属性。
- 扫描插件文件夹下的jar文件 ,加载生成url
- 利用反射机制生成ClassLoader,加载Class,?初始化Plugin
生成PluginInfo
public static PluginInfo readFromProperties(final Path path) throws IOException {
final Path descriptor = path.resolve(ES_PLUGIN_PROPERTIES);
final Map<String, String> propsMap;
{
final Properties props = new Properties();
try (InputStream stream = Files.newInputStream(descriptor)) {
props.load(stream);
}
propsMap = props.stringPropertyNames().stream().collect(Collectors.toMap(Function.identity(), props::getProperty));
}
final String name = propsMap.remove("name");
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException(
"property [name] is missing in [" + descriptor + "]");
}
final String description = propsMap.remove("description");
if (description == null) {
throw new IllegalArgumentException(
"property [description] is missing for plugin [" + name + "]");
}
final String version = propsMap.remove("version");
if (version == null) {
throw new IllegalArgumentException(
"property [version] is missing for plugin [" + name + "]");
}
final String esVersionString = propsMap.remove("elasticsearch.version");
if (esVersionString == null) {
throw new IllegalArgumentException(
"property [elasticsearch.version] is missing for plugin [" + name + "]");
}
final Version esVersion = Version.fromString(esVersionString);
final String javaVersionString = propsMap.remove("java.version");
if (javaVersionString == null) {
throw new IllegalArgumentException(
"property [java.version] is missing for plugin [" + name + "]");
}
JarHell.checkVersionFormat(javaVersionString);
final String extendedString = propsMap.remove("extended.plugins");
final List<String> extendedPlugins;
if (extendedString == null) {
extendedPlugins = Collections.emptyList();
} else {
extendedPlugins = Arrays.asList(Strings.delimitedListToStringArray(extendedString, ","));
}
final boolean hasNativeController = parseBooleanValue(name, "has.native.controller", propsMap.remove("has.native.controller"));
final PluginType type = getPluginType(name, propsMap.remove("type"));
final String classname = getClassname(name, type, propsMap.remove("classname"));
final String javaOpts = propsMap.remove("java.opts");
if (type != PluginType.BOOTSTRAP && Strings.isNullOrEmpty(javaOpts) == false) {
throw new IllegalArgumentException(
"[java.opts] can only have a value when [type] is set to [bootstrap] for plugin [" + name + "]"
);
}
boolean isLicensed = parseBooleanValue(name, "licensed", propsMap.remove("licensed"));
if (propsMap.isEmpty() == false) {
throw new IllegalArgumentException("Unknown properties for plugin [" + name + "] in plugin descriptor: " + propsMap.keySet());
}
return new PluginInfo(name, description, version, esVersion, javaVersionString,
classname, extendedPlugins, hasNativeController, type, javaOpts, isLicensed);
}
生成Bundle
static Set<Bundle> getModuleBundles(Path modulesDirectory) throws IOException {
return findBundles(modulesDirectory, "module");
}
private static Set<Bundle> findBundles(final Path directory, String type) throws IOException {
final Set<Bundle> bundles = new HashSet<>();
for (final Path plugin : findPluginDirs(directory)) {
final Bundle bundle = readPluginBundle(plugin, type);
if (bundles.add(bundle) == false) {
throw new IllegalStateException("duplicate " + type + ": " + bundle.plugin);
}
if (type.equals("module") && bundle.plugin.getName().startsWith("test-") && Build.CURRENT.isSnapshot() == false) {
throw new IllegalStateException("external test module [" + plugin.getFileName() + "] found in non-snapshot build");
}
}
logger.trace(
() -> "findBundles("
+ type
+ ") returning: "
+ bundles.stream().map(b -> b.plugin.getName()).sorted().collect(Collectors.toList())
);
return bundles;
}
Bundle(PluginInfo plugin, Path dir) throws IOException {
this.plugin = Objects.requireNonNull(plugin);
Set<URL> urls = new LinkedHashSet<>();
// gather urls for jar files
try (DirectoryStream<Path> jarStream = Files.newDirectoryStream(dir, "*.jar")) {
for (Path jar : jarStream) {
// normalize with toRealPath to get symlinks out of our hair
URL url = jar.toRealPath().toUri().toURL();
if (urls.add(url) == false) {
throw new IllegalStateException("duplicate codebase: " + url);
}
}
}
this.urls = Objects.requireNonNull(urls);
}
加载Bundle
private List<Tuple<PluginInfo,Plugin>> loadBundles(Set<Bundle> bundles) {
List<Tuple<PluginInfo, Plugin>> plugins = new ArrayList<>();
Map<String, Plugin> loaded = new HashMap<>();
Map<String, Set<URL>> transitiveUrls = new HashMap<>();
List<Bundle> sortedBundles = sortBundles(bundles);
for (Bundle bundle : sortedBundles) {
if (bundle.plugin.getType() != PluginType.BOOTSTRAP) {
checkBundleJarHell(JarHell.parseClassPath(), bundle, transitiveUrls);
final Plugin plugin = loadBundle(bundle, loaded);
plugins.add(new Tuple<>(bundle.plugin, plugin));
}
}
loadExtensions(plugins);
return Collections.unmodifiableList(plugins);
}
private Plugin loadBundle(Bundle bundle, Map<String, Plugin> loaded) {
String name = bundle.plugin.getName();
verifyCompatibility(bundle.plugin);
// collect loaders of extended plugins
List<ClassLoader> extendedLoaders = new ArrayList<>();
for (String extendedPluginName : bundle.plugin.getExtendedPlugins()) {
Plugin extendedPlugin = loaded.get(extendedPluginName);
assert extendedPlugin != null;
if (ExtensiblePlugin.class.isInstance(extendedPlugin) == false) {
throw new IllegalStateException("Plugin [" + name + "] cannot extend non-extensible plugin [" + extendedPluginName + "]");
}
extendedLoaders.add(extendedPlugin.getClass().getClassLoader());
}
// create a child to load the plugin in this bundle
ClassLoader parentLoader = PluginLoaderIndirection.createLoader(getClass().getClassLoader(), extendedLoaders);
ClassLoader loader = URLClassLoader.newInstance(bundle.urls.toArray(new URL[0]), parentLoader);
// reload SPI with any new services from the plugin
reloadLuceneSPI(loader);
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
// Set context class loader to plugin's class loader so that plugins
// that have dependencies with their own SPI endpoints have a chance to load
// and initialize them appropriately.
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
Thread.currentThread().setContextClassLoader(loader);
return null;
});
Class<? extends Plugin> pluginClass = loadPluginClass(bundle.plugin.getClassname(), loader);
if (loader != pluginClass.getClassLoader()) {
throw new IllegalStateException("Plugin [" + name + "] must reference a class loader local Plugin class ["
+ bundle.plugin.getClassname()
+ "] (class loader [" + pluginClass.getClassLoader() + "])");
}
Plugin plugin = loadPlugin(pluginClass, settings, configPath);
loaded.put(name, plugin);
return plugin;
} finally {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
Thread.currentThread().setContextClassLoader(cl);
return null;
});
}
}
private Plugin loadPlugin(Class<? extends Plugin> pluginClass, Settings settings, Path configPath) {
final Constructor<?>[] constructors = pluginClass.getConstructors();
if (constructors.length == 0) {
throw new IllegalStateException("no public constructor for [" + pluginClass.getName() + "]");
}
if (constructors.length > 1) {
throw new IllegalStateException("no unique public constructor for [" + pluginClass.getName() + "]");
}
final Constructor<?> constructor = constructors[0];
if (constructor.getParameterCount() > 2) {
throw new IllegalStateException(signatureMessage(pluginClass));
}
final Class<?>[] parameterTypes = constructor.getParameterTypes();
try {
if (constructor.getParameterCount() == 2 && parameterTypes[0] == Settings.class && parameterTypes[1] == Path.class) {
return (Plugin)constructor.newInstance(settings, configPath);
} else if (constructor.getParameterCount() == 1 && parameterTypes[0] == Settings.class) {
return (Plugin)constructor.newInstance(settings);
} else if (constructor.getParameterCount() == 0) {
return (Plugin)constructor.newInstance();
} else {
throw new IllegalStateException(signatureMessage(pluginClass));
}
} catch (final ReflectiveOperationException e) {
throw new IllegalStateException("failed to load plugin class [" + pluginClass.getName() + "]", e);
}
}
|