博主信息:
📢@博主: 嘟嘟的程序员铲屎官
💬:一位爱喵咪,爱开源,爱总结,爱分享技术的Java领域新星博主,如果你想和博主做朋友,关注博主,并私聊博主(给我发一条消息我就会关注你喔),博主本人也十分喜欢解决问题,如果你有什么问题,也可以来私聊博主喔,希望能够和C站的朋友相互学习,相互进步。
💬:关于本篇博客,最近在看廖雪峰大佬JavaWeB部分的知识,对大佬的Servlet这一篇进行个人总结,如果有什么错误的,请各位大佬能够及时提出,以免小弟误人子弟!
一.Tomcat
在前面我们通过自己编写的HTTP服务器代码实现了通过游览器访问网页,但是该服务器存在非常多的问题,我们编写的HTTP服务器访问的内容其实是静态资源,HTTP访问方式仅仅只有Get,网页代码就写在服务器端,没有任何的异常处理措施,并且我们仅仅只是为了编写网站,如果创建一个网站还要编写相应的服务器代码,成本负担就实在太大了,有没有现成的服务器,我们只需要将我们编写的网站代码放到服务器中进行部署,无需关心服务端代码怎么实现,这就是Tomcat服务器。
1.关于什么是Tomcat服务器?
- Tomcat 服务器是一个免费的开放源代码的Web应用服务器
- Tomcat 实际上运行JSP页面和Servlet
- Tomcat处理静态HTML的能力不如Apache服务器
搜狗百科:
Tomcat 服务器是一个免费的开放源代码的Web应用服务器
,[1]属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。
诀窍是,当配置正确时,Apache 为HTML页面服务,而Tomcat 实际上运行JSP
页面和Servlet
。另外,Tomcat和IIS等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache服务器
。目前Tomcat最新版本为9.0(下图就是代表Tomcat服务器的图标)。
游览器怎么和Tomcat服务器进行交互的?
- 游览器
发起请求
- Tomcat服务器通过Servlet
获得用户的请求
- Tomcat服务器通过Servlet
响应用户的请求
2.Tomcat的安装
相关学习链接:
TOMCAT原理详解及请求过程
二.Servlet
搜狗百科:
servlet是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,
主要功能在于交互式地浏览和修改数据,生成动态Web内容
。
Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。
关于什么是动态Web内容(和数据库有交互),如下图:
1.Servlet ApI 4个Java包
该API的jar包文件位于Tomcat的lib路径下
- Javax.servlet:包含定义Servlet与Servlet容器之间契约的类和接口。
- Javax.servlet.http:包含定义HTTP Servlet与Servlet容器之间契约的类和接口
- javax.servlet.annotaion:包含对Servlet,Filter和Listener进行标注的注解,它还为标注元件指定元数据。
- javax.servlet.descriptor:包含为Web应用程序的配置信息提供编程式访问的类型。
2.游览器怎么和Tomcat服务器进行交互的(进一步了解)?
- 客户端(通常都是浏览器)访问Web服务器,发送HTTP请求
- Web服务器接收到请求后,传递给Servlet容器
- Servlet容器加载Servlet,产生Servlet实例后,向其传递表示请求和响应的对象
- Servlet实例使用请求对象得到客户端的请求信息,然后进行相应的处理
- Servlet实例将处理结果通过响应对象发送回客户端,容器负责确保响应送出去,同时将控制返回给Web服务器
3.Servlet的生命周期
Sun 公司提供了一系列的接口和类用于 Servlet 技术的开发,其中最重要的接口是 javax.servlet.Servlet。在 Servlet 接口中定义了 5 个抽象方法,如下表所示。
方法声明 | 功能描述
-------- | -----
void init(ServletConfig config) |容器在创建好 Servlet 对象后,就会调用此方法。该方法接收一个 ServletConfig 类型的参数,Servlet 容器通过该参数向 Servlet 传递初始化配置信息|
ServletConfig getSendetConfig()| 用于获取 Servlet 对象的配置信息,返回 Servlet 的 ServletConfig 对象|
String getServletInfo() |返回一个字符串,其中包含关于 Servlet 的信息,如作者、版本和版权等信息|
void service (ServletRequest request,ServletResponse response) |负责响应用户的请求,当容器接收到客户端访问 Servlet 对象的请求时,就会调用此方法。容器会构造一个表示客户端请求信息的 ServletRequest 对象和一个用于响应客户端的 ServletResponse 对象作为参数传递给 service() 方法。在 service() 方法中,可以通过 ServletRequest 对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用 ServletResponse 对象的方法设置响应信息
void destroy()| 负责释放 Servlet 对象占用的资源。当服务器关闭或者 Servlet 对象被移除时,Servlet 对象会被销毁,容器会调用此方法|
在 Java 中,任何对象都有生命周期,Servlet 也不例外。Servlet 的生命周期如图下图所示。
4.ServletRequest和ServletResponse
(1) ServletRequest
当浏览器请求网页时,它会向 Web 服务器发送特定信息,这些信息不能被直接读取,因为这些信息是作为 HTTP 请求的头的一部分进行传输的,对于每一个HTTP请求,Servlet容器都会创建一个ServletRequest实例,并将它传给Servlet的service方法。
以下是来自于浏览器端的重要头信息,您可以在 Web 编程中频繁使用:
下面的方法可用在 Servlet 程序中读取 HTTP 头。这些方法通HttpServletRequest 对象可用。(1) ServletResponse
javax.servlet.ServletResponse接口表示一个Servlet响应。在调用一个Servlet的service方法之前,Servlet 容器会先创建一个ServletResponse,并将它作为第二个参数传给service方法。ServletResponse隐藏了将响应发给浏览器的复杂性。
当一个 Web 服务器响应一个 HTTP 请求时,响应通常包括一个状态行、一些响应报头、一个空行和文档。一个典型的响应如下所示:
HTTP/1.1 200 OK
Content-Type: text/html
Header2: ...
...
HeaderN: ...
(Blank Line)
<!doctype ...>
<html>
<head>...</head>
<body>
...
</body>
</html>
状态行包括 HTTP 版本(在本例中为 HTTP/1.1)、一个状态码(在本例中为 200)和一个对应于状态码的短消息(在本例中为 OK)。
下表总结了从 Web 服务器端返回到浏览器的最有用的 HTTP 1.1 响应报头,您会在 Web 编程中频繁地使用它们:
设置 HTTP 响应报头的方法
下面的方法可用于在 Servlet 程序中设置 HTTP 响应报头。这些方法通过 HttpServletResponse 对象可用。
编写传递给用户的文档(HTML)
通过HttpServletResponse .getWriter()获得PrintWWriter对象,该对象采用ISO-8859-1编码,该对象通过print("")方法输出文本内容。
上面的内容来源于(菜鸟编程):
Servlet 客户端 HTTP 请求
Servlet 服务器 HTTP 响应
5.ServletConfig和ServletContext接口及其使用方法详解
(1) ServletConfig
当 Tomcat 初始化一个 Servlet时,会将该 Servlet 的配置信息封装到 ServletConfig 对象中,此时可以通过调用 init(ServletConfig config)方法将 ServletConfig 对象传递给 Servlet。
ServletConfig 接口中定义了一系列获取配置信息的方法,如下表所示。
方法说明 | 功能描述
-------- | -----
String getInitParameter(String name) |根据初始化参数名返回对应的初始化参数值|
Enumeration getInitParameterNames() |返回一个 Enumeration 对象,其中包含了所有的初始化参数名|
ServletContext getServletContext()| 返回一个代表当前 Web 应用的 ServletContext 对象|
String getServletName()| 返回 Servlet 的名字,即在 web.xml 中 元素的值,在@WebServlet种name的值|
配置Servlet的二种方式
@WebServlet注解:
给Servlet映射了一条路径
@WebServlet(name="Myservlet" , urlPatterns={ "/my" })
- name:该Servlet的名称为Myservlet,可通过getServletName()获取
- urlPatterns:映射的路径
使用部署描述符(在WEB-INF目录下web.xml 文件)
在运行 Servlet 程序时,可能需要一些辅助信息,例如,文件使用的编码、使用 Servlet 程序的共享信息等,这些信息可以在 web.xml 文件中使用一个或多个 元素进行配置。
- 可以使用@WebServlet中没有的元素
- 如果需要修改配置值,如Servlet路径,就不需要重新编译Servlet类
- 允许覆盖Servlet中@WebServlet注解中的值
配置信息如下:
<servlet>
<servlet-name>Myservlet</servlet-name>
<servlet-class>app01.MyServlet</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Myservlet</servlet-name>
<url-pattern>/My01</url-pattern>
</servlet-mapping>
备注:ServletConfig的配置信息通过web.xml 文件进行配置,在 web.xml 文件中,不仅可以配置 Servlet 的映射信息,还可以配置整个 Web 应用的初始化信息。
(2) ServletContext接口
当 Tomcat 启动时,Tomcat 会为每个 Web 应用创建一个唯一的 ServletContext 对象代表当前的 Web应用,该对象封装了当前 Web 应用的所有信息。可以利用该对象获取 Web 应用程序的初始化信息、读取资源文件等。下面ServletContext 接口的不同作用分别进行讲解。
- 获取 Web 应用程序的初始化参数
在web.xml中定义:
<context-param>
<param-name>XXX</param-name>
<param-value>xxx</param-value>
</context-param>
<context-param>
<param-name>AAA</param-name>
<param-value>aaa</param-value>
</context-param>
在上面的示例中, 元素位于根元素 中,它的子元素 和 分别用于指定参数的名字和参数值。要想获取这些参数名和参数值的信息,可以使用 ServletContext 接口中定义的 getInitParameterNames() 和 getInitParameter(String name)方法分别获取。
在web.xml中定义:
<context-param>
<param-name>username</param-name>
<param-value>admin</param-value>
</context-param>
<context-param>
<param-name>password</param-name>
<param-value>1234</param-value>
</context-param>
在Servlet中获取web.xml中的参数,并输出
- 读取 Web 应用下的资源文件
在实际开发中,有时会需要读取 Web 应用中的一些资源文件,如配置文件和日志文件等。为此,在 ServletContext 接口中定义了一些读取 Web 资源的方法,这些方法是依靠 Servlet 容器实现的。Servlet 容器根据资源文件相对于 Web 应用的路径,返回关联资源文件的 I/O 流或资源文件在系统的绝对路径等。
ServletContext接口的常用方法:
方法说明 | 功能描述
-------- | -----
Set getResourcePaths(String path) |返回一个 Set 集合,集合中包含资源目录中子目录和文件的路径名 称。参数 path 必须以正斜线(/)开始,指定匹配资源的部分路径|
String getRealPath(String path) | 返回资源文件在服务器文件系统上的真实路径(文件的绝对路径)。参数 path 代表资源文件的虚拟路径,它应该以正斜线(/)开始,/ 表示当前 Web 应用的根目录,如果 Servlet 容器不能将虚拟路径转换为文 件系统的真实路径,则返回 null |
URL getResource(String path) | 返回映射到某个资源文件的 URL 对象。参数 path 必须以正斜线(/)开始,/ 表示当前 Web 应用的根目录 |
InputStream getResourceAsStream(String path)| 返回映射到某个资源文件的 InputStream 输入流对象。参数 path 的传递规则和 getResource() 方法完全一致|
如何使用 ServletContext 对象读取资源文件
- 在 servletDemo01 项目的 src 目录中创建一个名称为 itcast.properties
的文件,在创建好的文件中输入如下所示的配置信息:
username=admin
password=1234 - 在Servlet下如何读取 itcast.properties 资源文件
6.通过实现servlet接口的方式创建一个Servlet实例
File→New→Other,在 Select a wizard 窗口中选择 Dynamic Web Project 选项
输入项目名app01
点击Next
勾选Generate web.xml deployment descriptor,不然不能生成web.xml文件
点击finish
由于我是通过Java视图进行创建,所以会提示是否切换到JavaEE视图(JavaWeb开发的视图),点击Yes
点击Yes之后该视图就切换到Java EE视图
项目结构如下:
右击项目,创建一个app01的包
在该包下创建一个名为MyServlet的类,该类实现Servlet接口,并重写该类下的所有抽象方法。
package app01;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
@WebServlet(name="Myservlet" , urlPatterns={
"/my" })
public class MyServlet implements Servlet{
private transient ServletConfig servletConfig;
@Override
public void init(ServletConfig servletConfig) throws ServletException {
// TODO Auto-generated method stub
this.servletConfig=servletConfig;
System.out.println("init()通过servletConfig进行初始化操作");
}
@Override
public ServletConfig getServletConfig() {
// TODO Auto-generated method stub
return servletConfig;
}
@Override
public String getServletInfo() {
// TODO Auto-generated method stub
return "Myservlet";
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
System.out.println("service()获取请求,并进行响应!");
String name = servletConfig.getServletName();
response.setContentType("text/html");
PrintWriter writer=response.getWriter();
writer.print("<html><head></head>");
writer.print("<body>hello from"+name+"</body></html>");
}
@Override
public void destroy() {
// TODO Auto-generated method stub
System.out.println("销毁!");
}
}
运行项目:
运行效果:
运行之后我们发现无法访问,这是因为没有在web.xml中设置网站的首页,并且我们也没有创建任何的静态HTML或者Jsp等网页,给用户进行展示,要响应对应的Servlet可以通过/my进行响应(我们给Servlet映射了一条路径为/my
,必须通过该路径才能进行响应)。
在网址后面输入my,并进行跳转即可访问
获取内容:
上面的内容其实就是我们在MyServlet类下,service()方法所写的响应内容
从该实例中看看servlet的生命周期
- init():通过servletconfig进行初始化,在servlet生命周期中只执行一次
- service():获取用户的请求并发送服务器端的响应,网页刷新一次(用户请求一次)就调用一次service().
- destroy():当服务器关闭或者 Servlet 对象被移除时,Servlet 对象会被销毁,容器会调用此方法
7.servlet实例(app01)进行优化
- Sun 公司其实已经提供了一个Servlet的实现类GenericServlet抽象类,但是该抽象类并没有实现HTTP 请求处理
- HttpServlet 是 GenericServlet 的子类,它继承了 GenericServlet 的所有方法,并且为 HTTP 请求中的 GET 和 POST 等类型提供了具体的操作方法。通常情况下,编写的 Servlet 类都继承自 HttpServlet,在开发中使用的也是 HttpServlet 对象。
HttpServlet 类中包含两个常用方法,这两个方法的说明如下表所示:
方法声明 | 功能描述
-------- | -----
protected void doGet (HttpServletRequest req, HttpServletResponse resp) | 用于处理 GET 类型的 HTTP 请求的方法|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) | 用于处理 POST 类型的 HTTP 请求的方法|
优化1:通过继承GenericServlet类实现Servlet
创建一个MyServlet02类,继承GenericServlet抽象类,并重写该抽象类的所有抽象方法(抽象方法只有一个service())
运行效果:
优化2:通过继承HttpServlet类实现Servlet
优化1的方式虽然可以通过简化实现servlet但是该方式并没有实现HTTP 请求处理
创建一个MyServlet03类,继承HttpServlet类
- 通过重写对应的请求方式,实现对应请求方式的响应(重写doGet(),实现get请求的响应;重写doPost(),实现post请求的响应)
- doGet()和doPost()并不是抽象方法,不需要强制进行重写,由于这二种请求方式使用的最多所以一般都将其重写
运行效果:
其实在eclipse中,JavaEE视图下,可以直接创建Servlet
通过eclipse创建的Servlet代码如下:
优化三:ServletConfig的配置通过web.xml进行配置
在Web.xml中定义Servlet部署描述符
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<display-name>app01</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>My01</servlet-name>
<servlet-class>app01.MyServlet</servlet-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>My01</servlet-name>
<url-pattern>/My01</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>My02</servlet-name>
<servlet-class>app01.MyServlet02</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>My02</servlet-name>
<url-pattern>/My02</url-pattern>
</servlet-mapping>
</web-app>
备注: servlet-mapping: 如果url-pattern定义的是路径,那么以后所有对这个路径下资源的请求都会由servlet-name中定义的servlet处理;
注意:
如果在web.xml和@WebServlet("/my")都定义了映射路径,二者的映射路径不能一致,不然会出现异常
在web.xml和@WebServlet("/my")都定义了映射路径,二个路径都可以进行访问
关于<welcome-file-list >
:
<welcome-file-list>
:网站的首页列表(访问网站第一次见到的网页)
前面的app01项目,当访问网站的时候,会显示404(404表示没有找到<welcome-file-list >
里面的网页),这是因为我们网站没有创建任何的网页(HTML,JSP等),也没有设置网站的首页.
优化四:给app01网站创建首页
我们只要创建web.xml中 <welcome-file-list>
中任何一个网页,第一次打开网站就会显示该网页
在Webcontent中创建index.html
index.html代码如下:
运行效果:
优化五:在Tomcat中部署app01网站
War文件(扩展名为.War,Web Application Archive)包含全部Web应用程序。在这种情形下,一个Web应用程序被定义为单独的一组文件、类和资源,用户可以对jar文件进行封装,并把它作为小型服务程序(servlet)来访问。
部署:
将项目导包为*.war后缀
的文件,并复制文件到Tomcat服务器下的webapps目录下即可,具体步骤如下:
- 将app01导出为*.war文件
- 下面的路径可以直接选择Tomcat服务器下的webapps目录下
- 或者复制app01.war到Tomcat服务器下的webapps目录下
- 当Tomcat运行的时候,会自动解压(
手动启动Tomcat服务器
)
- 在游览器地址栏输入:
http://localhost:8085/app01/
即可访问(我的Tomcat的端口号是8085,有的是8080)
在Tomcat服务器中应用程序的目录结构如下:
我们编写的app01的应用结构是如何的?
Tomcat服务器的webapps目录下的app01
app01应用目录结构
META-INF目录下:
WEB-INF目录下:
classes目录下:
app01目录下:
8.处理HTML表单
每个Web应用程序中几乎都会包含一个或者多个HTML表单,用来接收用户输入。你可以轻松地将一个HTML表单从Servlet发送到浏览器。当用户提交表单时,在表单元素中输入的值会被当作请求参数发送到服务器。
HTML输入域(文本域、隐藏域或密码域)或者文本域的值被当作一个字符串发送到服务器。对于空白的输入域或者文本域将发送一条空白的字符串。因此,带有一个输入域名称的ServletRequest.getParameter()将永远不会返回null。
将复选框数据传递到 Servlet 程序
被选中的复选框将字符串“on”发送到服务器。没有被选中的复选框则不发送任何内容到服务器,并且ServletRequest.getParameter(fieldname)返回null。
项目结构:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="indexServlet" method="POST" target="_blank">
<input type="checkbox" name="runoob" checked="checked" /> 菜鸟教程
<input type="checkbox" name="google" /> Google
<input type="checkbox" name="taobao" checked="checked" /> 淘宝
<input type="submit" value="选择站点" />
</form>
</body>
</html>
IndexServlet.java
@WebServlet(urlPatterns={
"/indexServlet"})
public class IndexServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
// 设置响应内容类型
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
String title = "读取复选框数据";
String docType = "<!DOCTYPE html> \n";
out.println(docType +
"<html>\n" +
"<head><title>" + title + "</title></head>\n" +
"<body bgcolor=\"#f0f0f0\">\n" +
"<h1 align=\"center\">" + title + "</h1>\n" +
"<ul>\n" +
" <li><b>菜鸟按教程标识:</b>: "
+ req.getParameter("runoob") + "\n" +
" <li><b>Google 标识:</b>: "
+ req.getParameter("google") + "\n" +
" <li><b>淘宝标识:</b>: "
+ req.getParameter("taobao") + "\n" +
"</ul>\n" +
"</body></html>");
}
}
运行效果:
将单选框数据传递到 Servlet 程序
单选按钮将被选按钮的值发送到服务器。如果没有选中任何按钮,则不发送任何内容到服务器,并且ServletRequest.getParameter(fieldname)返回null。
selectgender.html
GenderServlet.Java
运行效果:
在地址栏输入:http://localhost:8085/app01_form/`selectgender.html`,并跳转
读取所有的表单参数(getParameterNames() 方法读取所有可用的表单参数)
以下是通用的实例,使用 HttpServletRequest 的 getParameterNames()
方法读取所有可用的表单参数。该方法返回一个枚举,其中包含未指定顺序的参数名。 一旦我们有一个枚举,我们可以以标准方式循环枚举,使用
hasMoreElements() 方法来确定何时停止,使用 nextElement() 方法来获取每个参数的名称。
course.html
ReadParamsServlet.java
@WebServlet("/ReadParams")
public class ReadParamsServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置响应内容类型
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String title = "读取所有的表单数据";
String docType =
"<!doctype html public \"-//w3c//dtd html 4.0 " +
"transitional//en\">\n";
out.println(docType +
"<html>\n" +
"<head><meta charset=\"utf-8\"><title>" + title + "</title></head>\n" +
"<body bgcolor=\"#f0f0f0\">\n" +
"<h1 align=\"center\">" + title + "</h1>\n" +
"<table width=\"100%\" border=\"1\" align=\"center\">\n" +
"<tr bgcolor=\"#949494\">\n" +
"<th>参数名称</th><th>参数值</th>\n"+
"</tr>\n");
Enumeration paramNames = request.getParameterNames();
while(paramNames.hasMoreElements()) {
String paramName = (String)paramNames.nextElement();
out.print("<tr><td>" + paramName + "</td>\n");
String[] paramValues =
request.getParameterValues(paramName);
// 读取单个值的数据
if (paramValues.length == 1) {
String paramValue = paramValues[0];
if (paramValue.length() == 0)
out.println("<td><i>没有值</i></td>");
else
out.println("<td>" + paramValue + "</td>");
} else {
// 读取多个值的数据
out.println("<td><ul>");
for(int i=0; i < paramValues.length; i++) {
out.println("<li>" + paramValues[i]);
}
out.println("</ul></td>");
}
out.print("</tr>");
}
out.println("\n</table>\n</body></html>");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
运行效果:
在地址栏后面输入course.html,并跳转