说明:
Servlet3.0及之后版本都是基于注解配置开发。
Servlet2.5及之前版本基于web.xml进行相关配置。
本次教程基于Servlet2.5版本。
什么是Servlet?
Servlet是使用Java语言编写的运行在Web服务器上的小型应用程序,用于接收客户端发送的请求并响应数据给客户端。
Servlet是一个接口,也是开发Web项目的规范之一,任何实现了Servlet接口的自定义类都可以实现请求响应的效果。通常,Servlet处理一般基于HTTP协议的Web服务器上的请求。它是JavaWeb三大组件之一。
Servlet的主要功能包括:
- 接收客户端发送的请求:当用户在Web浏览器中输入URL或点击链接时,Web服务器会调用相应的Servlet来处理请求。
- 生成动态Web内容:Servlet可以根据请求参数、会话信息或其他数据动态生成HTML、XML或其他格式的响应内容,并将其发送回客户端。
- 会话管理:Servlet可以维护客户端与服务器之间的会话状态,以便跟踪用户的活动和数据。
- 集成数据库:Servlet可以与数据库进行交互,执行CRUD(创建、读取、更新、删除)操作,并将结果呈现给用户。
- 过滤和拦截请求:Servlet可以用于实现请求过滤和拦截功能,例如对请求进行身份验证、授权、压缩等处理。
- 集成其他Web组件:Servlet可以与其他Web组件(如JSP、JSTL等)集成,以实现更复杂的Web应用程序。
总之,Servlet是Java Web开发中的重要组件,用于处理网络请求、生成动态Web内容和实现各种Web应用程序功能。
案例实操:
先创建web工程,创建过程不在此次教程范围,基于不同IDE创建web工程请自行google。
① 自定义类实现Servlet接口
public class HelloServlet implements Servlet {
/**
* service 方法是专门用来处理请求和响应的
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello Servlet 被访问了");
}
}
② 配置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 标签给 Tomcat 配置 Servlet 程序 -->
<servlet>
<!--servlet-name 标签 Servlet 程序起一个别名(一般是类名) -->
<servlet-name>helloServlet</servlet-name>
<!--servlet-class 是 Servlet 程序的全类名-->
<servlet-class>com.evan.java.HelloServlet</servlet-class>
</servlet>
<!--servlet-mapping 标签给 servlet 程序配置访问地址-->
<servlet-mapping>
<!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用-->
<servlet-name>helloServlet</servlet-name>
<!--url-pattern 标签配置访问地址 <br/>
/ 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/>
/hello 表示地址为:http://ip:port/工程路径/hello <br/>
-->
<url-pattern>/hello</url-pattern>
</servlet-mapping>
</web-app>
浏览器url执行Servlet程序的过程:
- 用户在浏览器地址栏输入http://localhost:8080/day02/hello发送请求
- Tomcat服务器接收到请求读取web.xml中<url-pattern>标签
- 然后再通过<servlet-name>找到<servlet-class>
- 再通过<servlet-class>标签中的全类名定位到Servlet接口实现类
- 请求到达Servlet接口实现类会被Tomcat加载到内存中,同时创建一个请求对象,处理客户端请求;创建一个响应对象,响应客户端请求。
- Servlet 调用 service() 方法,传递请求和响应对象作为参数。
- service() 方法获取请求对象的信息,根据请求信息获取请求资源。
- 然后service()方法使用响应对象的方法,将请求资源响应给Servlet,最后回传给浏览器。
-
对于更多的客户端请求,Tomcat创建新的请求和响应对象,仍然调用此 Servlet 的 service() 方法,将这两个对象作为参数传递给它。如此重复以上的循环,但无需再次调用 init() 方法。一般 Servlet 只初始化一次(只有一个对象),当 Tomcat不再需要 Servlet 时(一般当 Server 关闭时),Tomcat调用 Servlet 的 destroy() 方法。
Servlet程序的生命周期
实现Servlet规范接口演示Servlet程序的生命周期
public class HelloServlet implements Servlet {
public HelloServlet(){
System.out.println("1.执行Servlet程序的构造器");
}
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("2.执行Servlet程序的初始化方法");
}
/**
* service方法专门处理请求和响应的
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("3.执行Servlet程序的service方法:Hello Servlet访问了...");
}
@Override
public void destroy() {
System.out.println("4.执行Servlet程序的销毁方法");
}
}
Servlet程序的生命周期的执行流程:
- 执行空参构造器
- 执行Servlet程序初始化方法
- 执行service方法(对请求和响应进行处理)
- 执行Servlet程序的销毁方法(销毁方法在Tomcat服务器停止的时候执行)
结论:
生命周期:从出生到消亡的过程就是生命周期。对应Servlet中的三个方法:init(),service(),destroy()。
默认情况下:
第一次接收请求时,这个Servlet会进行实例化(调用构造方法)、初始化(调用init())、然后调用服务(调用service())。
从第二次请求开始,每一次都是调用服务。
当容器关闭时,其中的所有的servlet实例会被销毁,调用销毁方法。
Servlet程序的初始化时机
- Servlet实例Tomcat服务器只会创建一个,所有的请求都是这个实例去响应。
- 默认情况下,第一次请求时,tomcat才会去实例化,初始化,然后再服务.这样的好处是什么? 提高系统的启动速度 。 这样的缺点是什么? 第一次请求时,耗时较长。
- 因此得出结论: 如果需要提高系统的启动速度,当前默认情况就是这样。如果需要提高响应速度,我们应该设置Servlet的初始化时机。
- Servlet的初始化时机设置:
默认是第一次接收请求时,实例化,初始化。
我们可以通过<load-on-startup>
来设置servlet启动的先后顺序,将加载Servlet程序的初始化过程提前到项目启动时,数字越小,启动越靠前,最小值0。
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.evan.java.HelloServlet</servlet-class>
<!-- 设置Servlet程序的启动时机 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
Servlet的线程不安全问题
在Web应用程序中,一个Servlet可能同时被多个用户访问,此时Web容器会为每个用户创建一个线程来执行Servlet。如果Servlet不涉及共享资源,则无需担心多线程问题。但如果Servlet需要访问共享资源,必须确保其线程安全。
当Web容器处理多个线程产生的请求时,每个线程会执行单一的Servlet实例的service()方法。如果一个线程需要根据某个成员变量的值进行逻辑判断,但在判断过程中另一个线程改变了该变量的值,这可能导致第一个线程的执行路径发生变化。
由于Servlet是线程不安全的,应尽量避免在Servlet中定义成员变量。如果必须定义成员变量,应遵循以下原则:
不要修改成员变量的值。
不要根据成员变量的值进行逻辑判断。
尽量减少在doGet()或doPost()方法中使用同步代码,并在最小代码块范围内实施同步。
HttpServlet的使用
自定义类继承HttpServlet类,实现请求方式分发处理。
① 自定义子类继承HttpServlet类重写doGet()与doPost()
public class HelloServlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("get请求...");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post请求...");
}
}
② 配置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 标签给 Tomcat 配置 Servlet 程序 -->
<servlet>
<!--servlet-name 标签 Servlet 程序起一个别名(一般是类名) -->
<servlet-name>helloServlet2</servlet-name>
<!--servlet-class 是 Servlet 程序的全类名-->
<servlet-class>com.evan.java.HelloServlet2</servlet-class>
</servlet>
<!--servlet-mapping 标签给 servlet 程序配置访问地址-->
<servlet-mapping>
<!--servlet-name 标签的作用是告诉服务器,我当前配置的地址给哪个 Servlet 程序使用-->
<servlet-name>helloServlet2</servlet-name>
<!--url-pattern 标签配置访问地址 <br/>
/ 斜杠在服务器解析的时候,表示地址为:http://ip:port/工程路径 <br/>
/hello 表示地址为:http://ip:port/工程路径/hello <br/>
-->
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
</web-app>
③ 测试
方式1:使用form表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="http://localhost:8080/day02/hello2" method="post">
<input type="text" name="uname"><br/>
<input type="text" name="price"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
方式2:使用Postman工具
请求分发的执行流程:
- 用户点击form表单提交请求 <form action="xxx" method="post">
- Tomcat服务器收到请求,去web.xml中找到<url-pattern>标签value
- 找<servlet-mapping>标签<servlet-name>里面的value
- 通过<servlet-mapping>标签<servlet-name>里面的value找<servlet>标签中一致的<servlet-name>
- 然后再根据servlet找到<servlet-class>的value
- 用户发送的是 method="post" 请求,因此Tomcat找到HelloServlet2类中的doPost方法执行。
Servlet类的继承体系
ServletConfig类
概述
- ServletConfig 是Servlet程序的配置信息类。
- Servlet程序和ServletConfig对象都是由Tomcat创建,我们调用需要使用的相关信息即可。
- Servlet程序默认只在第一次访问的时候创建,ServletConfig是每个Servlet程序创建时,就创建一个对应的ServletConfig对象。
作用
- 可以获取Servlet程序的别名(web.xml中<servlet-name>的值)。
- 获取初始化参数<init-param>。
- 获取ServletContext对象。
public class HelloServlet3 implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("执行初始化方法....");
//获取web.xml中<servlet-name>的值,即Servlet程序别名
System.out.println("Servlet程序别名:" + servletConfig.getServletName());
//获取初始化参数值,即:<init-param>中<param-name>的参数值<param-value>
String username = servletConfig.getInitParameter("username");
System.out.println("初始化参数值:" + username);
//获取servletContext对象,即Application上下文环境
System.out.println("Servlet程序的上下文对象:" + servletConfig.getServletContext());
}
}
浏览器地址栏输入:http://localhost:8080/day02/hello3
控制台输出:
执行初始化方法....
Servlet程序别名:helloServlet3
初始化参数值:lisi
Servlet程序的上下文路径org.apache.catalina.core.ApplicationContextFacade@79c8f2b3
注意点:
子类继承HttpServlet类重写init方法时,必须显式的调用父类的init方法
//子类继承HttpServlet类重写其初始化方法时,必须显式调用父类的init方法
public class HelloServlet2 extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
System.out.println("重写初始化操作...");
}
}
ServletContext类
概述
- ServletContext是一个接口,表示Servlet程序上下文对象
- 一个web工程,只有一个ServletContext对象实例
- ServletContext对象是一个==域对象==
- ServletContext是在web工程部署启动时创建的,在web工程停止时销毁.
什么是域对象?
- 这里的域对象指web工程中的Servlet程序在执行期间存取的数据都保存在一个对象中。这个对象就是域对象(类似Map集合存取数据的概念)。
- 这里的域值存取数据的操作范围;范围指整个web工程。
域对象与Map对比
对象 | 存数据 | 取数据 | 删除数据 |
---|---|---|---|
Map | put() | get() | remove() |
域对象 | setAttribute() | getAttribute() | removeAttribute() |
作用
- 获取web.xml中配置的上下文参数context-param
- 获取当前的工程路径,格式:/工程路径
- 获取工程部署后在服务器磁盘上的绝对路径
- 跟Map一样存取数据
//获取ServletContext参数
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("接收到GET请求...");
//获取ServletConfig对象
ServletConfig config = getServletConfig();
//通过ServletConfig对象获取ServletContext对象,即web.xml中的上下文参数<context-param>
ServletContext context = config.getServletContext();
//获取<context-param>参数值
String username = context.getInitParameter("username");
System.out.println(username);
//获取<context-param>参数值
String password = context.getInitParameter("password");
System.out.println(password);
//获取当前Servlet程序上下文(即:/工程名 或 Servlet实例)
String contextPath = context.getContextPath(); // /day01_hello
System.out.println(contextPath);
/**
* 获取工程部署(编译)后在服务器磁盘上的绝对路径
* /斜杠被服务器解析地址为:http://IP:port/工程名/ 映射地址:IDEA中的web目录下
* D:\java-workspace\javaweb-pro\out\artifacts\day01_hello_war_exploded\
* D:\java-workspace\javaweb-pro\out\artifacts\day01_hello_war_exploded\imgs
* day01_hello_war_exploded相当于web工程的web目录
*/
System.out.println(context.getRealPath("/"));
System.out.println(context.getRealPath("/imgs"));
}
<!-- context-param是上下文参数(属于整个web工程),可配置多个 -->
<context-param>
<param-name>username</param-name>
<param-value>context</param-value>
</context-param>
<!-- context-param是上下文参数(属于整个web工程) -->
<context-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</context-param>
输出结果:
接收到GET请求...
hangman
123456
/day01_hello
D:\java-workspace\javaweb-pro\out\artifacts\day01_hello_war_exploded
D:\java-workspace\javaweb-pro\out\artifacts\day01_hello_war_exploded\imgs
在ServletContext中存取数据
public class ContextServlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletConfig servletConfig = getServletConfig();
ServletContext context = servletConfig.getServletContext();
System.out.println("保存之前的数据:" + context.getAttribute("key1"));
context.setAttribute("key1","value1");
System.out.println("context1中获取的值:" + context.getAttribute("key1"));
}
}
public class ContextServlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = getServletContext();
System.out.println("context2获取域中的key1的值:" + servletContext.getAttribute("key1"));
}
}
<servlet>
<servlet-name>contextServlet1</servlet-name>
<servlet-class>com.evan.java.ContextServlet1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>contextServlet1</servlet-name>
<url-pattern>/context1</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>contextServlet2</servlet-name>
<servlet-class>com.evan.java.ContextServlet2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>contextServlet2</servlet-name>
<url-pattern>/context2</url-pattern>
</servlet-mapping>
结论:
当首次获取域对象中的数据,域对象为null,当向域对象中添加了数据,可以获取指定参数的值,添加的数据会一直保存在整个web工程中,其它Servlet程序也可以获取域对象中的参数值,直到web工程停止。
HttpServletRequest类
客户端每次发送请求进入 Web容器中,Web容器就会把请求过来的 HTTP 协议信息解析好封装到 Request 对象中。 然后传递到 service 方法(doGet 和 doPost)中给我们使用。我们可以通过 HttpServletRequest 对象,获取到所有请求的信息。
常用方法
getRequestURI() 获取请求的资源路径
getRequestURL() 获取请求的统一资源定位符(绝对路径)
getRemoteHost() 获取客户端的 ip 地址
getHeader() 获取请求头
getParameter() 获取请求的参数
getParameterValues() 获取请求的参数(多个值的时候使用)
getMethod() 获取请求的方式 GET 或 POST
setAttribute(key, value); 设置域数据
getAttribute(key); 获取域数据
getRequestDispatcher() 获取请求转发对象
public class RequestAPIServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("请求相对路径:" + req.getRequestURI());
System.out.println("请求绝对路径:" + req.getRequestURL());
System.out.println("客户端IP:" + req.getRemoteHost());
System.out.println("请求头:" + req.getHeader("user-Agent"));
// 获取请求参数
String username = req.getParameter("username");
//解决get请求的中文乱码
//1 先以 iso8859-1 进行编码
//2 再以 utf-8 进行解码
//username = new String(username.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
System.out.println(username);
var pwd = req.getParameter("pwd");
System.out.println(pwd);
System.out.println("请求方式:" + req.getMethod());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("请求相对路径:" + req.getRequestURI());
System.out.println("请求绝对路径:" + req.getRequestURL());
System.out.println("客户端IP:" + req.getRemoteHost());
System.out.println("请求头:" + req.getHeader("user-Agent"));
//设置请求体的字符集为UTF-8,解决POST请求中文乱码
//字符集设置要在获取请求参数前
req.setCharacterEncoding("UTF-8");
var username = req.getParameter("username");
/*byte[] bytes = username.getBytes("ISO-8857-1");
var data = new String(bytes, StandardCharsets.UTF_8);
System.out.println(data);*/
System.out.println(username);
var pwd = req.getParameter("pwd");
System.out.println(pwd);
System.out.println("请求方式:" + req.getMethod());
}
}
HttpServletResponse类
HttpServletResponse类和HttpServletRequest类一样,都会在浏览器发送请求之后,Web容器会创建一个对象传给Servlet程序中的service(doGet和doPost)方法使用。只不过HttpServletResponse表示将请求信息进行处理后响应给浏览器,HttpServletRequest表示获取浏览器发送的请求信息。
输出流的说明
字节流 getOutputStream(); 常用于下载二进制数据文件(传递二进制数据)
字符流 getWriter(); 常用于回传字符串数据(常用)
使用了字符流,就不能使用字节流;反之亦然,否则报错java.lang.IllegalStateException
public class MyHttpServletResponse extends HttpServlet {
//获取字符流响应数据
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解决响应数据中文乱码
//设置浏览器与服务器都使用utf-8字符集,并设置了响应头
//此方法要在获取流对象之前使用
resp.setContentType("text/html;charset=UTF-8");
//获取字符流对象
var writer = resp.getWriter();
//服务端向客户端响应数据
writer.write("Hello,Browser,我是服务端!");
}
//获取字节流响应数据
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//resp.setContentType("text/html;charset=UTF-8");
//var os = resp.getOutputStream();
//os.write("哈哈哈哈哈".getBytes(StandardCharsets.UTF_8));
String str = "我是ABC";
var os = resp.getOutputStream();
byte[] buff = str.getBytes(StandardCharsets.UTF_8);
var data = new String(buff, StandardCharsets.ISO_8859_1);
os.print(data);
System.out.println(data);
}
}
响应报文给客户端的中文乱码问题:
解决中文乱码必须放到获取流之前。
//推荐使用
// 它会同时设置服务器和客户端都使用 UTF-8 字符集,还设置了响应头
// 此方法一定要在获取流对象之前调用才有效
resp.setContentType("text/html;charset=UTF-8");
// 不推荐使用
// 设置服务器字符集为 UTF-8
resp.setCharacterEncoding("UTF-8");
// 通过响应头,设置浏览器也使用 UTF-8 字符集
resp.setHeader("Content-Type", "text/html; charset=UTF-8");
请求转发与重定向
请求转发
请求转发指把客户端发送过来的请求交给指定Servlet程序,而内部把该请求转交给另外一个Servlet程序进行处理,完成之后响应给客户端(响应的过程中会路过转交的Servlet)。
特点:
因为请求转发是内部处理请求信息,所以客户端看不到处理请求的方式;客户端发送一次请求,内部会把请求信息存储到共享域中,然后转交给其他Servlet处理,该请求只能转发到web目录下,不可以访问web工程外的资源。
- 地址栏不会改变
- 发送一次请求
- 可以访问WEB-INF下的资源
- 不可以访问web工程外的资源
- 可以获取共享Request域中的数据
//Servlet程序1
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("servlet1:请求处理");
//获取请求参数
var username = req.getParameter("username");
System.out.println(username);
//向Servlet共享域中添加数据
req.setAttribute("key1","李四");
//告诉请求->转发到servlet2程序地址:请求转发必须以/开头,/表示上下文路径,映射到web目录
var requestDispatcher = req.getRequestDispatcher("/servlet2");
//将请求信息定位到转发信息处,即跳转至Servlet2
requestDispatcher.forward(req,resp);
}
}
//Servlet程序2
public class Servlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求参数
var username = req.getParameter("username");
//获取Servlet共享域中的数据
var key1 = req.getAttribute("key1");
System.out.println(key1);
System.out.println("servlet2:处理请求信息...");
}
}
<!-- Servlet程序1 -->
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class>com.evan.java.Servlet1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet1</servlet-name>
<url-pattern>/servlet1</url-pattern>
</servlet-mapping>
<!-- Servlet程序2 -->
<servlet>
<servlet-name>servlet2</servlet-name>
<servlet-class>com.evan.java.Servlet2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet2</servlet-name>
<url-pattern>/servlet2</url-pattern>
</servlet-mapping>
请求转发使用相等路径的问题
使用相对路径在工作时都会参照浏览器地址栏中的地址来进行跳转;所以当浏览器发送请求跳转的路径是相对路径时,会找地址栏中的地址;但使用请求转发时,地址栏中的地址始终都是请求跳转前的路径,此时相对路径去找地址栏中地址进行参照时,会出现路径错误情况。
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是 a 下的 b 下的 c.html 页面<br/>
<a href="../../index.html">跳回首页</a><br/>
</body>
</html>
请求跳转路径的问题演示
public class Servlet3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("servlet3:请求转发");
req.getRequestDispatcher("a/b/c.html").forward(req,resp);
}
}
<servlet>
<servlet-name>servlet3</servlet-name>
<servlet-class>com.evan.servlet.Servlet3</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servlet3</servlet-name>
<url-pattern>/s3</url-pattern>
</servlet-mapping>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>c.html</title>
<base href="http://localhost:8080/day02/a/b/" hidden="hidden"/>
</head>
<body>
这是 a目录下的 b目录下的 c.html 页面<br/>
<a href="../../index.jsp">跳回首页</a><br/>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>index.jsp</title>
</head>
<body>
<a href="a/b/c.html">a/b/c.html</a><br/>
<a href="http://localhost:8080/day02/s3">请求转发:a/b/c.html</a>
</body>
</html>
可以看到根据地址栏转发报错:
解决方案
此时可以使用bose标签设置相对路径工作时参照的地址。
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--base标签设置页面相对路径工作时参照的地址
href 属性就是参数的地址值
-->
<base href="http://localhost:8080/day01-hello/a/b/">
</head>
<body>
这是 a 下的 b 下的 c.html 页面<br/>
<a href="../../index.html">跳回首页</a><br/>
</body>
</html>
说明:
. 表示当前路径
.. 表示上一级路径
在实际开发中,尽量使用绝对路径。
请求重定向
指客户端(浏览器)给服务器发送请求,服务器收到请求后告诉客户端,该请求有了新地址,请到新地址去访问,叫请求重定向(之前的地址可能废弃,需要重新访问新地址)。
特点:
- 浏览器地址栏发生改变
- 发送两次请求
- 不能共享Request域数据
- 不能访问WEB-INF目录下的资源
- 可以访问工程外部资源
public class Request1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("r1:请求地址已废弃!");
//方式一
//设置响应状态码,302表示重定向
//resp.setStatus(302);
//设置响应头,说明新地址在哪里
//resp.setHeader("location","http://localhost:8080/day03-servlet/r2");
//方式二
//设置重定向地址r2,内部封装重定向状态码302
//
resp.sendRedirect("https://www.baidu.com");
resp.sendRedirect("http://localhost:8080/day03-servlet/r2");
}
}
public class Request2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("r2:正在处理请求...");
//响应数据到客户端
resp.getWriter().write("r2:请求已处理");
}
}
<!-- request1 -->
<servlet>
<servlet-name>request1</servlet-name>
<servlet-class>com.evan.java.Request1</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>request1</servlet-name>
<url-pattern>/r1</url-pattern>
</servlet-mapping>
<!-- request2 -->
<servlet>
<servlet-name>request2</servlet-name>
<servlet-class>com.evan.java.Request2</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>request2</servlet-name>
<url-pattern>/r2</url-pattern>
</servlet-mapping>