使用Tomcat 常見 "The value for the useBean class attribute is invalid" 錯誤。該錯誤是指 JSP 中給定的 useBean 標簽的 class 屬性的值無效(不是 Bean 的屬性值)。
在說明這個問題前,先看看有關的 Tomcat 源代碼(org.apache.jasper.compiler.Generator):
if (beanName == null) {
try {
Class bean = ctxt.getClassLoader().loadClass(klass);
int modifiers = bean.getModifiers();
if (!Modifier.isPublic(modifiers) ||
Modifier.isInterface(modifiers) ||
Modifier.isAbstract (modifiers)) {
throw new Exception("Invalid bean class modifier");
}
// Check that there is a 0 arg constructor
bean.getConstructor(new Class[] {});
generateNew = true;
} catch (Exception e) {
// Cannot instantiate the specified class
if (ctxt.getOptions().getErrorOnUseBeanInvalidClassAttribute()) {
err.jspError(n, "jsp.error.invalid.bean ", klass);
}
}
}
可見錯誤可能的原因包括:
1. 在編譯 JSP 時(不是運行時),指定的 Bean 類沒找到
2. Bean 雖然找到了,但是它不是 public 的,或者找到的 class 文件是 interface 或抽象類
3. Bean 類中沒有 public 的默認構建函數
第二點很明顯,不用多解釋,最經常發生的情況是 Bean 類忘了聲明為 public 。
第三點中需要注意的是,如果你的 Bean 類沒有提供任何構造函數,將自動生成一個默認構建函數,這沒有問題。但是,如果你有構造函數,則不會自動生成該默認構造函數。經常被忽略的問題是寫了默認構造函數卻不是 public 的。
第一點看起來簡單,不過卻最令人頭痛,尤其是在開發環境里。這里需要注意的是,"在編譯 JSP 時",意味著引用 Bean 的 JSP 是新的,或者剛剛更新過,或者 TOMCAT_HOME/work 中的編譯結果被清除了。此時,Tomcat 將自動(重新)編譯該 JSP,此時如果發現 Bean 沒找到,就會報這個錯。情況因為 JSP 或者 Bean 類正在開發而變得復雜,一一列舉所有情況沒有意義,這里我舉一些典型例子,借此應該可以舉一反三:
如果 JSP 編譯結果存在,且 JSP 沒有更新,Tomcat 不會重新編譯 JSP,同時默認情況也不會自動重新加載更新過的 Bean 類(參考 http://jakarta.apache.org/tomcat/tomcat-5.5-doc/config/context.html 中的 reloadable)。所以,你會發現此時即使刪除了 Bean 類都沒有問題,當然,更新 Bean 類也不會有什么用。如果在 JSP 編譯產生之后,我們重起了服務器,由于 JSP 文件編譯的結果存在,所以,可以仍然訪問 JSP 頁面,而不必重新編譯。可是如果訪問前,刪掉了 Bean 類,就會報過 ClassNotFoundException 而不是上述錯誤。關鍵在于 JSP 是否經過編譯,沒有編譯則沒有找到類報告題目中的編譯錯誤 ,編譯過則是 ClassNotFoundException 運行時實例化錯誤。
還有一個更為特殊的例子。如果 Web 應用在啟動時, WEB-INF/classes 目錄不存在,則在啟動應用后,新建 classes 目錄,動態添加新的類進去是沒有用的,會報告同樣的錯誤。原因是此時的 Tomcat 不會去檢查該目錄,也就不會找到你需要的類。對 WEB-INF/lib 目錄也是同樣。這一點可以參考下面的源代碼(org.apache.catalina.loader.WebappLoader):
// Setting up the class repository (/WEB-INF/classes), if it exists
String classesPath = "/WEB-INF/classes";
DirContext classes = null;
try {
Object object = resources.lookup(classesPath);
if (object instanceof DirContext) {
classes = (DirContext) object;
}
} catch(NamingException e) {
// Silent catch: it's valid that no /WEB-INF/classes collection
// exists
}
if (classes != null) {
File classRepository = null;
String absoluteClassesPath =
servletContext.getRealPath(classesPath);
if (absoluteClassesPath != null) {
classRepository = new File(absoluteClassesPath);
} else {
classRepository = new File(workDir, classesPath);
classRepository.mkdirs();
copyDir(classes, classRepository);
}
if(log.isDebugEnabled())
log.debug(sm.getString("webappLoader.classDeploy", classesPath,
classRepository.getAbsolutePath()));
// Adding the repository to the class loader
classLoader.addRepository(classesPath + "/", classRepository);
loaderRepositories.add(classesPath + "/" );
}
上面最后兩個語句將 "/WEB-INF/classes" 注冊到類裝載器中。如果目錄不存在,則該目錄不會被注冊,即使在運行過程中創建該目錄,Tomcat 目前也沒有機制動態的注冊該目錄(應該是出于效率考慮)。
盡管這個問題的復雜場景可能不一而足,不過解決它的辦法卻很簡單:停止服務器,確認你的 JSP 和 Bean 正確部署,清理掉 TOMCAT_HOME/work 中的內容,重起服務器。 此外,配置動態類加載對開發而言是個不錯的選擇。
本文基于 Tomcat 5.5.9 版本。
--
Stephen Suen(SUNRUJUN)
posted on 2006-06-14 14:25
前方的路 閱讀(798)
評論(0) 編輯 收藏