打包后的工具类 God+BlueJ+ClassLoader

来源:互联网 发布:twitter第三方登录js 编辑:程序博客网 时间:2024/06/05 03:20

工具类 God从开始的一个类,变成了使用策略模式的几个类;从项目中的一部分,变成了一个单独jar;

打包的yqj2065.jar在NetBeans环境中运行良好,有一天我在BlueJ中使用该yqj2065.jar,总是抛出NullPointerException。

我知道是路径问题,但是为什么NetBeans环境可以但是BlueJ中不行?虽然我一般用NetBeans,这个问题总在心里放着,真TM不舒服,而且一下两下还搞不定它。

近期有时间研究一下这个问题,发现是BlueJ的ClassLoader问题。ClassLoader问题其实在《编程导论(Java)·7.1类载入》中提到过,但是我已经忘记了。尴尬,或者说,当时我就没有太在意这个问题。p220写到:

注意:在BlueJ和控制台中运行,结果不同。在BlueJ中输出:java.net.URLClassLoader,而控制台中为sun.misc.Launcher$AppClassLoader。开发环境通常提供自己的装载器(注:选择某种装载器使用策略),而在控制台中myLoader 与ClassLoader loader = ClassLoader.getSystemClassLoader()的loader指向同一个对象。(这点差异不影响后面的介绍)
下面介绍一下我的探索过程,虽然走了一些弯路,但是走些弯路,可以看到一些也有意义的东西。

1.选择ClassLoader

第一阶段,在NetBeans和BlueJ项目中测试和选择ClassLoader。目标是访问 默认文件"my.properties"。

try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(path)) 

是关键所在,因此将该语句分开

    public  void testPropertiesLocator() {         ClassLoader[] cls = new ClassLoader[]{                ClassLoader.getSystemClassLoader(),                this.getClass().getClassLoader(),                Main.class.getClassLoader(),                Thread.currentThread().getContextClassLoader()};        for(ClassLoader x : cls ){            URL resource = x.getResource("my.properties");            pln(resource);        }        String cwd = System.getProperty("user.dir");        pln(cwd);    }

NetBeans输出:

file:/D:/yqj2065/yqj2065/build/classes/my.properties
file:/D:/yqj2065/yqj2065/build/classes/my.properties
file:/D:/yqj2065/yqj2065/build/classes/my.properties
file:/D:/yqj2065/yqj2065/build/classes/my.properties
D:\yqj2065\yqj2065


BlueJ项目输出:

null
file:/D:/path/my.properties
file:/D:/path/my.properties
file:/D:/path/my.properties
D:\path

基于classpath的相对路径,在BlueJ中需要排除ClassLoader.getSystemClassLoader()。

剩下的2个(2和3其实是一个东西),在BlueJ项目中都可以工作,

    public static void testGetValue() {        String str = new PropertiesLocator6().getValue("2065") ;        pln(str);     }
但是打包后,仅仅测试getValue("2065"),发现this.getClass().getClassLoader()不行了,它不能够正确访问测试项目的my.properties(它能够访问jar中的资源),

在测试项目中调用jar中testPropertiesLocator()和直接执行testPropertiesLocator()代码,可以明显发现,

public class Test{    public static void testGetValue() {        String str = new PropertiesLocator6().getValue("2065") ;        pln(str);         new Main().testPropertiesLocator();        pln("-------------------------------" );        ClassLoader[] cls = new ClassLoader[]{                Test.class.getClassLoader(),                Thread.currentThread().getContextClassLoader()};        for(ClassLoader x : cls ){            URL resource = x.getResource("my.properties");            pln(resource);        }            }}

输出:

a.DDD

null
null
null
file:/D:/test/my.properties
-------------------------------
file:/D:/test/my.properties
file:/D:/test/my.properties

当我以为问题搞定了,测试create,又出现NullPointerException。既然已经将类全名找到——a.DDD,用它创建对象还有问题?

这时,我才想起来翻书,我意识到是ClassLoader问题。

2.WhoLoadMe

类装载器系统采用托付模型,每个类装载器都有一个父加载器(与类层次无关)。细节请看《编程导论(Java)·7.1类载入》和随书代码。


在NetBeans使用yqj2065.jar的某个项目中,随便找一个类如Client,打印类装载器

        pln(Client.class.getClassLoader());        pln(ClassLoader.getSystemClassLoader());        pln(Thread.currentThread().getContextClassLoader());
输出:

sun.misc.Launcher$AppClassLoader@55f96302
sun.misc.Launcher$AppClassLoader@55f96302
sun.misc.Launcher$AppClassLoader@55f96302

在BlueJ使用yqj2065.jar的项目中,随便找一个类如Test,打印类装载器

    public static void testWhoLoadMe(){        pln(Test.class.getClassLoader() );        pln( God.class.getClassLoader() );        pln( Locator.class.getClassLoader() );        pln(Thread.currentThread().getContextClassLoader());    }
输出:

java.net.URLClassLoader@50ce9f3e
sun.misc.Launcher$AppClassLoader@14dad5dc
sun.misc.Launcher$AppClassLoader@14dad5dc
java.net.URLClassLoader@50ce9f3e

虽然访问 默认文件"my.properties"时,我们指定了Thread.currentThread().getContextClassLoader()——java.net.URLClassLoader,但是反射创建对象时God的类装载器,却是AppClassLoader。真tm烦人。最简单地Class.forName(typeName).newInstance()不能够用了。

新版本如下:

package yqj2065.util;public class God {    public static final  ClassLoader classLoader = Thread.currentThread().getContextClassLoader();    private static final Locator locator =LocatorFactory.getLocator();    public interface Locator{        public String getValue(String path, String key);        default public String getValue(String key){            return getValue("my.properties", key);        }        public static String getPath() {            return System.getProperty("user.dir");        }    }    private static Object _create(String typeName){        Object obj = null;        if (typeName != null) {            try {                obj =classLoader.loadClass(typeName).newInstance();             } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {            }        }        return obj;    }    public static Object create(String path, String key) {        String typeName = locator.getValue(path, key);         return _create( typeName);    }    public static Object create(String key) {                String typeName = locator.getValue(key);         return _create( typeName);    }}
而其他类没有什么变化,如

package yqj2065.util;/** * * @author yqj2065 */import java.io.InputStream;import java.io.InputStreamReader;import java.util.Properties;import static yqj2065.util.Print.pln;public class PropertiesLocator implements God.Locator{    @Override    public String getValue(String path, String key) {        Properties props = new Properties();        try (InputStream is = God.classLoader.getResourceAsStream(path)) {            try (InputStreamReader isr = new InputStreamReader(is, "UTF-8")) {                props.load(isr);//从流中加载properties文件信息            }        } catch (java.io.FileNotFoundException e) {            pln(e.getClass().getName());        } catch (java.io.IOException e) {            pln(e.getClass().getName());        }        return props.getProperty(key);    }}
打包后可以使用了。

但是,...................

①我有意留下的小尾巴:

private static final Locator locator =LocatorFactory.getLocator();
②我实在没有兴趣搞的东西,BlueJ的静态数据加载问题。在测试项目中使用了yqj2065.jar,打开BlueJ运行ok。

public class DDD{    public void foo(){        pln("okjhkkkkkkkjhlfkkdffddkk");    }}
    public static void testCreate(){        DDD dd = (DDD)God.create("2065");        dd.foo();    }
修改DDD,编译,再次运行:

java.lang.ClassCastException: a.DDD cannot be cast to a.DDD
(所以,运行前,请在BlueJ中重启JVM)






原创粉丝点击