美文网首页知识库
JavaWeb之Servlet程序教程

JavaWeb之Servlet程序教程

作者: Java_Evan | 来源:发表于2024-01-13 18:26 被阅读0次

    说明:
    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>
    

    相关文章

      网友评论

        本文标题:JavaWeb之Servlet程序教程

        本文链接:https://www.haomeiwen.com/subject/hujfodtx.html