例子下载在此

最近搞了个小实验,发现Eclipse 插件的类加载的一个问题。Eclipse使用Equinox实现OSGi的框架,可以在插件的配置中确定哪些类expose出去,哪些类不能为外部所见。我发现的问题是,可以通过ClassLoader绕过这个限制,在外部插件中加载到插件里那些不为外部所见的类,并且能够创建类的实例,可以通过反射调用其方法(当然,如果被加载的类实现了某些接口,也可以通过接口的引用直接调用相应的方法)。

为了演示这个问题,先在eclipse中创建一个插件UtilityLibrary

其中utilitylibrary.expose包中的类会暴露给外部,而utilitylibrary.hide包中的类不会暴露给外部。在MANIFEST.MF中增加这个设置:


VisiableClassVisiableClass类的内容很简单:
package utilitylibrary.expose;

public class VisiableClass {
    
public VisiableClass() {
        System.out.println(
"This is VisiableClass");
    }

    
public String getMessage() {
        
return "From VisiableClass:\r\n"
                
+ this.getClass().getClassLoader().toString() + "\t";
    }
}

package utilitylibrary.hide;

public class InvisiableClass {
    
public InvisiableClass() {
        System.out.println(
"InvisiableClass");
    }

    
public String getMessage() {
        
return "From InvisiableClass:\r\n"
                
+ this.getClass().getClassLoader().toString() + "\t";
    }
}


其实主要就是打印出相应的信息。类代码几乎是一样的。

下面创建另一个插件UsePlugin,依赖并使用UtilityLibrary中的类。插件其实就是Eclipse自带的Hello World程序,它会在eclipse 的toolbar上增加一个按钮,点击后会弹出一个MessageBox。好,MessageBox上显示的就是从UtilityLibrary中类的方法的返回值。首先增加插件依赖关系:


在SampleAction中的Run方法里,如果直接使用InvisiableClass,插件完全找不到这个类,修改建议里面建议expose这个类:


当然,使用VisiableClass是没问题的。下面通过VisiableClass来将InvisiableClass拽出来,SampleAction类的源代码如下,只要关心run方法就可以了:

package useplugin.actions;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;

import utilitylibrary.expose.VisiableClass;

/**
 * Our sample action implements workbench action delegate. The action proxy will
 * be created by the workbench and shown in the UI. When the user tries to use
 * the action, this delegate will be created and execution will be delegated to
 * it.
 * 
 * 
@see IWorkbenchWindowActionDelegate
 
*/
public class SampleAction implements IWorkbenchWindowActionDelegate {
    
private IWorkbenchWindow window;

    
/**
     * The constructor.
     
*/
    
public SampleAction() {
    }

    
/**
     * The action has been activated. The argument of the method represents the
     * 'real' action sitting in the workbench UI.
     * 
     * 
@see IWorkbenchWindowActionDelegate#run
     
*/
    
public void run(IAction action) {
        
try {
            Class
<?> clazz = VisiableClass.class.getClassLoader().loadClass(
                    
"utilitylibrary.hide.InvisiableClass");
            Object obj 
= clazz.newInstance();
            Method method 
= clazz.getMethod("getMessage");
            Object ret 
= method.invoke(obj, new Object[] {});
            System.out.println(ret);
            MessageDialog.openInformation(window.getShell(), 
"UsePlugin", ret
                    .toString());
        } 
catch (ClassNotFoundException e) {
            
// TODO Auto-generated catch block
            e.printStackTrace();
        } 
catch (InstantiationException e) {
            
// TODO Auto-generated catch block
            e.printStackTrace();
        } 
catch (IllegalAccessException e) {
            
// TODO Auto-generated catch block
            e.printStackTrace();
        } 
catch (SecurityException e) {
            
// TODO Auto-generated catch block
            e.printStackTrace();
        } 
catch (NoSuchMethodException e) {
            
// TODO Auto-generated catch block
            e.printStackTrace();
        } 
catch (IllegalArgumentException e) {
            
// TODO Auto-generated catch block
            e.printStackTrace();
        } 
catch (InvocationTargetException e) {
            
// TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    
/**
     * Selection in the workbench has been changed. We can change the state of
     * the 'real' action here if we want, but this can only happen after the
     * delegate has been created.
     * 
     * 
@see IWorkbenchWindowActionDelegate#selectionChanged
     
*/
    
public void selectionChanged(IAction action, ISelection selection) {
    }

    
/**
     * We can use this method to dispose of any system resources we previously
     * allocated.
     * 
     * 
@see IWorkbenchWindowActionDelegate#dispose
     
*/
    
public void dispose() {
    }

    
/**
     * We will cache window object in order to be able to provide parent shell
     * for the message dialog.
     * 
     * 
@see IWorkbenchWindowActionDelegate#init
     
*/
    
public void init(IWorkbenchWindow window) {
        
this.window = window;
    }
}


在run方法里面,直接使用VisiableClass.class.getClassLoader().loadClass("utilitylibrary.hide.InvisiableClass");来加载本不应该被外部所见的Invisiable类。因为在Eclipse中,每个插件使用一个ClassLoader,所以用来加载VisiableClass类的ClassLoader也同样负责加载在同一个插件中的InvisiableClass类。这样InvisiableClass就在插件外部被加载成功了。类加载成功后,剩下的事情就是顺水推舟了,创建个实例然后使用反射调用相应的方法。
程序运行的时候,点击toolbar上那个button,会弹出如下对话框:


程序运行也没啥错误。


问题分析:
其实我觉得这个问题是很难绕过去的。对于同一个插件,因为内部的类需要互相引用和互相使用,所以必须使用同一个类加载器来加载。所以,这个插件只要expose出来一个包,那么外部的插件就可以通过包中的任何一个类来得到加载这个插件中的类的类加载器,然后就可以通过reflect爱做啥做啥了。

换一个角度可能更好理解这个问题为什么难以绕过去。假设VisiableClass需要用到InvisiableClass,虽然InvisiableClass没有暴露出来,但是在正常的使用VisiableClass的时候,需要先加载VisiableClass类,而加载VisiableClass的时候JVM就会隐式的加载InvisiableClass。这个过程和例子里现式的加载InvisiableClass没啥本质不同。也就是说,从ClassLoader的角度,很难判断一个类的加载是正常的代码还是为了突破bundle的访问限制——它们都是在执行run方法时发生的类加载行为。

或者是我有什么地方没设置好?求解答。例子下载在此

Feedback

# re: OSGi(Equinox)类加载的问题——使用ClassLoader突破bundle的访问限制  回复  更多评论   

2010-05-17 12:30 by Jet Geng
你好,拜读你的文章我发现classloader的另外一种用法。谢谢。只是有一点不太明白。你为什么要绕过osgi的这种限制。

# re: OSGi(Equinox)类加载的问题——使用ClassLoader突破bundle的访问限制  回复  更多评论   

2010-05-17 12:40 by Johnny.Liang
从设计的角度来看,通过了解一些底层机制,绕过OSGi的类加载策略来直接访问不对外公开的类,不见得是一件好的事情,作为技术研究,了解这些底层机制有助于更熟悉一个框架,以更灵活的运用它,但作为软件开发,这些做法可能会导致很多隐患和风险,个人认为不值得推崇。

# re: OSGi(Equinox)类加载的问题——使用ClassLoader突破bundle的访问限制  回复  更多评论   

2010-05-17 12:46 by 三人行,必有我师焉
一般隐藏起来的,都是internal的。里面的内容可以随意改变,你用Class.ForName来初始化一个类,一旦class name改变,你的代码就报废了。

这么做有什么意义呢?

# re: OSGi(Equinox)类加载的问题——使用ClassLoader突破bundle的访问限制  回复  更多评论   

2010-05-17 12:57 by 深夜两点
@Jet Geng
@三人行,必有我师焉
@Johnny.Liang

谢谢大家的关注~。 我这也只是一种尝试,并不是在实际中要这么使用,也无意去绕过OSGi的限制,只是为了说明“可以绕过这种限制”。这么使用定然不好。所以我才觉得Equinox应该把这条路封掉。现在的疑问是Equinox没有封掉这条路,期待的答案是Equinox能不能封掉这条路。

# re: OSGi(Equinox)类加载的问题——使用ClassLoader突破bundle的访问限制  回复  更多评论   

2010-05-17 13:20 by 临远
其实没有这么麻烦,大家可以在bundle里看到有loadClass和getResource的方法,直接调用,就可以通过类名和资源名获得对应的资源。实际上内部也是利用了classLoader。

就像暴力反射一样,虽然一般不会这样用,但是也确实提供了让你为所欲为的可能。

# re: OSGi(Equinox)类加载的问题——使用ClassLoader突破bundle的访问限制  回复  更多评论   

2010-05-17 13:41 by 深夜两点
@临远
其实没有这么麻烦,大家可以在bundle里看到有loadClass和getResource的方法,直接调用,就可以通过类名和资源名获得对应的资源。实际上内部也是利用了classLoader。


请问boudle里指的是什么?

# re: OSGi(Equinox)类加载的问题——使用ClassLoader突破bundle的访问限制  回复  更多评论   

2010-05-17 13:52 by 表现表达
2010-05-17 12:40 by Johnny.Liang
从设计的角度来看,通过了解一些底层机制,绕过OSGi的类加载策略来直接访问不对外公开的类,不见得是一件好的事情,作为技术研究,了解这些底层机制有助于更熟悉一个框架,以更灵活的运用它,但作为软件开发,这些做法可能会导致很多隐患和风险,个人认为不值得推崇。

同意,真的不见得是好事情。弊大于利

# re: OSGi(Equinox)类加载的问题——使用ClassLoader突破bundle的访问限制  回复  更多评论   

2010-05-17 18:03 by 临远
@深夜两点
你看一下org.osgi.framework.Bundle的api javadoc。里边有你需要的方法。

只有注册用户登录后才能发表评论。


网站导航: