公司應用項目在客戶部署時經常遇到此類問題,為避免實施部署時增加配置量,花了點時間找到了此問題的終極解決辦法(方案二、修改org.hibernate.hql.ast.HqlLexer的源代碼)。在此進行記錄本問題的分析解決方案。 一、問題現象描述: 1、異常信息: 'weblogic.kernel.D ...
公司應用項目在客戶部署時經常遇到此類問題,為避免實施部署時增加配置量,花了點時間找到了此問題的終極解決辦法(方案二、修改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載入的優先順序
- 拷貝應用中的包antlr-2.7.6.jar到%WL_HOME%\server\lib下
-
修改% mydomain%\startWebLogic.cmd(.sh) :
在set CLASSPATH之前加上下麵一句:
set PRE_CLASSPATH=%WL_HOME%\server\lib\antlr-2.7.6.jar;
在set CLASSPATH之後加上下麵一句:
set CLASSPATH=%PRE_CLASSPATH%;%CLASSPATH%
此方案並不總是有效(尤其是在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;
}