JavaWeb之Servlet程序教程

说明:
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应用程序功能。

Servlet.png

案例实操

先创建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程序的过程:

  1. 用户在浏览器地址栏输入http://localhost:8080/day02/hello发送请求
  2. Tomcat服务器接收到请求读取web.xml中<url-pattern>标签
  3. 然后再通过<servlet-name>找到<servlet-class>
  4. 再通过<servlet-class>标签中的全类名定位到Servlet接口实现类
  5. 请求到达Servlet接口实现类会被Tomcat加载到内存中,同时创建一个请求对象,处理客户端请求;创建一个响应对象,响应客户端请求。
  6. Servlet 调用 service() 方法,传递请求和响应对象作为参数。
  7. service() 方法获取请求对象的信息,根据请求信息获取请求资源。
  8. 然后service()方法使用响应对象的方法,将请求资源响应给Servlet,最后回传给浏览器。
  9. 对于更多的客户端请求,Tomcat创建新的请求和响应对象,仍然调用此 Servlet 的 service() 方法,将这两个对象作为参数传递给它。如此重复以上的循环,但无需再次调用 init() 方法。一般 Servlet 只初始化一次(只有一个对象),当 Tomcat不再需要 Servlet 时(一般当 Server 关闭时),Tomcat调用 Servlet 的 destroy() 方法。


    多线程的Servlet生命周期.png

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程序的生命周期的执行流程:

  1. 执行空参构造器
  2. 执行Servlet程序初始化方法
  3. 执行service方法(对请求和响应进行处理)
  4. 执行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工具

image-20230724183216726.png

请求分发的执行流程:

  1. 用户点击form表单提交请求 <form action="xxx" method="post">
  2. Tomcat服务器收到请求,去web.xml中找到<url-pattern>标签value
  3. 找<servlet-mapping>标签<servlet-name>里面的value
  4. 通过<servlet-mapping>标签<servlet-name>里面的value找<servlet>标签中一致的<servlet-name>
  5. 然后再根据servlet找到<servlet-class>的value
  6. 用户发送的是 method="post" 请求,因此Tomcat找到HelloServlet2类中的doPost方法执行。

Servlet类的继承体系

extends.png

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>
相对路径跳转问题.png

请求跳转路径的问题演示

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>

可以看到根据地址栏转发报错:


图片.png

解决方案

此时可以使用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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容