Java Web应用
概述
Tomcat是符合Servlet规范的优秀Servlet容器。Java Web应用运行在Servlet容器中,Servlet容器能够动态调用Java Web应用中的Servlet。
下面以一个简单的helloapp应用为例,分享Java Web应用实操经验。在这个例子中将分享一下内容:
- Java Web应用的基本组成内容和目录结构。
- 在web.xml中配置Servlet。
- 在Tomcat中发布Java Web应用。
- 配置Tomcat的虚拟主机
- 创建、发布和使用自定义JSP标签。
Java Web应用简介
Oracle公司的Servlet规范对Java Web应用做了这样的定义:“Java Web应用由一组Servlet/JSP、HTML文件、相关Java类,以及其他可以被绑定的资源构成。它可以在各种由供应商提供符合Servlet规范的Servlet容器中运行。”
从Java Web应用的定义可以看出,Java Web应用不仅可以在Tomcat中运行,还可以在其他符合Servlet规范的Servlet容器中运行。在Java Web应用中可以包含如下内容:
-
Servlet组件
标准Servlet接口的实现类,运行在服务器,包含了被Servlet容器动态调用的程序代码。 -
JSP组件
包含Java程序代码的HTML文档。运行在服务器端,当客户端请求访问JSP文件时,Servlet容器先把它编译成Servlet类,然后动态调用它的程序代码。 -
相关的Java类
开发人员自定义的与Web应用相关的Java类。 -
静态文档
存放在服务器端的文件系统中,如HTML文件、图片文件和声音文件等。当客户端请求访问这些文件时,Servlet容器从本地文件系统中读取这些文件的数据,再把它发送到客户端。 -
客户端脚本程序
是由客户端来运行的程序,JavaScript是典型的客户端脚本程序。 -
web.xml文件
Java Web应用的配置文件,该文件采用XML格式。该文件必须位于Web应用的WEB-INF目录下。
提示
Servlet容器能够运行Java Web应用,而Java Web应用的最主要的组件就是Servlet和JSP。因此Servlet容器也被成为Jave Web容器或者Servlet/JSP容器。
创建Java Web应用
Java Web应用中可以包含HTML文档、Servlet、JSP和相关Java类等。为了让Servlet容器能顺利地找到Java Web应用中的各个组件,Servlet规范规定,Java Web应用必须是固定的目录结构,每种类型的组件在Web应用中都有固定的存放目录。Servlet规范还规定Java Web应用的配置信息存放在WEB-INF/web.xml文件中,Servlet容器从该文件中读取配置信息。在发布某些Web组件(如Servlet)时,需要在web.xml文件中添加相应的关于这些Web组件的配置信息。
Java Web应用的目录结构
Java Web应用具有固定的目录结构,假定开发一个名为helloapp的Java Web应用。首先,应该创建这个Web应用的目录结构,参见下表。
目录 | 描述 |
---|---|
/helloapp | Web应用的根目录,所有的JSP和HTML文件都存放于此目录或子目录下 |
/helloapp/WEB-INF | 存放Web应用的配置文件web.xml |
/helloapp/WEB-INF/classes | 存放各种.class文件,Servlet类的.class文件也放于此目录下 |
/helloapp/WEB-INF/lib | 存放Web应用所需的各种JAR文件(类库文件)。例如,在这个目录下可以存放JDBC驱动程序的JAR文件。 |
从上表可以看出,在WEB-INF目录的classes以及lib子目录下,都可以存放Java类文件。在运行时,Servlet容器的类加载器先加载classes目录下的类,再加载lib目录下的JAR文件(Java类库的打包文件)中的类。因此,如果两个目录下存在同名的类,classes目录下的类具有优先权。另外,浏览器端不可以直接请求访问WEB-INF目录下的文件,这些文件只能被服务器端的组件访问。
在此例中介绍的helloapp应用的完整目录结构如下图所示。
上图中有一个src目录,这是在开发helloapp应用阶段,开发者自定义的目录,该目录用于存放所有Java类的源文件。代理Web应用产品正式发布阶段,一般都不希望对外公开Java源码,所以届时应该将src目录转移到其他地方。
在helloapp应用中包含如下组件。
- HTML组件:login.html
- Servlet组件:DispatcherServlet类
- JSP组件:hello.jsp
这些组件之间的关系如下图所示。login.html与DispatcherServlet类之间的超级链接关系,DispatcherServlet类与hello.jsp之间为请求转发关系。
目录结构图还包含HelloTag.java、mytaglib.tld和HelloTag.class文件,这些文件用于创建、发布和使用自定义JSP标签。
创建HTML文件
在helloapp目录下加入login.html文件,它包含一个名为"loginForm"的登陆表单,要求客户输入用户名和口令。当用户提交表单,将由URI为“dispathcher”的servlet来处理,这个Servlet的Java类名为helloapp.DispathcherServle。以下是login.html的例程代码。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>helloapp</title>
</head>
<body>
<form name="loginForm" method="POST" action="dispatcher">
<table>
<tr>
<td><div align="right">User Name:</div></td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td><div align="right">Password:</div></td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td><input type="submit" name="submit" value="submit"></td>
<td><input type="reset" name="reset" value="reset"></td>
</tr>
</table>
</form>
</body>
</html>
访问login.html的URL为http://localhost/helloapp/login.html,该页面的显示结果如下图所示。
创建Servlet类
下面创建DispathcherServlet类,它调用ServletRequest对像的getParameter()方法读取客户提交的login.html表单数据,获取用户名和口令,然后将用户名和口令作为属性保存在ServletRequest对象中,再把请求转发给hello.jsp。
package helloapp;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.*;
import java.util.*;
public class DispatcherServlet extends GenericServlet{
private String target = "/hello.jsp";
//响应客户请求
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException
{
//读取表单中的用户名
String username = request.getParameter("username");
//读取表单中的口令
String password = request.getParameter("password");
//在request中添加USER属性
request.setAttribute("USER", username);
//在request中添加PASSWORD属性
request.setAttribute("PASSWORD", password);
//把请求转发给hello.jsp
ServletContext context = getServletContext();
RequestDispatcher dispatcher = context.getRequestDispatcher(target);
dispatcher.forward(request, response);
}
}
编译servlet时,需要将Servlet API的JAR文件(servlet-api.jar)加入到classpath中。servlet-api.jar文件位于<CATALINA-HOME>/lib目录下。此外,从Oracle的官方网址(http://www.oracle.com/technetwork/java/index.html)也可以下载该文件。
创建好DispathcherServlet后,还必须在web.xml文件中对其进行配置,这样客户端才能访问Servlet。
创建JSP文件
下面创建hello.jsp,hello.jsp文件放在helloapp根目录下。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>helloapp</title>
</head>
<body>
<b>hello: <%= request.getAttribute("USER") %></b>
</body>
</html>
hello.jsp和HTML文档看上去很相似,仅仅在一处地方使用了JSP语法:
<%= request.getAttribute("USER") %>
以上代码的作用是从request(即ServletRequest)对像中获得USER属性值,然后输出该USER属性值。我们在上面的创建Servlet类小节介绍的DispatcherServlet现在request对象中设置了USER属性,再把请求转发给hello.jsp,所以接下来hello.jsp就能从request对象中获取USER属性值,hello.jsp生成的页面如下图所示。
创建web.xml文件
web.xml文件是Java Web应用的XML格式的配置文件,存放于WEB-INF子目录下。web.xml文件由开发人员编写,供Servlet容器访问。web.xml文件也被称为Java Web应用的发布描述文件,Servlet容器在加载和启动Java Web应用时会读取它的web.xml文件,从中获得关于当前web的发布应用信息。在web.xml文件中可包含如下配置信息:
- Servlet的定义
- Servlet的初始化参数
- Servlet以及JSP的映射
- 安全域配置参数
- welcome文件清单
- 资源引用
- 环境变量的定义
现在创建一个默认的web.xml文件,并把这个文件放到WEB-INF目录中。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0" >
......
</web-app>
以上web.xml文件的第一行指定了XML的版本和字符编码,接下来声明一个<web-app>元素,它是根元素,所有关于Java Web应用的具体配置元素都将加入到这个<web-app>元素中。
接下来在web.xml中为DispatcherServlet类加上<servlet>和<servlet-mapping>元素。
<web-app xmlns=......>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>helloapp.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/dispatcher</url-pattern>
</servlet-mapping>
</web-app>
<servlet>元素用于为Servlet定义一个名字,它的子元素的说明参见下表。在此例配置中,没有为DispatcherServlet设置<load_startup>子元素,因此当web应用启动时,Servlet容器不会初始化这个Servlet,只有当客户端首次访问这个Servlet时,Servlet容器才会初始化它。
子元素 | 说明 |
---|---|
<servlet-name> | 定义Servlet的名字 |
<servlet-class> | 指定Servlet的完整类名(包括包的名字) |
<init-param> | 定义Servlet的初始化参数(包括参数名和参数值),一个<servlet>元素中可以有多个<init-param>。 |
<load-on-startup> | 指定当Servlet容器启动时Web应用时,加载各个Servlet的次序。当这个值为正数或零,Servlet容器先加载数值小的Servlet,再依次加载其他数值大的Servlet。如果这个值为负数或这没有设定,那么Servlet容器将在客户端首次访问这个Servlet时加载它 |
<servlet-mapping>元素用于为Servlet映射一个URI。<servlet-name>子元素指定待映射的Servlet名字;<url-pattern>子元素指定访问Servlet的相对URL路径。根据以上范例的<url-pattern>子元素的值,可以确定访问DispatcherServlet的URL为http://localhost/helloapp/dispatcher。
在web.xml文件中还可以加入元素,它用来为Web应用设置默认的主页。例如,当客户提供的URL为http://localhost/helloapp/时。如果希望服务器端自动返回login.html页面,则可以在web.xml文件中加入以下元素:
<welcome-file-list>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
在Tomcat中发布Java Web应用
Java Web应用可以运行在各种符合Servlet规范的Servlet容器中。此节介绍在Tomcat中发布Java Web应用的步骤。尽管发布的具体细节依赖于Tomcat本身的实现,但以下关于发布Java Web应用的基本思想适用于所有Servlet容器:
- 把Web应用的所有文件复制到Servlet容器的特定目录下,这是发布Web应用的最快捷的一种方式。
- 各种容器Servlet容器实现都会从Web应用的web.xml配置文件读取有关Web组件的配置信息。
- 为了使用户能更加灵活地控制Servlet容器发布和运行Web应用的行为,并且为了使Servlet容器与Web应用能进行更紧密地协作,许多Servlet容器还允许使用用户使用额外地配置文件以及配置元素,这些配置文件及配置元素的语法由Servlet容器的实现决定,与Oracle的Servlet规范无关。
Tomcat的目录结构
在Tomcat上发布Java Web应用之前,首先要了解Tomcat的目录结构。Tomcat的目录结构是由自身的实现决定的,与Oracle公司的Servlet规范无关。Tomcat9.x的目录结构参见下表,表中的目录都是<CATALINA_HOME>的子目录。
项目 | Value |
---|---|
/bin | 存放Windows平台以及Linux平台上启动和关闭Tomcat的脚本 |
/conf | 存放Tomcat服务器的各种配置文件,其中最重要的配置文件是server.xml |
/lib | 存放Tomcat服务器以及所有Web应用都可以访问的JAR文件 |
/logs | 存放Tomcat的日志文件 |
/webapps | 在Tomcat上发布Java Web应用时,默认情况下把Web应用文件存放于此目录下 |
/work | Tomcat的工作目录,Tomcat在运行时把生成的一些工作文件存放于此目录下。例如默认情况下,Tomcat把编译JSP生成的Servlet类文件放在此目录下。 |
按照默认方式发布Java Web应用
在Tomcat中发布Java Web应用的最快捷方式,就是直接把Java Web应用的所有文件复制到Tomcat的<CATALINA_HOME>/webapps目录下。默认情况下,<CATALINA_HOME>/webapps中所有的Web应用运行在名为localhost的虚拟主机中,而localhost虚拟主机则运行在名为Catalina的Engine组件中。
Tomcat既可以运行采用开放式目录结构的Web应用,也可以运行Web应用的打包文件(简称为WAR文件)。
在Web应用的开发阶段,为了便于调试,通常采用开放式的目录结构来发布Web应用,这压根可以方便更新或替换文件。如果开发完毕,进入产品发布阶段,应该将整个Web应用打包为WAR文件,再进行发布。此例中,按照如下步骤helloapp。
- 在DOS中进入helloapp应用的根目录helloapp。
- 使用jar cvf命令把整个Web应用打包为helloapp.war文件。
- 如果在Tomcat的webapps目录下已经由helloapp子目录,先将helloapp子目录删除。
- 把helloapp.war文件复制到<CATALINA_HOME>/webapps目录下。
- 驱动Tomcat服务器。
Tomcat服务器启动时,会把webapps目录下的所有WAR文件自动展开为开放的目录结构。因此服务器启动后,读者会发现服务器自动在webapps目录下创建一个helloapp目录,并把helloapp.war中的所有内容展开到helloapp子目录中。
Web组件的URL
无论按照开放式目录结构还是按照打包文件方式发布Web应用,Web应用的默认URL入口都是Web应用的根目录名。例如对于helloapp应用,它的URL入口为"/helloapp"。
对于HTML或JSP文件,它们的URL为http://localhost/helloapp/login.html。同样,hello/jsp的绝对路径为helloapp/hello.jsp,因此它的URL为http://localhost/helloapp/hello.jsp。
HTML或JSP问价不仅可以放在Web应用的根目录下,也可以放到自定义的子目录中。例如,假定hello.jsp的文件路劲为helloapp/dir1/dir/2hello.jsp,那么它的URL为:http://localhost/helloapp/dir1/dir2/hello.jsp。
提示:浏览器无法直接访问Web应用的WEB-INF目录下的文件。因此,如果出于安全的原因,不希望浏览器直接访问某个JSP文件,可以把它放到WEB-INF目录或其子目录下,在这种情况下,只有服务器端的组件才能访问该JSP文件,例如Servlet可以把请求转发给JSP文件。
对于Servlet,对其映射URL由两种方式:
- 在web.xml文件种映射URL。
- 用@WebServlet标注映射URL。
在此介绍如何在web.xml种配置Servlet。Servlet的URL由web.xml文件中的<url-pattern>元素来指定。在此例中,web.xml文件对helloapp.DispatcherServlet类做如下配置:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>helloapp.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/dispatcher</url-pattern>
</servlet-mapping>
以上<url-pattern>元素值为“/dispatcher”,因此访问helloapp.DispatcherServlet类的URL为:http://localhost/helloapp/dispatcher。当浏览器端首次通过该URL请求访问DispatcherServlet时,Tomcat需要先找到文件系统中的DispatcherServlet.class文件,从而能加载DispatcherServlet类。Tomcat查找DispatcherServlet文件的步骤如下。
- 参考helloapp应用的web.xml文件,找到<servlet-pattern>子元素的值为“/dispatcher”的<servlet-mapping>元素。
- 读取<servlet-mapping>元素的<servlet-name>子元素的值,因此确定Servlet的名字为dispatcher。
- 找到<servlet-name>子元素为dispatcher的<servlet>元素。
- 读取<servlet>元素的<servlet-class>子元素的值,由此确定Servlet的类名为helloapp.DispatcherServlet。
- 到<CATALINA_HOME>/helloapp/WEB-INF/classes/helloapp目录下查找DispatcherServlet.class文件。
提示:Tomcat在加载Web应用时,就会把相应的web.xml文件中的数据读入到内存中。因此当Tomcat需要参考web.xml文件时,实际上只要从内存中读取相关数据就行了,无需再到文件系统中读取web.xml文件。
初学者发布了Servlet后,再通过浏览器访问Servlet时,服务器端可能会返回“该文件不存在”的错误。这可能时由以下原因导致的:
- 提供的URL不正确,例如以下URL都无法访问DispatcherServlet类:
http://localhost/helloapp/DispatcherServlet.class
http://localhost/helloapp/DispatcherServlet
http://localhost/helloapp/helloapp/DispatcherServlet.class
http://localhost/dispatcher
- web.xml作为XML格式的文件,需要区分大小写。如果不注意大小写,可能会导致对Servlet的配置不正确。例如以下<Servlet>元素把helloapp.DispatcherServlet类命名为“Dispatcher”。而<servlet-mapping>元素对一个名为“dispatcher”的Servlet进行了URL映射。这使得名为“Dispatcher”的Servlet实际上没有进行URL映射。
<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>helloapp.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/dispatcher</url-pattern>
</servlet-mapping>
- Servlet的.class文件的存放路径不正确。Servlet的.class文件必须位于Web应用的WEB-INF/classes目录下,并且类的包名与文件路径不匹配。例如对于一个完整类名为helloapp1.helloapp2.MyServlet的类,它的文件路径应该为:
WEB-INF/helloapp1.helloapp2.MyServlet.class
也许你会问:Servlet规范为什么要对于Servlet进行URL映射呢?如果能直接根据Servlet的文件路径来访问Servlet,不是更方便吗?假如这种设想成立,访问DispatcherServlet类的URL将变为:
http://localhost/helloapp/helloapp/DispatcherServlet.class
Servlet规范之所以要对Servlet进行URL映射,主要由两个原因:
- 为一个Servlet对应多个URL提供方便地设置途径。加入有个Web应用规定所有以“.DO”结尾地URL都由ActionServlet来处理,那么只需在web.xml文件中对ActionServlet进行如下配置:
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>helloapp.ActionServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>/*.do</url-pattern>
</servlet-mapping>
这样,所有以“.DO”结尾的URL都对应ActionServlet,例如以下URL都映射到ActionServlet:
http://localhost/helloapp/login.DO
http://localhost/helloapp/logout.DO
http://localhost/helloapp/checkout.Do
再例如一个<servlet>还可以对应多个<servlet-mapping>元素:
<servlet>
<servlet-name>manager</servlet-name>
<servlet-class>helloapp.managerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>manager</servlet-name>
<url-pattern>/list</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>manager</servlet-name>
<url-pattern>/sessions</url-pattern>
</servlet-mapping>
- 简化Servlet地URL,并且可以向客户端隐藏Web应用的实现细节。如果在URL中暴露Servlet的完整类名,会让不懂Java Web开发的普通用户觉得URL很复杂,不容易理解和记忆。通过对Servlet进行URL映射,则可以提供一个简洁、易懂的URL。
提示:JSP文件和Servlet一样,也可以在web.xml文件中映射URL。
配置Tomcat的<Context>元素
前面已经介绍了发布Java Web应用的最快捷方式,只需把Java Web应用的所有文件复制到<CATALINA_HOME>/webapps目录下即可,Tomcat会按照默认方式来发布和运行Java Web应用。如果需要更加灵活地发布Web应用,则需要为Web应用配置Tomcat的<Context>元素。
<Context>元素是Tomcat中使用最频繁的元素,它代表了运行在虚拟主机<Host>上的单个Web应用。在Tomcat的配置文件中,一个<Engine>中可以有多个<Host>,一个<Host>中可以有多个<Context>。<Context>元素的主要属性的说明参见下表。
属性 | 描述 |
---|---|
path | 指定访问该Web应用的URL路径 |
docBase | 指定Web应用的文件路径,可以给定绝对路径,也可以给定相对于<Host>的appBase属性的相对路径。如果Web应用采用开放目录结构,则指定Web应用的根目录;如果Web应用是个WAR文件,则指定WAR文件的路径 |
className | 指定实现Context组件的Java类的名字,这个Java类必须实现org.apache.catalina.Context接口。该属性的默认值为org.apache.catalina.core.StandardContext |
reloadable | 如果这个属性设为true,Tomcat服务器在运行状态下会监视在WEB-INF/classes和WEB-INF/lib目录下.class类文件或.jar类库文件的改动。如果检测到由类文件或类库文件被更新,服务器会自动重新加载Web应用。该属性的默认值为false。在Web应用的开发和调试阶段,把reloadable设为false,可以降低Tomcat的运行负荷,提高Tomcat的运行性能 |
一般情况下,<Context>元素都会使用默认的标准Context组件,即className属性采用默认值org.apache.catalina.core.StandardContext。标准Context组件除了具有上表列出的属性,还具有下表所示的属性。
属性 | 描述 |
---|---|
uploadDelay | 设定Tomcat等待Servlet卸载毫秒数。该属性的默认值为2000毫秒 |
workDir | 指定Web应用的工作目录。Tomcat运行时会把与这个Web应用相关的临时文件放在此目录下 |
uppackWar | 如果此项设为true,表示将把Web应用WAR文件先展开为开放目录结构后在运行。如果设为false则直接运行WAR文件。该属性默认值为true |
在Tomcat低版本中,允许直接在<CATALINA_HOME>/conf/servlet.xml文件中配置<Context>元素。这种配置方式有一个弊端:如果在Tomcat运行时修改servlet.xml文件,比如添加<Context>元素,那么所作的修改不会立即生效,而必须重新启动Tomcat,才能使所做的修改生效。
因此从Tomcat 6.x开始的高版本尽管也允许直接在servlet.xml文件中配置<Context>元素,但不提倡采用这种方式。如今的Tomcat提供了多种配置<Context>元素的途径。当Tomcat加载一个应用时,会按照以下顺序查找Tomcat的<Context>元素:
- 到<CATALINA_HOME>/conf/context.xml文件中查找<Context>元素。这个文件中的<Context>元素的信息适用于所有Web应用。
- 到<CATALINA_HOME>/conf/[enginename]/[hostname]/context.xml.default文件中查找<Context>元素。[enginename]表示[Engine]的name属性,[hostname]表示<Host>的name属性。在context.xml.default文件中的<Context>元素的信息适用于当前虚拟主机中的所有Web应用。例如以下文件中的<Context>元素适用于名为Catalina的Engine下的localhost主机中的所有Web应用:
<CATALINA>/conf/Catalina/localhost/context.xml.default
- 到<CATALINA_HOME>/conf/[enginename]/[hostname]/[contextpath].xml文件中查找<Context>元素。[contextpath]表示单个Web应用的URL入口。在[contextpath].xml文件中的<Context>的信息只适用于耽搁Web应用。例如以下文件中的<Context>元素只适用于名为catalina的Engine下的localhost主机中的helloapp应用:
<CATALINA_HOME>/conf/Catelina/localhost/helloqpp.xml
- 到Web应用的META-INF/context.xml文件中查找<Context>元素。这个文件中的<Context>元素信息适用于当前Web应用。
- 到<CATALINA_HOME>/conf/server.xml文件中的<Host>元素中查找<Context>子元素。该<Context>元素的信息只适用于单个Web应用。
如果仅仅为单个Web应用配置<Context>元素,可以有限选择第三种或第四种方式。第三种方式要求在Tomcat的相关目录下增加一个包含<Context>元素的配置文件,而第四种方式则要求在Web应用的相关目录下增加一个包含<Context>元素的配置文件。对于这两种方式,Tomcat在运行时会监测包含<Context>元素的配置文件是否被更新,如果被更新,Tomcat会自动重新加载并启动Web应用,使对<Context>元素所在的修改内容生效。
下面先采用第四种方式配置<Context>元素。在helloapp目录下新建一个META-INF子目录,然后在其中创建一个context.xml文件,它的内容如下:
<Context path="/helloapp" docBase="helloapp" reloadable="true" />
以上<Context>元素的docBase属性表明,helloapp应用文件的文件路径为<CATALINA_HOME>/webapps/helloapp;path属性表明访问helloapp应用的URL入口为“/helloapp”。
下面再采用第三种方式配置<Context>元素,假定helloapp应用的文件路径为D:\TomcatAndJavaWeb\project\helloapp,并且在<CATALINA_HOME>/webapps目录下没有发布helloapp应用。在<CATALINA_HOME>/conf/Catalina/localhost目录下创建helloapp.xml文件,它的内容如下:
<Context path="/helloapp"
docBase="D:\TomcatAndJavaWeb\project\helloapp"
reloadable="true"/>
以上<Context>元素的docBase属性指定了helloapp应用的绝对路径,D:\TomcatAndJavaWeb\project\helloapp;path属性表明访问helloapp应用的入口为“/helloapp”。由于helloapp.xml文件位于Catalina/localhost子目录下,因此helloapp应用将运行在名为Catalina的Engine组件的localhost虚拟主机中。访问helloapp的login.html和hello.jsp的URL分别为:
http://localhost/helloapp/login.html
http://localhost/helloapp/hello.jsp
在server.xml文件中已经有一个名为localhost的<Host>元素,如果采用第五种方式配置<Context>元素,最常见的方法是在该<Host>元素中插入<Context>子元素,例如:
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
...
<Context path="/helloapp" docBase="D:\TomcatAndJavaWeb\project\helloapp" reloadable="true"/>
</Host>
提示:如果没有为Web应用配置Tomcat的Context元素,那么Tomcat会为Web应用提供一个默认的Context组件。例如前面介绍的按照默认方式发布helloapp应用时,Tomcat就给它提供了默认的Context组件。
配置Tomcat的虚拟主机
在Tomcat的配置文件servere.xml中,<Host>元素代表虚拟主机,在同一个<Engine>元素下可以配置多个虚拟主机。例如,有两个公司的Web应用都发布在同一个Tomcat服务器上,可以为两家公司分别创建一个虚拟主机,它们的虚拟主机名分别为:
www.helloapp.com
www.helloappex.com
尽管以上两个虚拟主机实际上对应同一个主机,但是当客户端通过以上两个不同的虚拟主机名访问Web应用时,客户端会感觉这两个不同应用分别拥有独立的主机。
此外,还可以为虚拟主机建立别名,例如,如果希望有客户端访问www.helloapp.com或www.helloappex.com都能对应同一个应用,那么可以把helloappex.com当作别名来处理。
下面介绍如何配置www.helloappex.com虚拟主机。
- 打开<CATALINA_HOME>/conf/server.xml文件,会发现在<Engine>元素中已经有一个名为localhoat的<Host>元素,可以在它后面(即<Host>标记后面)加入如下<Host>元素:
<Host name="www.helloappex.com" appBase="D:\Tomcat 10.0\webapps\helloapp"
unpackWARs="true" autoDeploy="true">
<Alias>helloappex.com</Alias>
<Alias>helloappex</Alias>
</Host>
<Host>元素的属性描述参见下表。<Host>元素还有一个子元素<Alias>,它用于指定虚拟主机的别名。<Host>元素允许包含多个<Alias>子元素,因此可以指定多个别名。
项目 | Value |
---|---|
name | 指定虚拟主机的名字 |
className | 指定实现虚拟主机的Java类名字,这个类必须实现org.apache.catalina.Host接口。该属性的值默认为org.apache.catalina.StandadHost |
appBase | 指定虚拟主机的目录,可以指定绝对目录,也乐意指定相对<CATALINA_HOME>的相对目录。如果此项没有设定,默认值为<CATALINA_HOME>/webapps |
autoDeploy | 如果此项设为true,表示Tomcat服务器处于运行状态时,能够监测appBase下的文件,如果有新的Web应用加入进来,则会自动发布这个Web应用 |
deployOnStartup | 如果此项设为true,则表示Tomcat启动时会自动发布appBase目录下所有的Web应用。如果Web应用没有相应的<Context>元素,那么Tomcat会提供一个默认的Context组件。deployOnStartup的默认值是true |
一般情况下,<Host>元素都会使用默认的标准虚拟主机,即className属性使用默认值org.apache.catalina.StandardHost。标准虚拟主机除了具有上表列出的属性,还具有以下所示的属性。
项目 | Value |
---|---|
unpackWARS | 如果此项设为true,表示将把appBase属性指定的目录下的Web应用的WAR文件先展开为开放目录结构后再运行。如果设为false,则直接运行WAR文件 |
workDir | 指定虚拟主机的工作目录。Tomcat运行时会把与这个虚拟主机的所有Went应用相关的临时文件放在此目录下。它的默认值为<CATALINA_HOME>/work。如果<Host>元素下的一个<Context>元素也设置了workDir属性,那么<Context>元素的workDir属性会覆盖<Host>元素的workDir属性 |
deployXML | 如果设为false,那么Tomcat不会解析Web应用中的用于设置Context元素的META-INF/context.xml文件。处于安全原因,如果不希望Web应用中包含Tomcat配置元素,就可以把这个设为false,在这种情况下,应该在<CATALINA_HOME>/conf/[enginename]/[hostname]下设置context元素。该属性的默认值为true |
- 把整个helloapp应用(helloapp.war文件或者是整个helloapp目录)复制到<Host>元素的appBase属性指定的目录D:\Tomcat 10.0\webapps\helloapp下。
- 为了使以上配置的虚拟主机生效,必须在DNS服务器中注册以上虚拟主机名称和别名,使它们和Tomcat服务器所在的主机IP地址进行映射。在章节的最后会介绍如何在Windows中配置DNS映射。必须在DNS服务器中注册以下虚拟主机名字和别名:
www.helloappex.com
helloappex.com
helloappex
- 重启Tomcat服务器,然后通过浏览器访问:
http://www.helloappex.com/
如果返回正常的页面就说明配置成功。还可以通过虚拟机的别名来访问helloapp应用:
http://helloappex.com
http://helloapp
每个虚拟主机都可以有一个默认的Web应用,它的默认根目录为ROOT。例如在<CATALINA_HOME>/webapps目录下有一个ROOT目录,它是localhoat虚拟主机的默认Web应用,访问http://localhost/index.jsp,就会显示这个Web应用的index.jsp页面。
对于www.helloappex.com虚拟主机也可以提供默认的Web应用。把D:\Tomcat 10.0\webapps\下的helloapp目录改名为ROOT目录,这个虚拟主机就有了一个默认的Web应用。访问http://www.helloappex.com/login.html,就会显示这个Web应用的login.html页面。
提示:如果要设置虚拟主机的默认Web应用的<Context>元素,那么它的path属性的值应该为一个空的字符串(即path=“”)。
以上步骤(3)提到的要惊醒虚拟主机名和IP地址之间的DNS映射。下面介绍在Windows系统中进行DNS映射的步骤。
-
在文件资源管理器中找到文件:C:\Windows\System32\drivers\etc\hosts。选中hosts文件,按下鼠标右键,在弹出的菜单中,选择【属性】->【安全】->【编辑】,在hosts文件的访问权限编辑窗口中,设置Windows用户具有“完全控制”权限,参见下图。
-
用Windows记事本打开hosts文件,在文件中加入如下内容,使得"www.helloappex.com"等虚拟主机名和本地主机IP地址映射:
127.0.0.1 www.helloappex.com
127.0.0.1 helloappex.com
127.0.0.1 helloappex
:: www.helloappex.com
:: helloappex.com
:: helloappex
以上代码中“127.0.0.1”和“::1”分别是本地主机的IPV4格式和IPV6格式的IP地址。
创建、配置和使用自定义JSP标签
接下来创建一个名为hello的简单的自定义JSP标签,它的作用是输出字符串“Hello”。hello标签位于一个名为mytaglib的标签库(Tag Library)中。hello.jsp会使用mytaglib标签库中的hello标签。在此我们侧重介绍hello标签的发布和使用。
以下是创建、配置和使用hello标签步骤。
- 编写用于处理hello标签的类,名为HelloTag类,下面例程列出了HelloTag.java的源代码。
package helloapp;
import jakarta.servlet.jsp.JspException;
import jakarta.servlet.jsp.JspTagException;
import jakarta.servlet.jsp.tagext.TagSupport;
public class HelloTag extends TagSupport{
//当JSP解析器遇到hello标签的结束标志时,调用此方法
public int doEndTag() throws JspException{
try {
//打印字符串“hello”
pageContext.getOut().print("Hello");
}catch(Exception e)
{
throw new JspTagException(e.getMessage());
}
return EVAL_PAGE;
}
}
编译HelloTag.java时,需要将Servlet API的类库文件(servlet-api.jar)以及JSP API的类库文件(jsp-api.jar)添加到classpath中,这两个JAR文件位于<CATALINA_HOME>/lib,目录下。此外,在Oracle的官方地址(http://www.oracle.com/technetwork/java/index.html)也可以下载JSP API的类库文件。编译生成的HelloTag.class存放位置为WEB-INF/calsses/helloapp/HelloTag.class。
2. 创建一个TLD(Tag Library Descriptor,标签库描述符)文件。假定hello标签位于mytaglib标签库中,因此创建一个名为mytaglib.tld的TLD文件。在这个文件中定义mytaglib标签库和hello标签。这个文件的存放位置为WEB-INF/mytaglib.tld。下面例程列出了mytaglib.tld的源代码。
<?xml version="1.0" encoding="UTF-8"?>
<!-- a tag library descriptor -->
<taglib>
<tlib-version>1.1</tlib-version>
<jsp-version>2.4</jsp-version>
<short-name>mytaglib</short-name>
<uri>mytaglib</uri>
<tag>
<name>hello</name>
<tag-class>helloapp.HelloTag</tag-class>
<body-content>empty</body-content>
<description>Just Says Hello</description>
</tag>
</taglib>
提示:Servlet规定,TLD文件在Web应用中必须存放在web-INF目录或这自定义的子目录下,但不能放在web-INF\classes目录和web-INF\lib目录下。web.xml文件中的<taglib>元素的<tag-location>子元素用来设置标签库描述文件的存放路径,应该保证<taglib-location>子元素的取值与TLD文件实际存放位置相符。
- 在web.xml文件中配置<taglib>元素,下面例程列出了修改后的web.xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0" >
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>helloapp.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/dispatcher</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
<jsp-config>
<taglib>
<taglib-uri>
/mytaglib
</taglib-uri>
<taglib-location>
/WEB-INF/mytaglib.tld
</taglib-location>
</taglib>
</jsp-config>
</web-app>
<taglib>元素位于<jsp-config>元素中。<taglib>元素中包含两个子元素:<taglib-uri>和<taglib-location>。其中<taglib-uri>指定标签库的URI;<taglib-location>指定标签库的TLD文件的存放位置。
- 在hello.jsp文件中使用hello标签。首先,在hello.jsp中加入引用mytaglib标签库的taglib标签指令:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<%@ taglib uri="/mytaglib" prefix="mm" %>
<html>
<head>
<title>helloapp</title>
</head>
<body>
<b><mm:hello/> : <%= request.getAttribute("USER") %></b>
</body>
</html>
hello.jsp修改后,再次访问login.html->DispatcherServlet->hello.jsp,最后生成的网页如下图所示。这个网页中的“Hello”字符串就是由<mm:hello/>标签输出来的。
当客户端请求访问hello.jsp时,Servlet容器会按照如下步骤处理hello.jsp中的<mm:hello/>标签。
- 由于<mm:hello/>的前缀为“mm”,与hello.jsp中的如下taglib指令匹配:
<%@ taglib uri="/mytaglib" prefix="mm" %>
由此得知hello标签来自URI为“/mytaglib”的标签库。
2. 在web.xml文件中对URI为“/mytaglib”的标签的配置如下:
<taglib>
<taglib-uri>
/mytaglib
</taglib-uri>
<taglib-location>
/WEB-INF/mytaglib.tld
</taglib-location>
</taglib>
由此可知URI为“/mytaglib”的标签库的TLD文件为WEB-INF/mytaglib.tld。
3. 在WEB-INF/mytaglib.tld文件中对名为hello的标签的定义如下:
<tag>
<name>hello</name>
<tag-class>helloapp.HelloTag</tag-class>
<body-content>empty</body-content>
<description>Just Says Hello</description>
</tag>
由此得知hello标签的处理类为helloapp.HelloTag类。因此Servlet容器运行hello.jsp时,如果遇到<mm:hello/>标签,就会加载WEB-INF/classes/helloapp目录下的HelloTag.class文件。遇到<mm:hello>标签的结束标志时,就会调用HelloTag类的doEndTag()方法。