1、在一般情況下,actionForm是被存儲在一定的scope中(request或session,通過action的scope屬性來配置),當我們在配置時,指定name而不指定attribute,那么指定的name值就作為actionForm存儲在scope中的key值,我們可以在action中通過httpServletRequest.getAttribute("指定的name屬性值")來獲得這個actionForm; 當我們既配置了name又配置了attribute,那么actionForm存儲在scope中的key值就采用attribute屬性指定的值了,這時要通過httpServletRequest.getAttribute("指定的attribute屬性值")來獲得actionForm,此時通過httpServletRequest.getAttribute("指定的name屬性值")是不能獲得actionForm的。
所以,是否配置attribute屬性就決定了actionForm存儲在scope中的key值是采用name,還是采用attribute
2、 在《Programming Jakarta Struts》這本書中的第四章“Configuring the Struts Application”中這樣一段說明來分別闡述這兩
個屬性:(102頁)
++++++++
atribute:
++++++++
The name of the request or session scope attribute under which the form bean for this action can be accessed.
A value is only allowed here if there is a form bean specified in the name attribute. This attribute is
optional and has no default value.
++++++++
name:
++++++++
The name of the form bean, if any, that is associated with this action. This value must be the name attribute
from one of the form-bean elements defined earlier. This attribute is optional and has no default value.
最初看這些真的還是不好區分這兩者。不過在仔細看過struts的源代碼以后,豁然開朗。。。
下面主要對attribute進行解釋,應為沒有人會對name屬性不了解的(呵呵。。。)
解釋:在struts實例化actionform的時候,有兩種情況:如果已經存在,那么從內存中取回;如果第一次實例化,那么創建,并放入內存。
這樣就有一個問題了,struts是根據什么來取回并創建actionform的呢,答案就是attribute的值。讓我們進入struts的源代碼:
/**
*創建或者取回formbean方法
*該方法在:org.apache.struts.util.RequestUtils中
*/
public static Actionform createActionform(
HttpServletRequest request,
ActionMapping mapping,
ModuleConfig moduleConfig,
ActionServlet servlet) {
。。。。
。。。
// Is there a form bean associated with this mapping?
//得到action mapping中attribute的值
String attribute = mapping.getAttribute();
。。。。
。。。。
Actionform instance = null;
HttpSession session = null;
//yes!!就在這里了,把創建以后的actionform放在request或者session里,看到放入的名字了么,就是mapping.getAttribute();
if ("request".equals(mapping.getScope())) {
instance = (Actionform) request.getAttribute(attribute);
} else {
session = request.getSession();
instance = (Actionform) session.getAttribute(attribute);
}
。。。
。。。
}
下面又有一個問題浮出水面:如果我沒有在action mapping中指定attribute呢,那struts 是如何解決的?
答案很簡單,如果單從結果上看,此時struts使用的name的值,為什么呢,看struts源代碼:
/**
* The request-scope or session-scope attribute name under which our
* form bean is accessed, if it is different from the form bean's
* specified <code>name</code>.
*該代碼在:org.apache.struts.config.ActionConfig中
*/
protected String attribute = null;
public String getAttribute() {
//yes!!!!就在這里,看到了吧,如果你沒有設定attribute,那么struts 會把name的值拿過來用。呵呵。。。
if (this.attribute == null) {
return (this.name);
} else {
return (this.attribute);
}
}
public void setAttribute(String attribute) {
if (configured) {
throw new IllegalStateException("Configuration is frozen");
}
this.attribute = attribute;
}
當兩個Web組件之間為轉發關系時,轉發源會將要共享 request范圍內的數據先用setAttribute將數據放入到HttpServletRequest對象中,然后轉發目標通過 getAttribute方法來取得要共享的數據。而MVC中用的就是Web組件之間的轉發啊!真是笨,怎么當時沒有想到呢?
下面整理一下getParameter和getAttribute的區別和各自的使用范圍。
(1)HttpServletRequest類有setAttribute()方法,而沒有setParameter()方法
(2)當兩個Web組件之間為鏈接關系時,被鏈接的組件通過getParameter()方法來獲得請求參數,例如假定welcome.jsp和authenticate.jsp之間為鏈接關系,welcome.jsp中有以下代碼:
<a href="authenticate.jsp?username=wolf">authenticate.jsp </a>
或者:
<form name="form1" method="post" action="authenticate.jsp">
請輸入用戶姓名:<input type="text" name="username">
<input type="submit" name="Submit" value="提交">
</form>
在authenticate.jsp中通過request.getParameter("username")方法來獲得請求參數username:
<% String username=request.getParameter("username"); %>
(3)當兩個Web組件之間為轉發關系時,轉發目標組件通過getAttribute()方法來和轉發源組件共享request范圍內的數據。
假定 authenticate.jsp和hello.jsp之間為轉發關系。authenticate.jsp希望向hello.jsp傳遞當前的用戶名字, 如何傳遞這一數據呢?先在authenticate.jsp中調用setAttribute()方法:
<%
String username=request.getParameter("username");
request.setAttribute("username",username);
%>
<jsp:forward page="hello.jsp" />
在hello.jsp中通過getAttribute()方法獲得用戶名字:
<% String username=(String)request.getAttribute("username"); %>
Hello: <%=username %>
從更深的層次考慮,request.getParameter()方法傳遞的數據,會從Web客戶端傳到Web服務器端,代表HTTP請求數據。request.getParameter()方法返回String類型的數據。
request.setAttribute()和getAttribute()方法傳遞的數據只會存在于Web容器內部,在具有轉發關系的Web組件之間共享。這兩個方法能夠設置Object類型的共享數據。
request.getParameter()取得是通過容器的實現來取得通過類似post,get等方式傳入的數據。
request.setAttribute()和getAttribute()只是在web容器內部流轉,僅僅是請求處理階段。
getAttribute是返回對象,getParameter返回字符串
總的來說:request.getAttribute()方法返回request范圍內存在的對象,而request.getParameter()方法是獲取http提交過來的數據。
<%@page
language="java"
contentType="text/html;charset=GBK"
%>
<html>
<head>
<title>MyHtml.html</title>
<meta http-equiv="content-type" content="text/html; charset=gbk">
<!--<link rel="stylesheet" type="text/css" href="./styles.css">-->
<script language="javascript">
function JM_PowerList(colNum)
{
headEventObject=event.srcElement;//取得引發事件的對象
while(headEventObject.tagName!="TR") //不是tr行,則從底下的td冒泡上來尋找到相應行
{
headEventObject=headEventObject.parentElement;
}
for (i=0;i<headEventObject.children.length;i++)
{ alert(headEventObject.children[i].tagName);
if (headEventObject.children[i]!=event.srcElement)//找到事件發生的td單元格
{
headEventObject.children[i].className='listTableHead';//把點擊的列的className屬性設為listTableHead
}
}
var tableRows=0;
trObject=clearStart.children[0].children; //取得表格中行對象, 原來這里叫DataTable, 可能是你寫錯了吧??
for (i=0;i<trObject.length;i++)
{
Object=clearStart.children[0].children[i];//取得每行的對象
tableRows=(trObject[i].id=='ignore')?tableRows:tableRows+1;//如果不是忽略行,則行數加一
}
var trinnerHTML=new Array(tableRows);
var tdinnerHTML=new Array(tableRows);
var tdNumber=new Array(tableRows)
var i0=0
var i1=0
for (i=0;i<trObject.length;i++)
{
if (trObject[i].id!='ignore')
{
trinnerHTML[i0]=trObject[i].innerHTML;//把行放在數組里
tdinnerHTML[i0]=trObject[i].children[colNum].innerHTML;//把要排序的行中td的內容放數組里
tdNumber[i0]=i;//行號
i0++;//加一,下個循環用
}
}
sourceHTML=clearStart.children[0].outerHTML;//取得表格中所有tr的html代碼
//對所有td中的字符串進行排序, 算不算冒泡排序???
for (bi=0;bi<tableRows;bi++)
{
for (i=0;i<tableRows;i++)
{
if(tdinnerHTML[i]>tdinnerHTML[i+1])
{
t_s=tdNumber[i+1];
t_b=tdNumber[i];
tdNumber[i+1]=t_b;
tdNumber[i]=t_s;
temp_small=tdinnerHTML[i+1];
temp_big=tdinnerHTML[i];
tdinnerHTML[i+1]=temp_big;
tdinnerHTML[i]=temp_small;
}
}
}
var showshow='';
var numshow='';
for (i=0;i<tableRows;i++)
{
showshow=showshow+tdinnerHTML[i]+' ';//把排序好的td的內容存在showshow字串里
numshow=numshow+tdNumber[i]+'|'; //把排序好的相應的行號也存在numshow中
}
sourceHTML_head=sourceHTML.split("<TBODY>");//從<TBODY>截斷,我試了,前頭串為空
numshow=numshow.split("|");
var trRebuildHTML='';
if (event.srcElement.className=='listHeadClicked')
{//已點擊的列, 則逆排
for (i=0;i<tableRows;i++)
{
trRebuildHTML=trRebuildHTML+trObject[numshow[tableRows-1-i]].outerHTML;//取出排序好的tr的內容連接起來
}
event.srcElement.className='listHeadClicked0';
}
else
{//默認順排,新點擊順排
for (i=0;i<tableRows;i++)
{
trRebuildHTML=trRebuildHTML+trObject[numshow[i]].outerHTML;
}
event.srcElement.className='listHeadClicked';
}
//取得排序后的tr集合結果字符串
var DataRebuildTable='';
//把舊的表格頭和新的tr排序好的元素連接起來, (修改了一下)
DataRebuildTable = "<table border=1 width=100% cellpadding=1 cellspacing=1 id='clearStart'><TBODY>"
+ trObject[0].outerHTML + trRebuildHTML + "</TBODY>" + "</table>";
clearStart.outerHTML=DataRebuildTable;//表格用新串重新寫一次
}
</script>
</head>
<table border=1 id="clearStart">
<tr bgcolor=cccccc id='ignore'>
<td onclick="JM_PowerList(0)">列一
</td>
<td onclick="JM_PowerList(1)">
列二
</td>
<td onclick="JM_PowerList(2)">
列二
</td>
</tr>
<tr>
<td>
周
</td>
<td>
公務員
</td>
<td>
22
</td>
</tr>
<tr>
<td>
張三
</td>
<td>
研究員
</td>
<td>
65
</td>
</tr>
<tr>
<td>
李思
</td>
<td>
科學家
</td>
<td>
24
</td>
</tr>
<tr>
<td>
王武
</td>
<td>
社會學家
</td>
<td>
38
</td>
</tr>
</table>
</body></html>
在html中如何使一個button或text控件不可見?
<INPUT type="button" value="Button" id=button1 name=button1 disabled>
<INPUT type="text" id=text1 name=text1 readonly>
<INPUT type="hidden" value="Button" id=button1 name=button1 >
<INPUT type="text" id=text1 name=text1 style="visibility:hidden">
<INPUT type="button" value="Button" id=button1 name=button1 style="visibility:hidden">
可以在jsp中做如下控制:
<INPUT TYPE="button" NAME="xzdq" value="<<" onClick="selectDept()" >
<script language="javascript">
if('<%=userId%>'!= null)document.all.xzdq.style.visibility="hidden";
</script>
或者:
<INPUT TYPE="button" NAME="xzdq" value="<<" onClick="selectDept()" >
<%
if(userId != null)
{
%>
<script language="javascript">
document.all.xzdq.style.visibility="hidden";
</script>
<%
}
%>
或者:
<INPUT TYPE="button" NAME="xzdq" value="<<" onClick="selectDept()" >
<%
if(userId != null)
out.println("<script language=\"javascript\"> document.all.xzdq.style.visibility=\"hidden\";</script>");
%>
安裝Tomcat之前要先安裝JDK,可從http://java.sun.com上下載最新版本的JDK。Tomcat可從Apache Jakarta Project站點(http://jakarta.apache.org/site/binindex.cgi)上下載,本書使用的Tomcat版本是5.5.7,它需要安裝J2SE 5.0(JDK 1.5)以上的版本才能運行。對于Windows操作系統,Tomcat 5.5.7提供了兩種安裝文件,一種是jakarta-tomcat-5.5.7.exe,一種是jakarta-tomcat-5.5.7.zip(如果讀者使用的是Linux系統,請下載jakarta-tomcat-5.5.7.tar.gz)。jakarta-tomcat-5.5.7.exe是可執行的安裝程序,讀者只需要雙擊這個文件,就可以開始安裝Tomcat了。在安裝過程中,安裝程序會自動搜尋JDK和JRE的位置。安裝完成后,在Windows系統的“開始”->“程序”菜單下會添加Apache Tomcat 5.5菜單組。jakarta-tomcat-5.5.7.zip是一個壓縮包,只需要將它解壓到硬盤上就可以了。在這里,我建議讀者下載jakarta-tomcat-5.5.7.zip壓縮包,通過解壓縮的方式安裝Tomcat,因為解壓縮的方式也適用于其他的操作系統,例如Linux系統。下面我們主要介紹jakarta-tomcat-5.5.7.zip的安裝與Tomcat運行環境的設置。
安裝Tomcat
使用WinZip或WinRAR等解壓縮工具將jakarta-tomcat-5.5.7.zip解壓到指定的驅動器和目錄中。筆者是在D盤上直接解壓,產生了目錄jakarta-tomcat-5.5.7,解壓后的文件存放于D:\ jakarta-tomcat-5.5.7下。
Tomcat安裝后的目錄層次結構如圖5-2所示。

圖5-2 Tomcat 5.5.7目錄層次結構
各目錄的用途如表5-1所示。
表5-1 Tomcat的目錄結構及其用途
目 錄
|
用 途
|
/bin
|
存放啟動和關閉Tomcat的腳本文件
|
/common/lib
|
存放Tomcat服務器及所有Web應用程序都可以訪問的JAR文件
|
/conf
|
存放Tomcat服務器的各種配置文件,其中包括server.xml(Tomcat的主要配置文件)、tomcat-users.xml和web.xml等配置文件
|
/logs
|
存放Tomcat的日志文件
|
/server/lib
|
存放Tomcat服務器運行所需的各種JAR文件
|
/server/webapps
|
存放Tomcat的兩個Web應用程序:admin應用程序和manager應用程序
|
/shared/lib
|
存放所有Web應用程序都可以訪問的JAR文件
|
/temp
|
存放Tomcat運行時產生的臨時文件
|
/webapps
|
當發布Web應用程序時,通常把Web應用程序的目錄及文件放到這個目錄下
|
/work
|
Tomcat將JSP生成的Servlet源文件和字節碼文件放到這個目錄下
|
從表5-1中可以看到,/common/lib目錄、/server/lib和/shared/lib目錄下都可以存放JAR文件,它們的區別在于:
— 在/server/lib目錄下的JAR文件只能被Tomcat服務器訪問;
— 在/shared/lib目錄下的JAR文件可以被所有的Web應用程序訪問,但不能被Tomcat服務器訪問;
— 在/common/lib目錄下的JAR文件可以被Tomcat服務器和所有的Web應用程序訪問。
此外,對于后面將要介紹的Java Web應用程序,在它的WEB-INF目錄下,也可以建立lib子目錄,在lib子目錄下可以存放各種JAR文件,這些JAR文件只能被當前Web應用程序所訪問。
運行Tomcat
在Tomcat安裝目錄下的bin子目錄中,有一些批處理文件(以.bat作為后綴名的文件),其中的startup.bat就是啟動Tomcat的腳本文件,用鼠標雙擊這個文件,將會看到如圖5-3所示的畫面。

圖5-3 運行Tomcat提示出錯信息
筆者以前碰到過很多學員,在初次運行Tomcat時,看到如圖5-3所示的信息就不知所措了。有的學員以前還配置過Tomcat,但是再次使用的時候,由于忘記了上次是如何配置的,同樣感覺無從下手。
我們在學習軟件開發時,一定要養成查看錯誤提示信息,進而根據錯誤提示解決問題的良好習慣。筆者第一次配置Tomcat時,就是根據錯誤提示信息一步一步配置成功的。很多人一看見錯誤信息,立即單擊“確定”按鈕,這樣就錯過了提示信息。當看到錯誤信息時,首先不要慌張和無所適從,仔細看清楚錯誤提示,不要著急單擊按鈕。
查看圖5-3中的錯誤提示信息,可以看到這樣一句話“The JAVA_HOME environment variable is not defined”,從畫面中可以看到,在執行到“Using JAVA_HOME”這句時出現了錯誤,由此,我們可以想到,出錯的原因可能是因為沒有設置JAVA_HOME環境變量。那么JAVA_HOME環境變量的值應該是什么呢?很容易就能想到應該是JDK所在的目錄,在筆者的機器上,JDK所在的目錄是D:\Java\jdk1.5.0_01。
在Windows 2000操作系統下設置環境變量的步驟如下。
① 在桌面“我的電腦”上單擊右鍵,選擇“屬性”,出現如圖5-4所示的畫面。

圖5-4 “我的電腦”屬性
② 單擊“高級”選項卡,選擇“環境變量(E)…”,如圖5-5和圖5-6所示。


圖5-5 “高級”選項卡 圖5-6 “環境變量”對話框
③ 在“系統變量”下方單擊“新建”按鈕。在“變量名”中輸入“JAVA_HOME”,在變量值中輸入JDK所在的目錄“D:\Java\jdk1.5.0_01”,然后單擊“確定”按鈕,如圖5-7所示。

圖5-7 新建JAVA_HOME環境變量
④ 最后在“環境變量”對話框上單擊“確定”按鈕,結束JAVA_HOME環境變量的設置。
我們再一次轉到D:\ jakarta-tomcat-5.5.7\bin目錄下,用鼠標雙擊startup.bat文件,可以看到如圖5-8所示的啟動信息。

圖5-8 Tomcat啟動信息
然后,打開瀏覽器,在地址欄中輸入http://localhost:8080/(localhost表示本地機器,8080是Tomcat默認監聽的端口號),將出現如圖5-9所示的Tomcat頁面。

圖5-9 Tomcat的默認主頁
注意圖5-9中鼠標(小手形狀)指向的鏈接——Tomcat Documentation,單擊將進入Tomcat的文檔頁面,有關Tomcat的幫助信息可以在文檔頁面中找到;讀者也可以直接訪問Tomcat的文檔,文檔首頁的位置是Tomcat安裝目錄下的webapps\tomcat-docs\index.html。如果要關閉Tomcat服務器,可以用鼠標雙擊D:\ jakarta-tomcat-5.5.7\bin目錄下的shutdown.bat文件。
如果你機器上的Tomcat啟動失敗,有可能是因為TCP的8080端口被其他應用程序所占用,如果你知道是哪一個應用程序占用了8080端口,那么先關閉此程序。如果你不知道或者不想關閉占用8080端口的應用程序,你可以修改Tomcat默認監聽的端口號。
前面介紹了,Tomcat安裝目錄下的conf子目錄用于存放Tomcat服務器的各種配置文件,其中的server.xml是Tomcat的主要配置文件,這是一個格式良好的XML文檔,在這個文件中可以修改Tomcat默認監聽的端口號。用UltraEdit(你可以用記事本程序或其他的文本編輯工具)打開server.xml,找到修改8080端口的地方。讀者也許要問了,“這個配置文件,我都不熟悉,怎么知道在哪里修改端口號呢?”對于初次接觸server.xml的讀者,確實不了解這個文件的結構,但是我們應該有一種開放的思路,既然Tomcat的監聽端口號是在server.xml中配置,那么只要我們在這個文件中查找“8080”這些數字字符序列,不就能找到修改端口號的地方了嗎!在UltraEdit中,同時按下鍵盤上的“Ctrl”和“F”鍵,出現如圖5-10所示的查找對話框。

圖5-10 UltraEdit查找對話框
然后在“查找內容”中輸入“8080”,單擊“查找下一個”按鈕。重復這個過程,直到找到如圖5-11所示的在server.xml中配置端口號位置。

圖5-11 server.xml中配置端口號的位置
找到后,如果我們不能確定此處就是修改端口號的地方,也沒有關系,可以先嘗試著修改一下端口號,然后啟動Tomcat,如果啟動成功,也就證明了我們修改的地方是正確的。學習時,我們應該養成這種探索并不斷實驗的精神。在這里,我們可以修改端口號為8000(讀者可以根據自己機器的配置選擇一個端口號),然后保存。再次啟動Tomcat,在Tomcat啟動完畢后,打開瀏覽器,在地址欄中輸入http://localhost:8000/(讀者根據自己設置的端口號做相應的修改),就可以看到Tomcat的默認主頁了。關閉Tomcat服務器時,執行bin目錄下的shutdown.bat文件。
Tomcat啟動分析
在本節中我們將通過對Tomcat啟動過程的分析,來幫助讀者更好地理解和掌握Tomcat。
用文本編輯工具打開用于啟動Tomcat的批處理文件startup.bat,仔細閱讀,可以發現,在這個文件中,首先判斷CATALINA_HOME環境變量是否為空,如果為空,就將當前目錄設為CATALINA_HOME的值,接著判斷當前目錄下是否存在bin\catalina.bat,如果文件不存在,將當前目錄的父目錄設為CATALINA_HOME的值,根據筆者機器上Tomcat安裝目錄的層次結構,最后CATALINA_HOME的值被設為Tomcat的安裝目錄。如果環境變量CATALINA_HOME已經存在,則通過這個環境變量調用bin目錄下的“catalina.bat start”命令。通過這段分析,我們了解到兩個信息,一是Tomcat啟動時,需要查找CATALINA_HOME這個環境變量,如果在當前目錄下調用startup.bat,Tomcat會自動設置CATALINA_HOME;二是執行startup.bat命令,實際上執行的是“catalina.bat start”命令。
如果我們不是在bin目錄作為當前目錄時調用startup.bat,就會出現如圖5-12所示的錯誤信息(在bin目錄的父目錄下調用除外)。

圖5-12 在其他目錄下啟動Tomcat出錯
要在其他目錄下也能啟動Tomcat,就需要設置CATALINA_HOME環境變量,你可以將CATALINA_HOME添加到Windows 2000系統的環境變量中,其值就是Tomcat的安裝目錄,在筆者的機器上安裝目錄是D:\jakarta-tomcat-5.5.7,添加環境變量的過程和前述添加JAVA_HOME環境變量的過程是一樣的。如果你不想在系統的環境變量中添加,也可以直接在startup.bat文件中進行設置。下面是在startup.bat文件中設置CATALINA_HOME后的文件片段:
……
rem $Id: shutdown.bat,v 1.5 2004/05/27 15:05:01 yoavs Exp $
rem ---------------------------------------------------------------------------
set CATALINA_HOME=D:\jakarta-tomcat-5.5.7
rem Guess CATALINA_HOME if not defined
set CURRENT_DIR=%cd%
if not "%CATALINA_HOME%" == "" goto gotHome
set CATALINA_HOME=%CURRENT_DIR%
……
注意以粗體顯示的這句話的作用就是設置CATALINA_HOME環境變量。在它的下面就可以判斷CATALINA_HOME是否為空了。如果你找不準位置,干脆將設置CATALINA_HOME環境變量的這句話放置到文件的第一行。JAVA_HOME環境變量也可以采用同樣的方式進行設置。不過,如果你要在其他目錄下,利用shutdown.bat來關閉Tomcat服務器,你也需要在shutdown.bat文件中設置CATALINA_HOME和JAVA_HOME這兩個環境變量,設置變量的位置和startup.bat文件一樣,都是在判斷CATALINA_HOME是否為空之前。當然,為了一勞永逸,避免重裝Tomcat后還要進行設置(需要是同一版本的Tomcat安裝在同一位置),我們最好還是將CATALINA_HOME和JAVA_HOME這兩個環境變量添加到Windows 2000系統的環境變量中。
有的讀者可能會對設置Tomcat安裝目錄的環境變量的名字是CATALINA_HOME而感到奇怪,按照以前設置的環境變量來看,JAVA_HOME表示JDK的安裝目錄,那么應該用TOMCAT_HOME來表示Tomcat的安裝目錄,可為什么要使用CATALINA_HOME呢?實際上,在Tomcat 4以前,用的就是TOMCAT_HOME來表示Tomcat的安裝目錄,在Tomcat 4以后,采用了新的Servlet容器Catalina,所以環境變量的名字也改為了CATALINA_HOME。
提示:在Windows系統下環境變量的名字是與大小寫無關的,也就是說JAVA_HOME和java_home是相同的。
了解了startup.bat文件以后,我們再來看看真正負責啟動Tomcat服務器的catalina.bat文件。通過分析catalina.bat文件,我們發現它還調用了一個文件setclasspath.bat。在setclasspath.bat文件中,它檢查JAVA_HOME環境變量是否存在,并通過設置的環境變量JAVA_HOME,找到java.exe,用于啟動Tomcat。在這個文件中,還設置了其他的一些變量,分別表示JDK中的一些工具,有興趣的讀者可以自行分析一下這個文件。在執行完setclasspath.bat之后,catalina.bat剩下的部分就開始了Tomcat服務器的啟動進程。
直接執行catalina.bat時,需要帶上命令行的參數。讀者可以在命令提示符窗口下,執行catalina.bat,就會打印出catalina.bat命令的各種參數及其含義,如圖5-13所示。

圖5-13 catalina.bat的各參數信息
其中常用的參數是start、run和stop,參數start表示在一個單獨的窗口中啟動Tomcat服務器,參數run表示在當前窗口中啟動Tomcat服務器,參數stop表示關閉Tomcat服務器。我們執行startup.bat,實際上執行的就是“catalina.bat start”命令;執行shutdown.bat,實際上執行的是“catalina.bat stop”命令。“catalina.bat run”命令有時候是非常有用的,特別是當我們需要查看Tomcat的出錯信息時。我們在開發JSP程序時,經常會碰到自己機器上的8080端口號被別的應用程序占用,或者在配置server.xml時出現錯誤,當通過startup.bat(相當于執行“catalina.bat start”)啟動Tomcat服務器時,會導致啟動失敗,因為是在單獨的窗口中啟動Tomcat服務器,所以一旦啟動失敗,命令提示符窗口就自動關閉了,程序運行中輸出的出錯信息也隨之消失,而且沒有任何的日志信息,這就使得我們沒有辦法找出錯誤原因。當出現錯誤時,我們可以換成“catalina.bat run”命令再次啟動,一旦啟動失敗,僅僅是Tomcat服務器異常終止,但是在當前的命令提示符窗口下仍然保留了啟動時的出錯信息,這樣我們就可以查找啟動失敗的原因了。
Tomcat的體系結構
Tomcat服務器是由一系列可配置的組件構成的,其中核心組件是Catalina Servlet容器,它是所有其他Tomcat組件的頂層容器。Tomcat各組件之間的層次關系如圖5-14所示。

圖5-14 Tomcat組件之間的層次結構
我們下面簡單介紹一下各組件在Tomcat服務器中的作用。
(1)Server
Server表示整個的Catalina Servlet容器。Tomcat提供了Server接口的一個默認實現,這通常不需要用戶自己去實現。在Server容器中,可以包含一個或多個Service組件。
(2)Service
Service是存活在Server中的內部組件,它將一個或多個連接器(Connector)組件綁定到一個單獨的引擎(Engine)上。在Server中,可以包含一個或多個Service組件。Service也很少由用戶定制,Tomcat提供了Service接口的默認實現,而這種實現既簡單又能滿足應用。
(3)Connector
連接器(Connector)處理與客戶端的通信,它負責接收客戶請求,以及向客戶返回響應結果。在Tomcat中,有多個連接器可以使用。
(4)Engine
在Tomcat中,每個Service只能包含一個Servlet引擎(Engine)。引擎表示一個特定的Service的請求處理流水線。作為一個Service可以有多個連接器,引擎從連接器接收和處理所有的請求,將響應返回給適合的連接器,通過連接器傳輸給用戶。用戶可以通過實現Engine接口提供自定義的引擎,但通常不需要這么做。
(5)Host
Host表示一個虛擬主機,一個引擎可以包含多個Host。用戶通常不需要創建自定義的Host,因為Tomcat給出的Host接口的實現(類StandardHost)提供了重要的附加功能。
(6)Context
一個Contex表示了一個Web應用程序,運行在特定的虛擬主機中。什么是Web應用程序呢?在Sun公司發布的Java Servlet規范中,對Web應用程序做出了如下的定義:“一個Web應用程序是由一組Servlet、HTML頁面、類,以及其他的資源組成的運行在Web服務器上的完整的應用程序。它可以在多個供應商提供的實現了Servlet規范的Web容器中運行”。一個Host可以包含多個Context(代表Web應用程序),每一個Context都有一個惟一的路徑。用戶通常不需要創建自定義的Context,因為Tomcat給出的Context接口的實現(類StandardContext)提供了重要的附加功能。
下面我們通過圖5-15來幫助讀者更好地理解Tomcat服務器中各組件的工作流程。

圖5-15 Tomcat各組件的工作流程
要了解這些組件的其他信息,可以看下面的頁面:
%CATALINA_HOME%\webapps\tomcat-docs\architecture\index.html
我們可以在conf目錄下的server.xml文件中對這些組件進行配置,讀者打開server.xml文件,就可以看到元素名和元素之間的嵌套關系,與Tomcat服務器的組件是一一對應的,server.xml文件的根元素就是server。關于server.xml配置文件中的各元素及其屬性的含義,請參見附錄C。
在Tomcat中,提供了各組件的接口及其實現類,如果你要替代Tomcat中的某個組件,只需要根據該組件的接口或類的說明,重寫該組件,并進行配置即可。圖5-16是Tomcat各組件的類圖。

在類圖的接口名或類名下面是該接口或該類所在的包,這些接口和類都在%CATALINA_HOME%\ server\lib\catalina.jar文件中。對Tomcat服務器的實現感興趣的讀者,可以從http://tomcat.apache.org/上下載Tomcat的源代碼。
提示:由于Apache軟件基金會并不是一個商業性的組織,所以文檔更新的速度有時候跟不上版本更新的速度。在Tomcat 5.5.7中,就可以發現文檔與其源碼實現有不一致的地方。在Tomcat 5.5.x中,去掉了org.apache.catalina.Connector接口及其相關的實現類,而直接以org.apache.catalina.connector.Connector類來代替。我們在看Tomcat的文檔時,最好結合其API文檔一起看,這樣才能保證了解的信息是完整的和準確的。
Tomcat提供了兩個管理程序:admin和manager。其中admin用于管理和配置Tomcat服務器,manager用于管理部署到Tomcat服務器中的Web應用程序。
admin Web應用程序
admin Web應用程序需要單獨下載,與Tomcat在同一個下載頁面,鏈接名是Admin zip,下載后的文件名是jakarta-tomcat-5.5.7-admin.zip,解壓縮后,覆蓋Tomcat安裝目錄下的同名目錄。admin Web應用程序位于%CATALINA_HOME%\server\webapps\admin目錄下。
要訪問admin Web應用程序,需要添加具有管理員權限的賬號,編輯%CATALINA_HOME%\ conf\tomcat-users.xml文件,在<tomcat-users>元素中添加如下內容:
<user username="admin" password="12345678" roles="admin"/>
其中用戶名和密碼可以根據自己的喜好設置。
啟動Tomcat服務器,打開瀏覽器,在地址欄中輸入:
http://localhost:8080/admin/
將出現如圖5-17所示的頁面。

圖5-17 admin Web應用程序的登錄界面
也可以在Tomcat的默認主頁的左上方單擊“Tomcat Administration”鏈接,進入admin登錄頁面。輸入用戶名admin,密碼12345678,單擊“Login”按鈕,將看到如圖5-18所示的頁面。

圖5-18 admin Web應用程序的主頁面
在這個頁面中,可以進行Tomcat服務器的各項配置。
5.6.2 manager Web應用程序
manager Web應用程序包含在Tomcat的安裝包中。和admin程序一樣,需要添加訪問manager Web應用程序的管理員賬號,編輯%CATALINA_HOME%\conf\tomcat-users.xml文件,在<tomcat-users>元素中添加如下內容:
<user username="manager" password="12345678" roles="manager"/>
其中用戶名和密碼可以根據自己的喜好設置。
啟動Tomcat服務器,打開瀏覽器,在地址欄中輸入:
http://localhost:8080/manager/html/
將出現如圖5-19所示的頁面。

圖5-19 manager Web應用程序的登錄界面
也可以在Tomcat的默認主頁的左上方單擊“Tomcat Manager”鏈接,訪問manager程序。輸入用戶名manager,密碼12345678,單擊“確定”按鈕,將看到如圖5-20所示的頁面。

圖5-20 manager Web應用程序的主頁面
在這個頁面中,你可以部署、啟動、停止、重新加載、卸載Web應用程序。注意在兩個圓角矩形框中的路徑“/jsp-examples”和“/servlets-examples”,單擊這兩個路徑,將看到Tomcat提供的JSP和Servlet的例子程序,這些程序可以作為學習JSP和Servlet的參考。不過在這兩個路徑下,只列出了部分的例子程序,完整的JSP和Servlet例子程序位于下面的兩個目錄中:
%CATALINA_HOME%\webapps\jsp-examples
%CATALINA_HOME%\webapps\servlets-examples
關于JSP中的錯誤頁面處理
通常JSP 在執行時,在兩個階段下會發生錯誤。
JSP 網頁 → Servlet 類
Servlet 類處理每一個請求時
在第一階段時,產生的錯誤我們稱為Translation Time Processing Errors;在第二階段時,產生的錯誤我們稱為Client Request Time Processing Errors。接下來我們針對這兩個階段產生錯誤的原因和處理方法,進行介紹。
1、 Translation Time Processing Errors
Translation Time Processing Errors 產生的主要原因:我們在撰寫JSP時的語法有錯誤,導致JSP Container 無法將JSP 網頁編譯成Servlet 類文件( .class),例如:500 Internal Server
Error,500 是指HTTP 的錯誤狀態碼,因此是Server Error。
通常產生這種錯誤時,可能是JSP 的語法有錯誤,或是JSP Container 在一開始安裝、設定時,有不適當的情形發生。解決的方法就是再一次檢查程序是否有寫錯的,不然也有可能是JSPContainer 的bug。
2、 Client Request Time Processing Errors
Client Request Time Processing Errors 錯誤的發生,往往不是語法錯誤,而可能是邏輯上的錯誤,簡單地說,你寫一個計算除法的程序,當用戶輸入的分母為零時,程序會發生錯誤并拋出異常(Exception),交由異常處理(Exception Handling)機制做適當的處理。對于這種錯誤的處理,我們通常會交給errorPage 去處理。下面舉個例子:
使用errorPage 的范例程序 :ErrorPage.jsp
<%@ page contentType="text/html;charset=GB2312" errorPage="Error.jsp" %> //設置Error.jsp頁為本頁的錯誤處理頁
<html>
<head>
<title>CH4 - ErrorPage.jsp</title>
</head>
<body>
<h2>errorPage 的范例程序</h2>
<%!
private double toDouble(String value)
{
return(Double.valueOf(value).doubleValue());
}
%>
<%
double num1 = toDouble(request.getParameter("num1"));
double num2 = toDouble(request.getParameter("num2"));
%>
您傳入的兩個數字為:<%= num1 %> 和 <%= num2 %><br>
兩數相加為 <%= (num1+num2) %>
</body>
</html>
ErrorPage.jsp 程序中,我們使用page 指令中的errorPage 屬性,告訴JSP Container,如果在程序中有錯誤產生時(指的是servlet運行時產生的錯誤或顯示用throw拋出的異常),會自動交給Error.jsp 處理。
Error.jsp
<%@ page contentType="text/html;charset=GB2312" isErrorPage="true" %> //設置該頁為錯誤處理頁
<%@ page import="java.io.PrintWriter" %>
<html>
<head>
<title>CH4 - Error.jsp</title>
</head>
<body>
<h2>errorPage 的范例程序</h2>
<p>ErrorPage.jsp 錯誤產生:<I><%= exception %></I></p><br>
<pre>
問題如下:<% exception.printStackTrace(new PrintWriter(out)); %> //輸出錯誤的原因
</pre>
</body>
</html>
Error.jsp 主要處理ErrorPage.jsp 所產生的錯誤,所以在ErrorPage.jsp 中page 指令的屬性errorPage設為Error.jsp,因此,若ErrorPage.jsp有錯誤發生時,會自動轉到Error.jsp來處理。Error.jsp 必須設定page 指令的屬性isErrorPage為true,因為Error.jsp是專門用來處理錯誤的網頁。設定page 指令的屬性isErrorPage為true后,在Error.jsp里就可以使用exception異常類了。
由于在這個程序中并沒有做一個窗體來輸入兩個數字,所以必須手動在URL后輸入num1和num2的值,如http://localhost:8080/ErrorPage.jsp?num1=100&num2=245。當ErrorPage.jsp 產生錯誤時(如加數為字符串型),就會交由Error.jsp 去處理,所以我們看到的結果,不
再是原始的服務器提示的亂七把糟的錯誤提示,將是執行Error.jsp 后的結果。
在jsp中顯式地拋出異常時,系統也會轉到錯誤處理頁面:
<%@page language="java" contentType="text/html;charset=GBK" import= "java.util.* " errorPage="exception.jsp"
%>
<jsp:useBean id="user" scope="session" class="S_userObj"/>
<%
if(user= =null)
throw new Exception("您沒有登陸或者登陸超時,請重新登陸");
。。。 。。。
當user為空時系統會自動轉到錯誤處理頁面。
總之,以下情況jsp會轉到錯誤處理頁面:
前提:jsp成功的轉換到了servlet,轉換過程中沒有發生錯誤。
當servlet運行時,程序中碰到異常。
當servlet運行時,程序控制轉到了顯式拋出的異常。例如:if(…) throw new exception();
java繼承中對構造函數是不繼承的,只是調用(隱式或顯式)。
以下是例子:
public class FatherClass {
public FatherClass() {
System.out.println(100);
}
public FatherClass(int age) {
System.out.println(age);
}
}
public class SonClass extends FatherClass{
public SonClass() {
}
public SonClass(int c) {
System.out.println(1234);
}
public static void main(String[] args) {
SonClass s = new SonClass(66);
}
}
編譯后執行結果如下是什么呢?
分析:SonClass s = new SonClass(66);執行這句時,調用
public SonClass(int c) {
System.out.println(1234);//系統會自動先調用父類的無參構造函數(super())
}
在這個構造函數中,等價于
public SonClass(int c) {
super();//必須是第1行,否則不能編譯
System.out.println(1234);
}
所以結果是 100
1234
3.如果子類構造函數是這樣寫的
public SonClass(int c) {
super(22);//必須是第1行,否則不能編譯
//顯式調用了super后,系統就不再調用無參的super()了;
System.out.println(1234);
}
執行結果是 22
1234
總結1:構造函數不能繼承,只是調用而已。
如果父類沒有無參構造函數
創建子類時,不能編譯,除非在構造函數代碼體中第一行,必須是第一行顯式調用父類有參構造函數
如下:
SonClass (){
super(777);//顯示調用父類有參構造函數
System.out.println(66);
}
如果不顯示調用父類有參構造函數,系統會默認調用父類無參構造函數super();
但是父類中沒有無參構造函數,那它不是不能調用了。所以編譯就無法通過了。
總結2:創建有參構造函數后,系統就不再有默認無參構造函數。
如果沒有任何構造函數,系統會默認有一個無參構造函數。