今天来看下怎么在程序中使用Equinox容器。
startup
上一篇文章中,我们通过下面的命令启动了容器,
> java -jar org.eclipse.osgi_3.10.2.v20150203-1939.jar -console
通过查看org.eclipse.osgi_3.10.2.v20150203-1939.jar
这个jar包的MANIFEST.MF
找到Main-Class
,
Main-Class: org.eclipse.core.runtime.adaptor.EclipseStarter
EclipseStarter
的main
方法中有如下注释,
/**
* This is the main to start osgi.
* It only works when the framework is being jared as a single jar
*/
所以不能通过main
方法来启动。Google了一把,在SO上面发现有人问过这个问题,里面说到可以通过startup
方法来启动,EclipseStarter.startup((new String[]{"-console"}), null);
等同于上面jar包启动的方式。
EquinoxConfiguration
以编程方式启动,同样的,需要configuration/config.ini
文件与org.eclipse.osgi_3.10.2.v20150203-1939.jar
在同一目录下,但这只是默认的配置,可以通过设置system property来改变。
容器的配置主要都存在EquinoxConfiguration
中,而关于目录这样的location配置则单独抽了出来,放在EquinoxLocations
中。二者都是在EquinoxContainer
中初始化,
public EquinoxContainer(Map<String, ?> configuration) {
this.equinoxConfig = new EquinoxConfiguration(configuration, new HookRegistry(this));
this.equinoxLocations = new EquinoxLocations(this.equinoxConfig);
loadConfig(equinoxConfig, equinoxLocations);
...
}
EquinoxConfiguration
将所有配置到保存到了一个Properties
中,
private final Properties configuration;
而由EquinoxContainer
传入的那个Map
则是initialConfig
,最终也会被存到configuration
当中。这个initialConfig
是由EclipseStarter#getConfiguration
构造出来的,
private synchronized static Map<String, String> getConfiguration() {
if (configuration == null) {
configuration = new HashMap<String, String>();
// TODO hack to set these to defaults for EclipseStarter
// Note that this hack does not allow these properties to be specified in config.ini
configuration.put(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES, System.getProperty(EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES, "true")); //$NON-NLS-1$
configuration.put(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION, System.getProperty(EquinoxConfiguration.PROP_COMPATIBILITY_BOOTDELEGATION, "true")); //$NON-NLS-1$
}
return configuration;
}
注意,EquinoxConfiguration.PROP_USE_SYSTEM_PROPERTIES
的配置默认是true
,会将所有的system property存入EquinoxConfiguration
中,
this.configuration = useSystemProperties ? System.getProperties() : new Properties();
EquinoxLocations
说完EquinoxConfiguration
接下来看看EquinoxLocations
。EquinoxLocations
会在构造函数中,使用EquinoxConfiguration
,初始化各种位置信息。我们来看下config.ini
的位置是怎么得出来的。
config.ini
文件在EquinoxContainer#loadConfig
方法中使用,
private static void loadConfig(EquinoxConfiguration equinoxConfig, EquinoxLocations equinoxLocations) {
if (Boolean.TRUE.toString().equals(equinoxConfig.getConfiguration(EquinoxConfiguration.PROP_IGNORE_USER_CONFIGURATION)))
return;
Location configArea = equinoxLocations.getConfigurationLocation();
if (configArea == null)
return;
URL location = null;
try {
location = new URL(configArea.getURL().toExternalForm() + CONFIG_FILE);
} catch (MalformedURLException e) {
// its ok. This should never happen
}
equinoxConfig.mergeConfiguration(loadProperties(location, equinoxConfig));
}
所以是通过EquinoxLocations#configurationLocation
来得出config.ini
的具体位置。configurationLocation
的计算逻辑,上面已经说了,在EquinoxLocations
的构造函数中,
mungeConfigurationLocation();
// compute a default but it is very unlikely to be used since main will have computed everything
temp = buildLocation(PROP_CONFIG_AREA_DEFAULT, null, "", false, false, null); //$NON-NLS-1$
defaultLocation = temp == null ? null : temp.getURL();
if (defaultLocation == null && equinoxConfig.getConfiguration(PROP_CONFIG_AREA) == null)
// only compute the default if the configuration area property is not set
defaultLocation = buildURL(computeDefaultConfigurationLocation(), true);
configurationLocation = buildLocation(PROP_CONFIG_AREA, defaultLocation, "", false, false, null); //$NON-NLS-1$
也就是说如果没有设置PROP_CONFIG_AREA
,就会使用默认的配置,
private String computeDefaultConfigurationLocation() {
// 1) We store the config state relative to the ‘eclipse‘ directory if possible
// 2) If this directory is read-only
// we store the state in <user.home>/.eclipse/<application-id>_<version> where <user.home>
// is unique for each local user, and <application-id> is the one
// defined in .eclipseproduct marker file. If .eclipseproduct does not
// exist, use "eclipse" as the application-id.
URL installURL = computeInstallConfigurationLocation();
if (installURL != null && "file".equals(installURL.getProtocol())) { //$NON-NLS-1$
File installDir = new File(installURL.getFile());
File defaultConfigDir = new File(installDir, CONFIG_DIR);
if (!defaultConfigDir.exists())
defaultConfigDir.mkdirs();
if (defaultConfigDir.exists() && StorageUtil.canWrite(defaultConfigDir))
return defaultConfigDir.getAbsolutePath();
}
// We can‘t write in the eclipse install dir so try for some place in the user‘s home dir
return computeDefaultUserAreaLocation(CONFIG_DIR);
}
private URL computeInstallConfigurationLocation() {
String property = equinoxConfig.getConfiguration(PROP_INSTALL_AREA);
if (property != null)
return LocationHelper.buildURL(property, true);
return null;
}
所以默认的配置是使用了PROP_INSTALL_AREA
,而PROP_INSTALL_AREA
是在EquinoxConfiguration#initializeProperties
中设置的,
// initialize some framework properties that must always be set
if (getConfiguration(PROP_FRAMEWORK) == null || getConfiguration(EquinoxLocations.PROP_INSTALL_AREA) == null) {
ProtectionDomain pd = EquinoxConfiguration.class.getProtectionDomain();
CodeSource cs = pd == null ? null : pd.getCodeSource();
URL url = cs == null ? null : cs.getLocation();
if (url == null) {
IOException cause = null;
// try to determine by loading a resource we know we have
URL java6Profile = EquinoxConfiguration.class.getResource("/JavaSE-1.6.profile"); //$NON-NLS-1$
if (java6Profile != null && "jar".equals(java6Profile.getProtocol())) { //$NON-NLS-1$
try {
url = ((JarURLConnection) java6Profile.openConnection()).getJarFileURL();
} catch (IOException e) {
cause = e;
}
}
if (url == null) {
throw new IllegalArgumentException(NLS.bind(Msg.ECLIPSE_STARTUP_PROPS_NOT_SET, PROP_FRAMEWORK + ", " + EquinoxLocations.PROP_INSTALL_AREA), cause); //$NON-NLS-1$
}
}
// allow props to be preset
if (getConfiguration(PROP_FRAMEWORK) == null) {
String externalForm = getFrameworkPath(url.toExternalForm(), false);
setConfiguration(PROP_FRAMEWORK, externalForm);
}
if (getConfiguration(EquinoxLocations.PROP_INSTALL_AREA) == null) {
String filePart = getFrameworkPath(url.getFile(), true);
setConfiguration(EquinoxLocations.PROP_INSTALL_AREA, filePart);
}
}
// always decode these properties
setConfiguration(PROP_FRAMEWORK, decode(getConfiguration(PROP_FRAMEWORK)));
setConfiguration(EquinoxLocations.PROP_INSTALL_AREA, decode(getConfiguration(EquinoxLocations.PROP_INSTALL_AREA)));
妥妥的,原来是使用了ProtectionDomain
里面的CodeSource
,关于他俩,之前有文章介绍过了。所以正如上面所说的,configuration/config.ini
与org.eclipse.osgi_3.10.2.v20150203-1939.jar
默认情况下需要在同一目录。这个PROP_INSTALL_AREA
会被很多其他location用到,具体可以看看EquinoxLocations
的构造函数。
简单起见,我直接将相关jar包及配置扔到%JAVA_HOME%/jre/lib/ext
目录下了,
C:\Java\jdk1.7.0_51\jre\lib>tree ext /F
C:\JAVA\JDK1.7.0_51\JRE\LIB\EXT
│ dnsns.jar
│ jaccess.jar
│ localedata.jar
│ org.eclipse.equinox.common_3.6.200.v20130402-1505.jar
│ org.eclipse.osgi_3.10.2.v20150203-1939.jar
│ org.eclipse.update.configurator_3.3.300.v20140518-1928.jar
│ sunec.jar
│ sunjce_provider.jar
│ sunmscapi.jar
│ zipfs.jar
│
├─configuration
│ config.ini
│
└─plugins
me.kisimple.just4fun.osgi.common_1.0.0.jar
org.apache.felix.gogo.command_0.10.0.v201209301215.jar
org.apache.felix.gogo.runtime_0.10.0.v201209301036.jar
org.apache.felix.gogo.shell_0.10.0.v201212101605.jar
org.eclipse.equinox.console_1.1.0.v20140131-1639.jar
接下来我们写代码来加载plugins
目录下me.kisimple.just4fun.osgi.common_1.0.0.jar
这个bundle里面的me.kisimple.just4fun.osgi.HelloOSGi
,
package me.kisimple.just4fun.osgi;
public class HelloOSGi {
public void run() {
System.out.println("Hello,OSGi.");
}
}
该bundle的MANIFEST.MF
如下,
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: common
Bundle-SymbolicName: me.kisimple.just4fun.osgi.common
Bundle-Version: 1.0.0
Bundle-Vendor: KISIMPLE
Bundle-Localization: systembundle
Import-Package: org.osgi.framework
Export-Package: me.kisimple.just4fun.osgi
直接上代码,
import org.eclipse.core.runtime.adaptor.EclipseStarter;
import org.eclipse.osgi.container.ModuleWiring;
import org.eclipse.osgi.internal.framework.EquinoxBundle;
import org.eclipse.osgi.internal.loader.BundleLoader;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
public class Main {
public static void main(String[] args) throws Throwable {
// BundleContext context = EclipseStarter.startup((new String[]{"-console"}), null);
BundleContext context = EclipseStarter.startup((new String[]{}), null);
String className = "me.kisimple.just4fun.osgi.HelloOSGi";
String packageName = BundleLoader.getPackageName(className);
for (Bundle osgiBundle : context.getBundles()) {
if(osgiBundle instanceof EquinoxBundle) {
EquinoxBundle bundle = (EquinoxBundle)osgiBundle;
ModuleWiring wiring = bundle.getModule().getCurrentRevision().getWiring();
if (wiring != null) {
BundleLoader loader = (BundleLoader)wiring.getModuleLoader();
if (loader != null && loader.isExportedPackage(packageName)) {
Class<?> klass = bundle.loadClass(className);
System.out.println(klass);
System.out.println(klass.getClassLoader());
}
}
}
}
EclipseStarter.shutdown();
}
}
输出如下,
class me.kisimple.just4fun.osgi.HelloOSGi
org.eclipse.osgi.internal.loader.EquinoxClassLoader@54432395[me.kisimple.just4fun.osgi.common:1.0.0(id=3)]
alright,今天就到这^_^