2010年5月17日

例子下载在此

最近搞了个小实验,发现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方法时发生的类加载行为。

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

posted @ 2010-05-17 12:09 深夜两点 阅读(4782) | 评论 (8)编辑 收藏