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();