Java语言的模块化之路似乎走得异常艰辛,但实际上技术难点看上去并不像是最大的问题,OSGi已经是业内公认的标准,正如这篇文章中作者所说的,
I suspect the answer to these questions has little to do with technology, and more to do with politics.
anyway,要等到Java语言级别来支持模块化,不知道要何年何月。最近做了个尝试,直接在ClassLoader中嵌入Equinox容器,这样是不是也算得上是在语言级别上支持了模块化:)
V1
在类加载的整体流程中,我们可以通过-Djava.system.class.loader
这个system property来介入类加载过程当中。
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader(); // sun.misc.Launcher$AppClassLoader
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}
class SystemClassLoaderAction
implements PrivilegedExceptionAction<ClassLoader> {
private ClassLoader parent;
SystemClassLoaderAction(ClassLoader parent) {
this.parent = parent; // 传入的是sun.misc.Launcher$AppClassLoader
}
public ClassLoader run() throws Exception {
String cls = System.getProperty("java.system.class.loader");
if (cls == null) {
return parent;
}
Constructor ctor = Class.forName(cls, true, parent)
.getDeclaredConstructor(new Class[] { ClassLoader.class });
ClassLoader sys = (ClassLoader) ctor.newInstance(
new Object[] { parent });
Thread.currentThread().setContextClassLoader(sys);
return sys;
}
}
可以看到,由-Djava.system.class.loader
指定的ClassLoader的parent将会是sun.misc.Launcher$AppClassLoader
,并且该ClassLoader必须有一个接收parent的构造函数。
开始写ClassLoader之前需要先准备下环境,见之前写的这篇栗子,Main
类改一下,
import me.kisimple.just4fun.osgi.HelloOSGi;
public class Main {
public static void main(String[] args) throws Throwable {
new HelloOSGi().run();
}
}
第一版的ClassLoader是这样子的,
package me.kisimple.just4fun;
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 IClassLoader extends ClassLoader {
// -Djava.system.class.loader=me.kisimple.just4fun.IClassLoader
private BundleContext bundleContext;
public IClassLoader(ClassLoader parent) {
super(parent);
try {
bundleContext = EclipseStarter.startup((new String[]{}), null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class c = findLoadedClass(name);
if (c == null) {
if(name.startsWith("java")) {
return getParent().loadClass(name);
}
if(bundleContext != null) {
for (Bundle osgiBundle : bundleContext.getBundles()) {
if (osgiBundle instanceof EquinoxBundle) {
EquinoxBundle equinoxBundle = (EquinoxBundle)osgiBundle;
ModuleWiring wiring = equinoxBundle.getModule().getCurrentRevision().getWiring();
if (wiring != null) {
BundleLoader loader = (BundleLoader)wiring.getModuleLoader();
String packageName = BundleLoader.getPackageName(name);
if (loader != null && loader.isExportedPackage(packageName)) {
System.out.println("[LOADED] " + name);
System.out.println("[FORM] Bundle#" + equinoxBundle);
return equinoxBundle.loadClass(name);
}
}
}
}
}
return getParent().loadClass(name);
}
return c;
}
}
}
直接在构造函数中启动Equinox。但是这一版执行之后会报下面这个错,
Caused by: java.lang.InternalError: internal error: SHA-1 not available.
at sun.security.provider.SecureRandom.init(Unknown Source)
at sun.security.provider.SecureRandom.<init>(Unknown Source)
at java.security.SecureRandom.getDefaultPRNG(Unknown Source)
at java.security.SecureRandom.<init>(Unknown Source)
at java.io.File$TempDirectory.<clinit>(Unknown Source)
at java.io.File.createTempFile(Unknown Source)
at org.eclipse.osgi.storage.StorageUtil.canWrite(StorageUtil.java:172)
at org.eclipse.osgi.internal.location.EquinoxLocations.computeDefaultConfigurationLocation(EquinoxLocations.java:257)
at org.eclipse.osgi.internal.location.EquinoxLocations.<init>(EquinoxLocations.java:97)
at org.eclipse.osgi.internal.framework.EquinoxContainer.<init>(EquinoxContainer.java:70)
at org.eclipse.osgi.launch.Equinox.<init>(Equinox.java:31)
at org.eclipse.core.runtime.adaptor.EclipseStarter.startup(EclipseStarter.java:295)
看上去应该是一些安全相关的provider在这个时候还无法找到,so,此路不通。
V2
既然在构造函数中不行,那就挪到loadClass
方法中试试,
package me.kisimple.just4fun;
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 IClassLoader extends ClassLoader {
// -Djava.system.class.loader=me.kisimple.just4fun.IClassLoader
private volatile static boolean equinoxStartupFlag = false;
private BundleContext bundleContext;
public IClassLoader(ClassLoader parent) {
super(parent);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class c = findLoadedClass(name);
if (c == null) {
if(!equinoxStartupFlag) {
equinoxStartup();
}
if(name.startsWith("java")) {
return getParent().loadClass(name);
}
if(bundleContext != null) {
for (Bundle osgiBundle : bundleContext.getBundles()) {
if (osgiBundle instanceof EquinoxBundle) {
EquinoxBundle equinoxBundle = (EquinoxBundle)osgiBundle;
ModuleWiring wiring = equinoxBundle.getModule().getCurrentRevision().getWiring();
if (wiring != null) {
BundleLoader loader = (BundleLoader)wiring.getModuleLoader();
String packageName = BundleLoader.getPackageName(name);
if (loader != null && loader.isExportedPackage(packageName)) {
System.out.println("[LOADED] " + name);
System.out.println("[FORM] Bundle#" + equinoxBundle);
return equinoxBundle.loadClass(name);
}
}
}
}
}
return getParent().loadClass(name);
}
return c;
}
}
private synchronized void equinoxStartup() {
if(!equinoxStartupFlag) {
try {
bundleContext = EclipseStarter.startup((new String[]{}), null);
System.out.println("////////////////////");
System.out.println("//Equinox Started.//");
System.out.println("////////////////////");
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
EclipseStarter.shutdown();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}catch(Exception e){
throw new RuntimeException(e);
}
equinoxStartupFlag = true;
}
}
}
执行的时候会发现,一直在输出错误。这其实是一个递归加载的问题。执行equinoxStartup
方法的时候,会遇到其他需要加载的类,这时候loadClass
方法会被递归调用,也就一直卡在equinoxStartup
了。解决方案是,把equinoxStartupFlag = true;
移到最前,
private synchronized void equinoxStartup() {
if(!equinoxStartupFlag) {
equinoxStartupFlag = true;
try {
bundleContext = EclipseStarter.startup((new String[]{}), null);
System.out.println("////////////////////");
System.out.println("//Equinox Started.//");
System.out.println("////////////////////");
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
EclipseStarter.shutdown();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
不过这时候会出现新的错误,
> java -Djava.system.class.loader=me.kisimple.just4fun.IClassLoader me.kisimple.just4fun.Main
////////////////////
//Equinox Started.//
////////////////////
Exception in thread "main" java.lang.NoClassDefFoundError: me/kisimple/just4fun/osgi/HelloOSGi
at me.kisimple.just4fun.Main.main(Main.java:40)
Caused by: java.lang.ClassNotFoundException: me.kisimple.just4fun.osgi.HelloOSGi
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 1 more
V3
上面的问题,找不到HelloOSGi
,其实是由于并没有使用我们的IClassLoader
来加载,可以通过在loadClass
方法中输出正在加载的类来证实。
那么为什么没有使用IClassLoader
呢?其实是由于Main-Class
,也就是me.kisimple.just4fun.Main
,并不是由IClassLoader
所加载,而是由IClassLoader.getParent()
,也就是sun.misc.Launcher$AppClassLoader
,所完成的。所以接下来由Main-Class
所触发的类加载都跟IClassLoader
没有一毛钱关系了。
那接下来的问题就是要自己去加载Main-Class
了。首先需要先找到Main-Class
的名字,这个可以通过sun.java.command
这个system property获得。然后就是怎么加载的问题了。既然sun.misc.Launcher$AppClassLoader
可以加载Main-Class
,那我们就复用它的代码呗,自我感觉是一个比较取巧的方法:)
package me.kisimple.just4fun;
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;
import java.net.URL;
import java.net.URLClassLoader;
public class IClassLoader extends URLClassLoader {
// -Djava.system.class.loader=me.kisimple.just4fun.IClassLoader
private volatile static boolean equinoxStartupFlag = false;
private BundleContext bundleContext;
private String mainClassName = "";
public IClassLoader(ClassLoader parent) {
super(((URLClassLoader)parent).getURLs(), parent);
mainClassName = System.getProperty("sun.java.command");
System.out.println("[sun.java.command] " + mainClassName);
}
public IClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class c = findLoadedClass(name);
if (c == null) {
System.out.println("[IClassLoader] " + name);
if(!equinoxStartupFlag) {
equinoxStartup();
}
if(name.startsWith("java")) {
return getParent().loadClass(name);
}
if(mainClassName.startsWith(name)) {
return findClass(name);
}
if(bundleContext != null) {
for (Bundle osgiBundle : bundleContext.getBundles()) {
if (osgiBundle instanceof EquinoxBundle) {
EquinoxBundle equinoxBundle = (EquinoxBundle)osgiBundle;
ModuleWiring wiring = equinoxBundle.getModule().getCurrentRevision().getWiring();
if (wiring != null) {
BundleLoader loader = (BundleLoader)wiring.getModuleLoader();
String packageName = BundleLoader.getPackageName(name);
if (loader != null && loader.isExportedPackage(packageName)) {
System.out.println("[Equinox] LOADING# " + name);
System.out.println("[Equinox] FORM# Bundle#" + equinoxBundle);
return equinoxBundle.loadClass(name);
}
}
}
}
}
return getParent().loadClass(name);
}
return c;
}
}
private synchronized void equinoxStartup() {
if(!equinoxStartupFlag) {
equinoxStartupFlag = true;
try {
bundleContext = EclipseStarter.startup((new String[]{}), null);
System.out.println("////////////////////");
System.out.println("//Equinox Started.//");
System.out.println("////////////////////");
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
EclipseStarter.shutdown();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
}
不再是继承ClassLoader
,而是直接继承URLClassLoader
,并且在构造函数拿到加载Main-Class
所需要的URL
信息,然后匹配mainClassName
,通过调用findClass
方法来完成Main-Class
的加载。
事实证明此路是可以通的:)输出如下,
> java -Djava.system.class.loader=me.kisimple.just4fun.IClassLoader me.kisimple.just4fun.Main
[sun.java.command] me.kisimple.just4fun.Main
[IClassLoader] me.kisimple.just4fun.Main
[IClassLoader] sun.security.provider.Sun
[IClassLoader] sun.security.rsa.SunRsaSign
[IClassLoader] sun.net.www.protocol.c.Handler
[IClassLoader] sun.net.www.protocol.e.Handler
[IClassLoader] sun.net.www.protocol.c.Handler
[IClassLoader] sun.util.resources.CalendarData
[IClassLoader] sun.util.resources.CalendarData_zh
[IClassLoader] sun.text.resources.FormatData
[IClassLoader] sun.text.resources.FormatData_zh
[IClassLoader] sun.text.resources.FormatData_zh_CN
[IClassLoader] sun.util.resources.CurrencyNames
[IClassLoader] sun.util.resources.CurrencyNames_zh_CN
////////////////////
//Equinox Started.//
////////////////////
[IClassLoader] java.lang.Object
[IClassLoader] java.lang.String
[IClassLoader] java.lang.Throwable
[IClassLoader] me.kisimple.just4fun.osgi.HelloOSGi
[Equinox] LOADING# me.kisimple.just4fun.osgi.HelloOSGi
[Equinox] FORM# Bundle#me.kisimple.just4fun.osgi.common_1.0.0 [3]
Hello,OSGi.
这种有点hack的方式,跟JDK版本应该还是相关的,下面是我的JDK版本,
> java -version
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)
alright,今天就到这,have fun ^_^
参考资料
- http://moi.vonos.net/java/osgi-classloaders/
- http://java.dzone.com/news/java-modularity-osgi-and
- http://stackoverflow.com/questions/41894/0-program-name-in-java-discover-main-class