公司應用項目在客戶部署時經常遇到此類問題,為避免實施部署時增加配置量,花了點時間找到了此問題的終極解決辦法(方案二、修改org.hibernate.hql.ast.HqlLexer的源代碼)。在此進行記錄本問題的分析解決方案。
一、問題現象描述:
1、異常信息:
'weblogic.kernel.Default (self-tuning)']…
org.hibernate.QueryException: ClassNotFoundException: org.hibernate.hql.ast.HqlToken [
at org.hibernate.hql.ast.HqlLexer.panic(HqlLexer.java:57)
at antlr.CharScanner.setTokenObjectClass(CharScanner.java:340)
at org.hibernate.hql.ast.HqlLexer.setTokenObjectClass(HqlLexer.java:31)
at antlr.CharScanner.<init>(CharScanner.java:51)
at antlr.CharScanner.<init>(CharScanner.java:60)
at org.hibernate.hql.antlr.HqlBaseLexer.<init>(HqlBaseLexer.java:56)
at org.hibernate.hql.antlr.HqlBaseLexer.<init>(HqlBaseLexer.java:53)
at org.hibernate.hql.antlr.HqlBaseLexer.<init>(HqlBaseLexer.java:50)
at org.hibernate.hql.ast.HqlLexer.<init>(HqlLexer.java:26)
at org.hibernate.hql.ast.HqlParser.getInstance(HqlParser.java:44)
at org.hibernate.hql.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:242)
atorg.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:157)
at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:111)
at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:77)
at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:56)
at org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:72)
at org.hibernate.impl.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:402)
at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:352)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1294)
2、查詢weblogic安裝目錄下的antlr包:
3、應用中引用的是hibernate3和antlr_2.7.6的jar
二、原因分析
根據以上異常信息查看hibernate及antlr的源代碼:
org.hibernate.hql.ast.HqlLexer的部分代碼:
public void setTokenObjectClass(String cl) {
super.setTokenObjectClass( HqlToken.class.getName() );
}
以上super.setTokenObjectClass 方法就是antlr.CharScanner類中定義的方法:
public void setTokenObjectClass(String paramString) {
try {
this.tokenObjectClass = Utils.loadClass(paramString);
} catch (ClassNotFoundException localClassNotFoundException) {
panic("ClassNotFoundException: " + paramString);
}
}
此方法的關鍵部分:Utils.loadClass(paramString);即在hibernate在解析hql是會采用此工具加載org.hibernate.hql.ast.HqlToken類(即HqlLexer類中的setTokenObjectClass方法)。此處會發生什麼情況呢,請看Utils.loadClass的源代碼:
static {
if ("true".equalsIgnoreCase(System.getProperty("ANTLR_DO_NOT_EXIT", "false")))
useSystemExit = false;
if ("true".equalsIgnoreCase(System.getProperty("ANTLR_USE_DIRECT_CLASS_LOADING", "false")))
useDirectClassLoading = true;
}
/** Thanks to Max Andersen at JBOSS and Scott Stanchfield */
public static Class loadClass(String name) throws ClassNotFoundException {
try {
ClassLoader contextClassLoader =Thread.currentThread(). getContextClassLoader();
if (!useDirectClassLoading && contextClassLoader!=null ) {
return contextClassLoader.loadClass(name);
}
return Class.forName(name);
}
catch (Exception e) {
return Class.forName(name);
}
}
從以上的代碼可看處,加載org.hibernate.hql.ast.HqlToken類的類加載器是weblogic啟動類加載器(不管是Thread.currentThread().getContextClassLoader()還是Class.forName,其中Class.forName采用的是Reflection.getCallerClass()的類加載器,即antlr的類加載器),並非應用類加載器。Weblogic類路徑下已經存在antlr的jar包了,系統會優先使用weblogic下的antlr包,而weblogic類路徑下並沒有hibnate的jar包,所以在加載org.hibernate.hql.ast.HqlToken類是會拋出ClassNotFoundException: org.hibernate.hql.ast.HqlToken異常。
三、解決方案
方案一、修改weblogic類加載器中antlr加載的優先級
此方案並不總是有效(尤其是在osgi類型項目或者同一個weblogic域下部署多個項目的情況),當然根據筆者遇到的情況成功率也在95%以上。當此方案無效時可以采用方案二。
方案二、修改org.hibernate.hql.ast.HqlLexer的源代碼:
加載org.hibernate.hql.ast.HqlToken類是,直接用hibernate所在類classload加載即可:
將原來的代碼:
public void setTokenObjectClass(String cl) {
super.setTokenObjectClass( HqlToken.class.getName() );
}
修改為:直接將hqltoken類賦值給this.tokenObjectClass
public void setTokenObjectClass(String cl) {
this.tokenObjectClass = HqlToken.class;
}