OSGi#3:ClassLoader中嵌入Equinox

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来介入类加载过程当中。

看下ClassLoader的源码

  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 ^_^

参考资料

时间: 2024-09-20 16:29:06

OSGi#3:ClassLoader中嵌入Equinox的相关文章

javascript-如何在iframe中嵌入mht文件

问题描述 如何在iframe中嵌入mht文件 除了IE浏览器可以直接显示外其它浏览器都会弹出下载,该如何解决这样的问题? 解决方案 如果你有php服务器端,可以将mht文件作为mime编码的html解析,然后返回客户端,而不是直接嵌入(和读取邮件附件其实是一个道理,mht其实就是邮件附件的格式).因为mht是微软自己用的,所以别的浏览器不认. 解决方案二: Iframe不能解析mht格式文件

JavaScript解析:HTM中嵌入嵌入JavaScript语言引擎

文章简介:JavaScript解析:让搜索引擎看到更真实的网页. 长期以来,站长们选择使用JavaScript来实现网页的动态行为,这样做的原因是多种多样的,如加快页面的响应速度.降低网站流量.隐藏链接或者嵌入广告等.由于早期的搜索引擎没有相应的处理能力,导致在索引这类网页上往往出现问题,可能无法收录有价值的资源,也可能出现作弊. 引入JavaScript解析的目的,正是为了解决上述两方面的问题,其结果也就是使搜索引擎可以更为清晰的了解用户实际打开该网页时看到的效果.比如有些网站会将用户评论.评

在Flex中嵌入完整HTML页面

页面 有时候我们需要在Flex应用中嵌入HTML代码,根据嵌入HTML要求的不同有以下两种方法: 1.Flex文本组件(Label.Text.TextArea)的htmlText属性支持一些基本的HTML代码,例如: <mx:TextArea>   <mx:htmlText>     <![CDATA[       <p align="center"><font size="15" color="#3399f

怎样在unity3d中嵌入播放视频

Unity播放视频主要支持mov, .mpg, .mpeg, .mp4, .avi, .asf格式.首先,我们需要安装QuickTime播放器.然后,在Unity3D中加载视频. unity3d教程-如何在unity3d中嵌入播放视频 [html] view plaincopyprint? nbsp; public var movTexture : MovieTexture; nbsp; function Update() { nbsp; renderer.material.mainTextur

在HTML代码中嵌入Flash文件的解决方案!(下)

关于我的SWFObject V1.5的使用过程,以上篇中的介绍暂时告一段落了,下面我将会带领SWFObject V2.1出场与大家见面,如果我早一点结识V2.1的话,或许就不会受到"等待HTML DOM加载"问题的侵扰了. 首先,给大家简要介绍一下V2.1语法的调用示例: <script type="text/javascript" src="swfobject.js"></script><script type=&

如何在Word 2013文档中嵌入字体

通常情况下,每台电脑安装的字体可能并不相同.用户在Word2013文档中为文本设置字体后,如果更换电脑打开该Word文档时,有可能会出现原先设置的字体不可用的情况.在Word2013文档中嵌入使用的字体可以解决该问题,操作步骤如下所述: 第1步,打开Word2013文档窗口,依次单击"文件"→"另存为"按钮,并选择Word文档保存位置,如图2013080115所示. 图2013080115 单击"另存为"按钮 第2步,在打开的"另存为&

在Eclipse中嵌入NASA World Wind Java SDK

使用此开源 SDK 开发 GIS 应用程序 简介:NASA 开发的开源 World Wind Java (WWJ) SDK 为地理信息系统(Geographic Information Systems,GIS)社区提供了新的可能性.World Wind 是一种使用 Java 语言和 OpenGL 编写的 3D 交互式地球查看工具,使用户可以从外太空观看地球上的任何地方.本文将讲述想要增强基于 Eclipse 的 应用程序的 GIS 开发人员如何将 WWJ SDK 作为插件嵌入到 Eclipse

如何在HTML中嵌入PHP 代码

  对于一个有经验的 PHP Web 开发者,这是一件非常容易的事情.但是对于刚开始接触 PHP 编程语言的新手这就是一个问题.所以这里介绍如何在常规的 HTML 代码中嵌入 PHP 代码. 在常规的 HTML 中嵌入 PHP 代码 创建一个 hello 脚本,命名为 hello.php: ? 1 2 3 4 5 6 7 8 <html> <head> <title>PHP Test</title> </head> <body> &l

PPT2003中嵌入其它字体的方法

  我们在制作PPT时常常用到比较漂亮的字体,为了能够在别的电脑上同样显示出字体的效果,可以选择在PPT中嵌入字体的方式.希望对大家有点帮助. PPT2003的做法是: 打开一个PowerPoint文件,单击菜单栏中的[工具]按钮,在下拉菜单中选择"选项"命令,系统会打开"选项"对话框,切换到"保存"标签,选中其中的"嵌入TrueType字体"选项.为了减少演示文稿的容量,在选中"嵌入TrueType字体"