問題描述:程序代碼中,執行下列語句:Object mapperObj = Class.forName(mapperClassName).newInstance();MapperInterface mapper = (MapperInterface)mapperObj; 報ClassCastException。
bug fix: 1.分別取得運行環境下mapperObj和MapperInerface.class的 classloader: mapperObj.getClass().getClassLoader() : sun.misc.Launcher$AppClassLoader MapperInterface.class.getClass().getClassLoader() : WebContainerClassLoader2.由jvm classload 機制可知,jvm load class 分四個層次: 第一層為bootstrapclassloader : 主要負責load rt.jar等jvm必須的jar包中的類。 第二層為extclassloader : 主要負責load 被置於java.ext.dirs屬性值所指路徑(默認%Java_HOME%/lib/ext) 中所有的class.其實現類為sun.misc.Launcher$ExtClassLoader 第三層為systemclassloader : 負責load 被置於CLASSPATH路徑中的類。 其實現類一般為sun.misc.Launcher$AppClassLoader 第四層為appclassloader : 由應用程序設計者繼承ClassLoader並實現完成相應user-defined ClassLoader。用於根據應用程序需要加載並不是設計時就知道的類。
詳細的load策略偶就不多寫了,很多文章上都有,總之兩句話: 當define一個類的時候,低層classloader會向上層詢問是否已經define,有則直接拿來用;當load一個類的時候,同樣低層向高層詢問是否能find到,能就直接拿來用。
由此可知,由於原有系統原因, mapperObj被 systemclassloader define;而這裡使用的接口是被appclassloader WebContainerClassLoader define的。所以會造成 ClassCastException錯誤。用instanceof也可發現mapperObj 確實不是 MapperInterface的實例。
由於原系統原因,無法通過改動其他代碼完成更換mapperObj classload的動作,而運行到當前代碼時,mapperObj 已經被define,所以無法通過forName方法的參數更改其class loader,後面的代碼就無法調用其方法。
解決方法:采用類反射,換有Object定義的屬性接 mapperObj;在下面的代碼中,利用mapperObj.getClass().getInterface()方法判斷是否其繼承了 MapperInterface。 然後用反射調用其方法。例:
mapper = Class.forName(mapperClassName).newInstance();
Class[] tmpInterface = mapper.getClass().getInterfaces(); for(int i=0 ;i
if(flag){ Class[] tc = new Class[2] ; tc[0] = String.class; tc[1] = HttpServletRequest.class; Method mapperFunc = mapper.getClass().getMethod( "mapFunction", tc ); Object res = mapperFunc.invoke( mapper, new Object[] { event.getServletClassName(),req } );}