-
Notifications
You must be signed in to change notification settings - Fork 498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support plugin management and dynamic plugin installation with extensions #984
base: master
Are you sure you want to change the base?
Changes from all commits
a9512c5
7a53f28
0bbd4e3
b8bfd91
f5e356f
94d01ab
ae207e0
4ee8415
58ca02a
345c577
e7d6e2a
84c8548
f81c2be
0e2645b
7b7ea95
c293814
5a35f75
4b08ce4
e4cd09b
792c726
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -38,6 +38,7 @@ | |||||||||||||||||
import com.alipay.sofa.ark.spi.event.biz.BeforeBizStopEvent; | ||||||||||||||||||
import com.alipay.sofa.ark.spi.model.Biz; | ||||||||||||||||||
import com.alipay.sofa.ark.spi.model.BizState; | ||||||||||||||||||
import com.alipay.sofa.ark.spi.model.Plugin; | ||||||||||||||||||
import com.alipay.sofa.ark.spi.service.biz.BizManagerService; | ||||||||||||||||||
import com.alipay.sofa.ark.spi.service.event.EventAdminService; | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -46,6 +47,7 @@ | |||||||||||||||||
import java.net.URL; | ||||||||||||||||||
import java.nio.file.Files; | ||||||||||||||||||
import java.nio.file.Paths; | ||||||||||||||||||
import java.util.ArrayList; | ||||||||||||||||||
import java.util.Date; | ||||||||||||||||||
import java.util.HashSet; | ||||||||||||||||||
import java.util.LinkedHashSet; | ||||||||||||||||||
|
@@ -111,6 +113,8 @@ public class BizModel implements Biz { | |||||||||||||||||
|
||||||||||||||||||
private List<BizStateRecord> bizStateRecords = new CopyOnWriteArrayList<>(); | ||||||||||||||||||
|
||||||||||||||||||
private Set<Plugin> dependentPlugins = new HashSet<>(); | ||||||||||||||||||
|
||||||||||||||||||
public BizModel setBizName(String bizName) { | ||||||||||||||||||
AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz Name must not be empty!"); | ||||||||||||||||||
this.bizName = bizName; | ||||||||||||||||||
|
@@ -229,6 +233,15 @@ private void addStateChangeLog(StateChangeReason reason, String message) { | |||||||||||||||||
bizStateRecords.add(new BizStateRecord(new Date(), bizState, reason, message)); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public Set<Plugin> getDependentPlugins() { | ||||||||||||||||||
return dependentPlugins; | ||||||||||||||||||
} | ||||||||||||||||||
Comment on lines
+236
to
+238
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Return Unmodifiable Set in Returning the mutable Example: public Set<Plugin> getDependentPlugins() {
- return dependentPlugins;
+ return Collections.unmodifiableSet(dependentPlugins);
} Committable suggestion
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
public BizModel setDependentPlugins(Set<Plugin> dependentPlugins) { | ||||||||||||||||||
this.dependentPlugins = dependentPlugins; | ||||||||||||||||||
return this; | ||||||||||||||||||
} | ||||||||||||||||||
Comment on lines
+240
to
+243
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Make a Defensive Copy in Assigning the provided set directly to Example: public BizModel setDependentPlugins(Set<Plugin> dependentPlugins) {
- this.dependentPlugins = dependentPlugins;
+ this.dependentPlugins = new HashSet<>(dependentPlugins);
return this;
} Committable suggestion
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
@Override | ||||||||||||||||||
public String getBizName() { | ||||||||||||||||||
return bizName; | ||||||||||||||||||
|
@@ -617,4 +630,39 @@ private static String markBizTempWorkDirRecycled(File bizTempWorkDir) throws IOE | |||||||||||||||||
|
||||||||||||||||||
return targetPath; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/* export class and classloader relationship cache */ | ||||||||||||||||||
private ConcurrentHashMap<String, Plugin> exportClassAndClassLoaderMap = new ConcurrentHashMap<>(); | ||||||||||||||||||
private ConcurrentHashMap<String, Plugin> exportNodeAndClassLoaderMap = new ConcurrentHashMap<>(); | ||||||||||||||||||
private ConcurrentHashMap<String, Plugin> exportStemAndClassLoaderMap = new ConcurrentHashMap<>(); | ||||||||||||||||||
|
||||||||||||||||||
/* export cache and classloader relationship cache */ | ||||||||||||||||||
private ConcurrentHashMap<String, List<Plugin>> exportResourceAndClassLoaderMap = new ConcurrentHashMap<>(); | ||||||||||||||||||
private ConcurrentHashMap<String, List<Plugin>> exportPrefixStemResourceAndClassLoaderMap = new ConcurrentHashMap<>(); | ||||||||||||||||||
private ConcurrentHashMap<String, List<Plugin>> exportSuffixStemResourceAndClassLoaderMap = new ConcurrentHashMap<>(); | ||||||||||||||||||
|
||||||||||||||||||
public ConcurrentHashMap<String, Plugin> getExportClassAndClassLoaderMap() { | ||||||||||||||||||
return exportClassAndClassLoaderMap; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public ConcurrentHashMap<String, Plugin> getExportNodeAndClassLoaderMap() { | ||||||||||||||||||
return exportNodeAndClassLoaderMap; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public ConcurrentHashMap<String, Plugin> getExportStemAndClassLoaderMap() { | ||||||||||||||||||
return exportStemAndClassLoaderMap; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public ConcurrentHashMap<String, List<Plugin>> getExportResourceAndClassLoaderMap() { | ||||||||||||||||||
return exportResourceAndClassLoaderMap; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public ConcurrentHashMap<String, List<Plugin>> getExportPrefixStemResourceAndClassLoaderMap() { | ||||||||||||||||||
return exportPrefixStemResourceAndClassLoaderMap; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public ConcurrentHashMap<String, List<Plugin>> getExportSuffixStemResourceAndClassLoaderMap() { | ||||||||||||||||||
return exportSuffixStemResourceAndClassLoaderMap; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,11 +31,8 @@ | |
import com.alipay.sofa.ark.spi.archive.Archive; | ||
import com.alipay.sofa.ark.spi.archive.BizArchive; | ||
import com.alipay.sofa.ark.spi.constant.Constants; | ||
import com.alipay.sofa.ark.spi.model.Biz; | ||
import com.alipay.sofa.ark.spi.model.*; | ||
import com.alipay.sofa.ark.spi.model.BizInfo.StateChangeReason; | ||
import com.alipay.sofa.ark.spi.model.BizOperation; | ||
import com.alipay.sofa.ark.spi.model.BizState; | ||
import com.alipay.sofa.ark.spi.model.Plugin; | ||
import com.alipay.sofa.ark.spi.service.biz.BizFactoryService; | ||
import com.alipay.sofa.ark.spi.service.plugin.PluginManagerService; | ||
import com.google.inject.Inject; | ||
|
@@ -48,9 +45,11 @@ | |
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.HashSet; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.jar.Attributes; | ||
import java.util.stream.Stream; | ||
|
||
import static com.alipay.sofa.ark.spi.constant.Constants.ARK_BIZ_NAME; | ||
import static com.alipay.sofa.ark.spi.constant.Constants.ARK_BIZ_VERSION; | ||
|
@@ -80,15 +79,60 @@ public class BizFactoryServiceImpl implements BizFactoryService { | |
|
||
@Override | ||
public Biz createBiz(BizArchive bizArchive) throws IOException { | ||
return createBiz(bizArchive, new BizConfig()); | ||
} | ||
|
||
@Override | ||
public Biz createBiz(BizArchive bizArchive, URL[] extensionUrls) throws IOException { | ||
BizConfig bizConfig = new BizConfig(); | ||
bizConfig.setExtensionUrls(extensionUrls); | ||
return createBiz(bizArchive, bizConfig); | ||
} | ||
|
||
@Override | ||
public Biz createBiz(File file) throws IOException { | ||
BizArchive bizArchive = prepareBizArchive(file); | ||
return createBiz(bizArchive, new BizConfig()); | ||
} | ||
|
||
@Override | ||
public Biz createBiz(File file, URL[] extensionUrls) throws IOException { | ||
yuanyuancin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
BizArchive bizArchive = prepareBizArchive(file); | ||
BizConfig bizConfig = new BizConfig(); | ||
bizConfig.setExtensionUrls(extensionUrls); | ||
return createBiz(bizArchive, bizConfig); | ||
} | ||
|
||
Comment on lines
+85
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider refactoring overloaded The overloaded Consider refactoring to eliminate duplication, possibly by introducing helper methods or utilizing default parameter values. |
||
@Override | ||
public Biz createBiz(BizOperation bizOperation, File file) throws IOException { | ||
BizArchive bizArchive = prepareBizArchive(file); | ||
BizConfig bizConfig = new BizConfig(); | ||
bizConfig.setSpecifiedVersion(bizOperation.getBizVersion()); | ||
return createBiz(bizArchive, bizConfig); | ||
} | ||
|
||
@Override | ||
public Biz createBiz(File file, BizConfig bizConfig) throws IOException { | ||
BizArchive bizArchive = prepareBizArchive(file); | ||
return createBiz(bizArchive, bizConfig); | ||
} | ||
|
||
@Override | ||
public Biz createBiz(BizArchive bizArchive, BizConfig bizConfig) throws IOException { | ||
AssertUtils.isTrue(isArkBiz(bizArchive), "Archive must be a ark biz!"); | ||
BizModel bizModel = new BizModel(); | ||
AssertUtils.isTrue(bizConfig != null, "BizConfig must not be null!"); | ||
|
||
Attributes manifestMainAttributes = bizArchive.getManifest().getMainAttributes(); | ||
String mainClass = manifestMainAttributes.getValue(MAIN_CLASS_ATTRIBUTE); | ||
String startClass = manifestMainAttributes.getValue(START_CLASS_ATTRIBUTE); | ||
BizModel bizModel = new BizModel(); | ||
bizModel | ||
.setBizState(BizState.RESOLVED, StateChangeReason.CREATED) | ||
.setBizName(manifestMainAttributes.getValue(ARK_BIZ_NAME)) | ||
.setBizVersion(manifestMainAttributes.getValue(ARK_BIZ_VERSION)) | ||
.setBizVersion( | ||
!StringUtils.isEmpty(bizConfig.getSpecifiedVersion()) ? bizConfig | ||
yuanyuancin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.getSpecifiedVersion() : manifestMainAttributes.getValue(ARK_BIZ_VERSION)) | ||
.setBizUrl(!(bizArchive instanceof DirectoryBizArchive) ? bizArchive.getUrl() : null) | ||
.setMainClass(!StringUtils.isEmpty(startClass) ? startClass : mainClass) | ||
.setPriority(manifestMainAttributes.getValue(PRIORITY_ATTRIBUTE)) | ||
.setWebContextPath(manifestMainAttributes.getValue(WEB_CONTEXT_PATH)) | ||
|
@@ -99,22 +143,49 @@ public Biz createBiz(BizArchive bizArchive) throws IOException { | |
getInjectDependencies(manifestMainAttributes.getValue(INJECT_PLUGIN_DEPENDENCIES))) | ||
.setInjectExportPackages(manifestMainAttributes.getValue(INJECT_EXPORT_PACKAGES)) | ||
.setDeclaredLibraries(manifestMainAttributes.getValue(DECLARED_LIBRARIES)) | ||
.setClassPath(bizArchive.getUrls()).setPluginClassPath(getPluginURLs()); | ||
.setClassPath(getMergedBizClassPath(bizArchive.getUrls(), bizConfig.getExtensionUrls())); | ||
|
||
if (!(bizArchive instanceof DirectoryBizArchive)) { | ||
bizModel.setBizUrl(bizArchive.getUrl()); | ||
// prepare dependent plugins and plugin export map | ||
List<String> dependentPlugins = bizConfig.getDependentPlugins(); | ||
if (dependentPlugins == null || dependentPlugins.isEmpty()) { | ||
dependentPlugins = StringUtils.strToList( | ||
manifestMainAttributes.getValue("dependent-plugins"), | ||
Constants.MANIFEST_VALUE_SPLIT); | ||
} | ||
resolveExportMapIfNecessary(bizModel, dependentPlugins); | ||
|
||
// must be after prepare dependent plugins | ||
bizModel.setPluginClassPath(getPluginURLs(bizModel)); | ||
|
||
// create biz classloader | ||
BizClassLoader bizClassLoader = new BizClassLoader(bizModel.getIdentity(), | ||
getBizUcp(bizModel.getClassPath()), bizArchive instanceof ExplodedBizArchive | ||
|| bizArchive instanceof DirectoryBizArchive); | ||
getBizUcp(bizModel), bizArchive instanceof ExplodedBizArchive | ||
|| bizArchive instanceof DirectoryBizArchive); | ||
bizClassLoader.setBizModel(bizModel); | ||
bizModel.setClassLoader(bizClassLoader); | ||
|
||
// set biz work dir | ||
if (bizModel.getBizUrl() != null) { | ||
bizModel.setBizTempWorkDir(new File(bizModel.getBizUrl().getFile())); | ||
} | ||
|
||
return bizModel; | ||
} | ||
|
||
@Override | ||
public Biz createBiz(File file) throws IOException { | ||
public Biz createEmbedMasterBiz(ClassLoader masterClassLoader) { | ||
BizModel bizModel = new BizModel(); | ||
bizModel.setBizState(BizState.RESOLVED, StateChangeReason.CREATED) | ||
.setBizName(ArkConfigs.getStringValue(MASTER_BIZ)).setBizVersion("1.0.0") | ||
.setMainClass("embed main").setPriority("100").setWebContextPath("/") | ||
.setDenyImportPackages(null).setDenyImportClasses(null).setDenyImportResources(null) | ||
.setInjectPluginDependencies(new HashSet<>()).setInjectExportPackages(null) | ||
.setClassPath(ClassLoaderUtils.getURLs(masterClassLoader)) | ||
.setClassLoader(masterClassLoader); | ||
return bizModel; | ||
} | ||
|
||
private BizArchive prepareBizArchive(File file) throws IOException { | ||
BizArchive bizArchive; | ||
if (ArkConfigs.isEmbedEnable()) { | ||
File unpackFile = FileUtils.file(file.getAbsolutePath() + "-unpack"); | ||
|
@@ -131,35 +202,56 @@ public Biz createBiz(File file) throws IOException { | |
JarFileArchive jarFileArchive = new JarFileArchive(bizFile); | ||
bizArchive = new JarBizArchive(jarFileArchive); | ||
} | ||
BizModel biz = (BizModel) createBiz(bizArchive); | ||
biz.setBizTempWorkDir(file); | ||
yuanyuancin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return biz; | ||
return bizArchive; | ||
} | ||
|
||
@Override | ||
public Biz createBiz(BizOperation bizOperation, File file) throws IOException { | ||
BizModel biz = (BizModel) createBiz(file); | ||
if (bizOperation != null && !StringUtils.isEmpty(bizOperation.getBizVersion())) { | ||
biz.setBizVersion(bizOperation.getBizVersion()); | ||
if (biz.getBizClassLoader() instanceof BizClassLoader) { | ||
BizClassLoader bizClassLoader = (BizClassLoader) (biz.getBizClassLoader()); | ||
bizClassLoader.setBizIdentity(biz.getIdentity()); | ||
} | ||
private URL[] getMergedBizClassPath(URL[] bizArchiveUrls, URL[] extensionUrls) { | ||
if (extensionUrls == null || extensionUrls.length == 0) { | ||
return bizArchiveUrls; | ||
} | ||
return biz; | ||
return Stream.concat(Arrays.stream(bizArchiveUrls), Arrays.stream(extensionUrls)).toArray(URL[]::new); | ||
} | ||
|
||
@Override | ||
public Biz createEmbedMasterBiz(ClassLoader masterClassLoader) { | ||
BizModel bizModel = new BizModel(); | ||
bizModel.setBizState(BizState.RESOLVED, StateChangeReason.CREATED) | ||
.setBizName(ArkConfigs.getStringValue(MASTER_BIZ)).setBizVersion("1.0.0") | ||
.setMainClass("embed main").setPriority("100").setWebContextPath("/") | ||
.setDenyImportPackages(null).setDenyImportClasses(null).setDenyImportResources(null) | ||
.setInjectPluginDependencies(new HashSet<>()).setInjectExportPackages(null) | ||
.setClassPath(ClassLoaderUtils.getURLs(masterClassLoader)) | ||
.setClassLoader(masterClassLoader); | ||
return bizModel; | ||
private void resolveExportMapIfNecessary(BizModel bizModel, List<String> dependentPlugins) { | ||
Set<Plugin> plugins = new HashSet<>(); | ||
if (ArkConfigs.isBizSpecifyDependentPluginsEnable()) { | ||
if (dependentPlugins != null && !dependentPlugins.isEmpty()) { | ||
for (String pluginName : dependentPlugins) { | ||
Plugin plugin = pluginManagerService.getPluginByName(pluginName); | ||
plugins.add(plugin); | ||
Comment on lines
+220
to
+221
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential NullPointerException when adding plugins The method Please add a null check before adding the plugin to the set. For example: Plugin plugin = pluginManagerService.getPluginByName(pluginName);
if (plugin != null) {
plugins.add(plugin);
} else {
// Handle the case where the plugin is not found
} |
||
} | ||
} | ||
} else { | ||
plugins.addAll(pluginManagerService.getPluginsInOrder()); | ||
} | ||
|
||
bizModel.setDependentPlugins(plugins); | ||
for (Plugin plugin : plugins) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 所以每个 plugin,是需要定义自己的 export 内容的,这部分需要在使用文档里说明。 |
||
for (String exportIndex : plugin.getExportPackageNodes()) { | ||
bizModel.getExportNodeAndClassLoaderMap().putIfAbsent(exportIndex, plugin); | ||
} | ||
for (String exportIndex : plugin.getExportPackageStems()) { | ||
bizModel.getExportStemAndClassLoaderMap().putIfAbsent(exportIndex, plugin); | ||
} | ||
for (String exportIndex : plugin.getExportClasses()) { | ||
bizModel.getExportClassAndClassLoaderMap().putIfAbsent(exportIndex, plugin); | ||
} | ||
for (String resource : plugin.getExportResources()) { | ||
bizModel.getExportResourceAndClassLoaderMap().putIfAbsent(resource, | ||
new LinkedList<>()); | ||
bizModel.getExportResourceAndClassLoaderMap().get(resource).add(plugin); | ||
} | ||
for (String resource : plugin.getExportPrefixResourceStems()) { | ||
bizModel.getExportPrefixStemResourceAndClassLoaderMap().putIfAbsent(resource, | ||
new LinkedList<>()); | ||
bizModel.getExportPrefixStemResourceAndClassLoaderMap().get(resource).add(plugin); | ||
} | ||
for (String resource : plugin.getExportSuffixResourceStems()) { | ||
bizModel.getExportSuffixStemResourceAndClassLoaderMap().putIfAbsent(resource, | ||
new LinkedList<>()); | ||
bizModel.getExportSuffixStemResourceAndClassLoaderMap().get(resource).add(plugin); | ||
} | ||
} | ||
} | ||
|
||
private Set<String> getInjectDependencies(String injectPluginDependencies) { | ||
|
@@ -183,16 +275,16 @@ public boolean matches(Archive.Entry entry) { | |
}); | ||
} | ||
|
||
private URL[] getBizUcp(URL[] bizClassPath) { | ||
private URL[] getBizUcp(BizModel bizModel) { | ||
List<URL> bizUcp = new ArrayList<>(); | ||
bizUcp.addAll(Arrays.asList(bizClassPath)); | ||
bizUcp.addAll(Arrays.asList(getPluginURLs())); | ||
bizUcp.addAll(Arrays.asList(bizModel.getClassPath())); | ||
bizUcp.addAll(Arrays.asList(getPluginURLs(bizModel))); | ||
return bizUcp.toArray(new URL[bizUcp.size()]); | ||
} | ||
|
||
private URL[] getPluginURLs() { | ||
private URL[] getPluginURLs(BizModel bizModel) { | ||
List<URL> pluginUrls = new ArrayList<>(); | ||
for (Plugin plugin : pluginManagerService.getPluginsInOrder()) { | ||
for (Plugin plugin : bizModel.getDependentPlugins()) { | ||
pluginUrls.add(plugin.getPluginURL()); | ||
} | ||
return pluginUrls.toArray(new URL[pluginUrls.size()]); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider Thread Safety for
dependentPlugins
If
dependentPlugins
may be accessed by multiple threads concurrently, consider using a thread-safe collection likeCopyOnWriteArraySet
or synchronizing access appropriately to prevent concurrent modification issues.