JavaWeb-学习笔记

news2024/9/23 11:15:22

第一章 Tomcat 介绍

前言

使用Tomcat服务器,必须先安装JDK,因为Tomcat使用的是java语言开发的。

在系统环境变量中,必须包含**%JAVA_HOME%\bin**。否则Tomcat无法启动

环境变量配置:

  1. 此电脑 --> 右键 --> 属性
  2. 高级系统设置
  3. 环境变量
  4. 系统环境变量 --> 新建
  5. 变量名: JAVA_HOME
  6. 变量值:D:\java\jdk-17.0.5
  7. 确定
  8. 点击 Path – > 编辑 --> 新建
  9. %JAVA_HOME%\bin
  10. 确定

测试:

  1. win + R
  2. 输入:cmd
  3. 测试命令1:java -version
  4. 测试命令2:javac -version
  5. 测试命令3:java

1. 下载Tomcat

链接:Apache Tomcat® - Welcome!

2. 安装

  • 解压下载后的压缩包
  • 解压后的文件就是Tomcat服务器的文件
  • Tomcat服务器文件所在的位置统称为 CATALINA_HOME

3. 目录介绍

  • bin: Tomcat 服务器命令文件存放的目录,比如:启动Tomcat,关闭Tomcat等。
  • conf: Tomcat 服务器配置文件存放的目录。
  • lib: Tomcat服务器的核心程序目录。因为Tomcat 服务器是java语言编写的,这里面的jar包都是class字节码文件。
  • logs: Tomcat 服务器的日志信息目录。
  • temp: Tomcat服务器的临时文件目录。
  • webapps: Tomcat服务器 存放 webapp 文件的目录(web application :web应用)。
  • work: Tomcat服务器 存放 JSP文件解析后的Java文件及编译后的class字节码文件。

4. 启动/关闭Tomcat

  • bin目录:
    • 双击 startup.bat 启动
      • 实际上系统会执行 catalina.bat 文件
      • 在catalina.bat 文件中,有这样一行配置:MAINCLASS=org.apache.catalina.startup.Bootstrap 类。
      • 这个类相当于 main方法。
      • 想要了解的可以查看源码。
    • 双击 shutdown.bat 关闭
      • 关闭可以直接关闭窗口,但是这样是不建议的,这是非正常关闭。
      • 双击 shutdown.bat 正常关闭。
  • 解决Tomcat启动终端乱码的问题:
    • conf目录下,打开 **logging.properties**文件,修改 51行为:
    • java.util.logging.ConsoleHandler.encoding = GBK

第二章 Tomcat入门

1. 实现第一个web程序

  • 第一步:找到**CATALiNA_HOME\webapps**目录

    • 因为所有的webapp要放到**webapps目录下,Tomcat 服务器加载web应用时,会在webapps**目录下查找webapp资源,如果放到其他地方 Tomcat会找不到资源。
  • 第二步: 在**CATALINA_HOME\webapps**目录下,新建子目录: hello

    • 这里新建的目录就是 webapp 的名字。
  • 第三步:在目录 **hello**编写web资源文件,例如:index.html

    • <!Doctype html>
      <html>
      	<head>
              <meta charset="UTF-8">
      		<title>index page</title>
      	<head>
      	<body>
      		<h1>Hello World!</h1>		
      	</body>
      </html>
      
  • 第四步:启动**Tomcat服务器,双击startup.bat**.

  • 第五步:打开浏览器

    • 输入地址:http://127.0.0.1:8080/hello/index.html
    • 或者:http://localhost:8080/hello/index.html
    • 或者:http://localhost:8080/hello/
    • 为什么第三个地址也能访问到呢?
      • 因为 在web应用中,启动一个web项目,在不指定欢迎页的前提下,服务器会默认启动项目目录下的index.html资源。

2. 为什么需要启动Tomcat服务器?

  • 我们可以直接双击 打开这个index.html,为什么还要启动 Tomcat服务器呢?

    因为我们现在编写的**webapp项目,将来是要部署到服务器上的,用户客户端上没有这些资源,无法访问到这个webapp**项目,所以要启动 Tomcat服务器 ,把资源共享到网络中,这样用户就可以通过网络访问这个资源了。

  • 实现资源的共享

    因为启动了Tomcat服务后,可以使用超链接访问其他网络资源。

3. Tomcat服务器访问地址URL

  • 使用**http://** 协议 + 服务器地址:端口号 + 资源路径

  • 如果是在资源内访问其他资源:

    • 使用根路径:/hello/login.html

      • 根路径是 CATALINA_HOME\webapps,可以使用根路径访问其他web资源。

      • 例如:

        <!--使用根路径访问资源-->
        <a href="/hello/login.html">登录</a>
        
    • 使用相对路径:./login.html

      • 相对路径是当前资源所在的目录
      • ./ 表示当前目录
      • **../**表示当前目录的上一级
      • 直接资源名,表示当前目录下的资源。
      • **/资源目录**表示当前目录的下一级。
    • 使用绝对路径:“D:\software\tomcat\apache-tomcat-11.0.0-M4-windows-x64\apache-tomcat-11.0.0-M4\webapps\hello\login.html”

      • 不建议使用,当资源移动时,链接对应的资源就访问不到了。

第三章 Servlet介绍

1. 什么是Servlet?

Servlet是运行在web服务器端(web容器,如tomcat)的程序,它与Applet相对,Applet是运行在客户端的程序。

Servlet的主要作用是处理客户端的请求,并把处理结果响应给客户端。生成动态网页。

2. Servlet的本质

Servlet的本质是实现了javax.servlet.Servlet接口的Java类。javax.servlet.GenericServlet实现了Servlet接口,实现了Servlet基本的特征和功能,能够接受客户端发出的请求和产生响应信息。而javax.servlet.http.HttpServlet又继承了javax.servlet.GenericServlet类,所以我们编写的Servlet继承HttpServlet即可。

3. 实现第一个Servlet的webapp

  • 第一步: 在**webapps**目录下新建一个项目目录: crm

  • 第二步:在项目目录下新建一个目录:WEB-INF

    • 注意:这个目录的名称是 Servlet规范中规定的,必须全部大写。
  • 第三步:在**WEB-INF目录下新建一个目录:classes**

    • 注意: 这个目录名称也是Servlet规范中规定的,必须全部小写。
    • 这个目录下一定存放是**java程序编译后class文件**(字节码文件)。
  • 第四步:在**WEB-INF目录下新建一个目录:lib**

    • 注意:这个目录不是必须的。但是如果这个 **webapp**需要依赖第三方的jar包,则这个jar包必须放到 **lib**目录下,这个也必须全部小写。
  • 第五步:在 WEB-INF目录下新建一个目录:web.xml

    • 注意:这个文件必须是在**WEB-INF目录下,且名字必须是web.xml**。

    • 这个是一个配置文件,这个配置文件描述了请求路径和Servlet类之间的对照关系。

    • **web.xml**文件格式:

      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                            https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
        version="6.0"
        metadata-complete="true">
      
      
      </web-app>
      
  • 第六步: 编写一个java程序,这个程序必须实现**Servlet**接口.

    • 这个**Servlet**类在Tomcat服务器中使用了。

    • Servlet接口的jar包在Tomcat服务器的lib\servlet-api.jar

    • Servlet接口之前的包名:javax.servlet.Servlet

    • 从**JavaEE9开始,Servlet接口包名改为了:jakarta.servlet.Servlet**

    • 现在**JavaEE9**不叫这个了,改名叫 **JakartaEE9**了。

    • 在**Tomcat10之前的使用的是javax,从Tomcat10及以后的都是jakarta**了。

    • 编写程序:

      package com.powernode.servlet;
      
      import jakarta.servlet.Servlet;
      import jakarta.servlet.ServletException;
      import jakarta.servlet.ServletConfig;
      import jakarta.servlet.ServletRequest;
      import jakarta.servlet.ServletResponse;
      import java.io.IOException;
      import java.io.PrintWriter;
      
      public class HelloServlet implements Servlet {
      	
      	public void init(ServletConfig config) throws ServletException{
      
      	}
      
      	public void service(ServletRequest request,ServletResponse response)
      		throws ServletException,IOException {
              
              // 向控制台数据信息
      		System.out.print("Hello,Servlet!");
              
              // 设置响应的内容类型是普通文本或html代码
              response.setContentType("text/html");
              // 从服务端向客户端浏览器发送数据的数据流
              PrintWriter out = response.getWriter();
      		// 在浏览器上输出信息。
      		out.print("Hello,Servlet!");
              // 向浏览器输出html代码
              out.print("<h1>Hello,Servlet!</h1>");
      	}
      
      	public void destroy(){
      
      	}
      	
      	public String getServletInfo(){
           	return "";
      	}
      
      	public ServletConfig getServletConfig(){
      		return null;
      	}
      	
      }
      
  • 第七步:编译java程序。

    • java编译命令: javac -d . *.java
    • 因为java程序中实现了**Servlet接口所有要导入jakarta.servlet.Servlet**包。
    • 编译依然无法进行,因为编译的时候,这个包找不到,我们要将这个包所在的路径添加到系统环境变量下。
    • 具体实现:在系统环境变量中 新建
      • 变量名:CLASSPATH
      • 变量值:"D:\software\tomcat\apache-tomcat-11.0.0-M4-windows-x64\apache-tomcat-11.0.0-M4\lib\servlet-api.jar"
  • 第八步:将生成的目录**class字节码文件移动到第三步的classes**目录下。

  • 第九步:编写第五步创建的**web.xml**文件,让“请求路径"和"servlet类"关联在一起。(注册Servlet类)

    • 具体实现:

      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                            https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
        version="6.0"
        metadata-complete="true">
      
          <!-- servlet描述信息 -->
          <servlet>
              <!-- 给这个servlet类起的映射名 -->
              <servlet-name>helloServlet</servlet-name>
              <!-- 类的全限定类名 -->
              <servlet-class>com.powernode.servlet.HelloServlet</servlet-class>
          </servlet>
          <!-- servlet映射信息 -->
          <servlet-mapping>
              <!-- 请求路径对应的servlet类的映射名 -->
              <servlet-name>helloServlet</servlet-name>
              <!-- 请求路径 以:/ 开头-->
              <url-pattern>/myServlet</url-pattern>
          </servlet-mapping>
      
      </web-app>
      
  • 第十步:启动Tomcat服务器,双击 startup.bat

  • 第十一步:浏览器访问地址: http://127.0.0.1:8080/crm/myServlet

    • crm: 表示 webapp 应用的项目名
    • myServlet:**web.xml配置文件中Servlet类**对应的请求路径。
  • 第十二步:观察中终端。

    • 输出了:Hello,Servlet!
    • 由此可知,当我们访问一个Servlet 类时,Tomcat会调用Servlet类里面的Service方法。

注意:如果编写的资源想要通过客户端浏览器地址栏访问到,资源一定不能放到**WEB-INF**目录下。

​ **WEB-INF**目录下的资源,不能通过客户端访问到,只能是服务端访问。

​ 如果一些资源不想公开,则将资源放到**WEB-INF**目录下。

​ 公开的资源一定要放到**WEB-INF**目录外。

在超链接及form表单中访问**WEB-INF**目录下的资源为什么可以?

​ 因为这些请求访问的路径都是服务端的请求转发得到的,是在服务端请求的,所以能够访问。

原理: 当你点击 超链接或提交表单的时候,你只是将你想要访问的资源目录告诉了服务器,由服务器端进行请求转发,将资源返回给客户端。并没有直接的去访问**WEB-INF**目录下的资源。

第四章 Servlet连接数据库

1. 创建数据库

在终端输入:

mysql -uroot -p123456

-- 创建数据库
create database javawebdb;
-- 使用数据库
use javawebdb;
-- 创建数据表
create table t_student(
    no int,
    name varchar(20),
    gender char(2)
);
-- 插入数据
insert into t_student values(1,'张三','男'),(2,'李四','女'),(3,'王五','男');
-- 查看数据
select * from t_student;

2. 编写Servlet类

编写一个StudentServlet类

package com.powernode.servlet;

import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;

public class StudentServlet implements Servlet{

    public void init(ServletConfig config) throws ServletException {
    }

    public void service(ServletRequest request,ServletResponse response) throws ServletException,IOException{
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
       
        Connection conn = null;
        PreparedStatement ps =null;
        ResultSet rs = null;

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/javawebdb";
            String user = "root";
            String password = "123456";
            conn = DriverManager.getConnection(url,user,password);
            
            String sql= "select no,name,gender from t_student";

            ps = conn.prepareStatement(sql);

            rs = ps.executeQuery();

            while(rs.next()){
                int no = rs.getInt("no");
                String name = rs.getString("name");
                String gender = rs.getString("gender");
                System.out.println("no:"+no+",name:"+name+",gender:"+gender);
                out.print("<h3> no:"+no+"---name:"+name+"---gender:"+gender);
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }finally {
            if(conn!=null){
                try {
                    conn.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            if(ps !=null){
                try{
                    ps.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            if(rs !=null){
                try{
                    rs.close();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
        
    }

    public void destroy(){

    }

    public String getServletInfo(){
        return "";
    }

    public ServletConfig getServletConfig(){
        return null;
    }
}

3. 编译Servlet类

javac -d . *.java

4. 导入字节码文件

移动到webapp 项目 - -> WEB-INF --> classes

5. 编写配置文件

<!-- servlet描述信息 -->
<servlet>
    <!-- 给这个servlet类起的映射名 -->
    <servlet-name>studentServlet</servlet-name>
    <!-- 类的全限定类名 -->
    <servlet-class>com.powernode.servlet.StudentServlet</servlet-class>
</servlet>
<!-- servlet映射信息 -->
<servlet-mapping>
    <!-- 请求路径对应的servlet类的映射名 -->
    <servlet-name>studentServlet</servlet-name>
    <!-- 请求路径 以:/ 开头-->
    <url-pattern>/student</url-pattern>
</servlet-mapping>

6. 导入mysql驱动包

找到mysql驱动包 移动到 webapp 项目 - -> WEB-INF --> lib下。

7. 访问

浏览器地址栏输入: http://127.0.0.1:8080/crm/student

8. 结果

no:1—name:张三—gender:男

no:2—name:李四—gender:女

no:3—name:王五—gender:男

第五章 IDEA部署Tomcat

1. 新建项目

选择空项目:Empty Project
在这里插入图片描述

2. 新建模块

项目名上右键 --> New --> Module…

在这里插入图片描述

编辑模块名: Serlvet01

在这里插入图片描述

3. 添加Web框架

在这里插入图片描述

在这里插入图片描述

4. 添加依赖的包

打开项目结构

在这里插入图片描述

导入依赖的Servlet-api.jar

在这里插入图片描述

5. 编辑程序

在这里插入图片描述

程序代码:

package com.powernode.javaweb.servlet;

import jakarta.servlet.*;

import java.io.IOException;

/**
 * @Date: 2023/4/25-14:04
 * @Author: HGA
 * @Class: HelloServlet
 * Description:
 */


public class HelloServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        System.out.println("Hello,Servlet!");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

6. 编辑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>helloServlet</servlet-name>
        <servlet-class>com.powernode.javaweb.servlet.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>helloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

7. 部署Tomcat服务器

在这里插入图片描述

加粗样式

在这里插入图片描述
在这里插入图片描述

8. 启动Tomcat服务器

在程序开发阶段,使用Debug模式启动。

9. 访问

浏览器地址栏输入:http://localhost:8080/Servlet01/

第六章 Servlet对象的生命周期

1. 什么是Servlet对象的生命周期?

  • Servlet对象什么时候被创建?
  • Sevvlet对象什么时候被销毁?
  • Servlet对象创建了几个?
  • Servlet对象的生命周期表示:
    • 一个Servlet对象在出生到最后的死亡,整个过程是怎样的?

2. Servlet对象是由谁来维护的?

  • Servlet对象的创建,对象上的方法调用,对象最终的销毁,Javaweb程序员无权干预。
  • Servlet对象的生命周期是由**Tomcat服务器(WEB Server)**全权负责的。
  • Tomcat服务器通常我们又称为:WEB容器。(WEB Container)
  • WEB容器是管理Servlet对象的。

思考: 我们自己new的Servlet对象,受WEB容器管理吗?

  • 我们自己new的Servlet对象是不受WEB容器管理的。
  • WEB容器创建的Servlet对象,这些Servlet对象都会被放到一个集合中(HashMap),只有这个HashMap集合中的Servlet对象才能被WEB容器管理,自己new的Servlet对象没有放到WEB容器中,所以不受WEB容器的管理。
  • WEB容器底层有一个HashMap这样的集合,在这集合中存储了Servlet对象和请求路径的之间的映射关系。

3. Servlet的创建

  1. 在服务器启动时,Servlet对象有没有被创建出来?(默认情况下)

    • 在Servlet中提供一个无参构造方法,启动Tomcat服务器,观察控制台结果。

      • 发现并没有输出无参构造方法中的 输出语句。
    • 经过测试,说明在默认情况下,Tomcat服务器启动时,不会创建Servlet对象。

  2. 为什么不在启动Tomcat服务器的时候创建出来呢?

    • 因为如果有1000个Servlet类对象,那么服务器启动都去创建对象,服务器是无法承受的。
    • 如果在启动的时候就创建出来,会造成服务器启动缓慢,内存承载过大。
    • 如果创建出来的Servlet没有用户访问,那么相当于增加了系统的负担,(不用却占用了资源)。
  3. 怎么让服务器启动的时候创建Servlet对象呢?

    • 在**web.xml**配置文件中,添加一个标签:

      <load-on-startup></load-on-startup>
      
    • 标签内填入一个整数,代表优先级,数字越低,优先级越高

      <load-on-startup>0</load-on-startup>
      
    • 这个标签放在想要在服务器启动的时候创建对象的Servlet标签中。

      <servlet>
          <servlet-name>helloServlet</servlet-name>
          <servlet-class>com.powernode.javaweb.servlet.HelloServlet</servlet-class>
          <!-- 在服务器启动时加载-->
          <load-on-startup>0</load-on-startup>
      </servlet>
      <servlet-mapping>
          <servlet-name>helloServlet</servlet-name>
          <url-pattern>/hello</url-pattern>
      </servlet-mapping>
      

4. Servlet生命周期

ServletLife 类:

package com.powernode.javaweb.servlet;

import jakarta.servlet.*;
import java.io.IOException;

/**
 * @Date: 2023/4/25-15:50
 * @Author: HGA
 * @Class: ServletLife
 * Description:
 */

public class ServletLife implements Servlet {

    public ServletLife(){
        System.out.println("Servlet's No-Parameter-Constructor method execute!");
    }

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("Servlet's init method execute!");
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("Servlet's service method execute!");
    }

    @Override
    public void destroy() {
        System.out.println("Servlet's destroy method execute!");
    }



    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }
}
  • 默认情况下,Tomcat服务器启动的时候,没有任何变化。

  • 当浏览器第一次发送请求时:(控制台变化)

    Servlet’s No-Parameter-Constructor method execute!
    Servlet’s init method execute!
    Servlet’s service method execute!

  • 根据以上输出内容得出结论:

    • 客户端第一次请求的时候Servlet对象被实例化了**ServletLife()**(Servlet‘s No-Parameter-Constructor method execute!)。
    • ServletLife 对象被创建出来了之后,Tomcat服务器马上又调用了ServletLife对象的 init() 方法。(Servlet’s init method execute!)。
    • 紧接着,又调用了ServletLife对象的**service()方法**。(Servlet’s service method execute!)。
  • 当浏览器再次发送请求的时:(控制台变化)

    Servlet’s No-Parameter-Constructor method execute!
    Servlet’s init method execute!
    Servlet’s service method execute!
    Servlet’s service method execute!
    Servlet’s service method execute!

  • 根据输出的结果我们得出结论:

    • 构造方法只被调用了一次,后面请求的是同一个Servlet对象。(假单实例模式)

      • 之所以称为单例,是因为Servlet对象的创建我们程序员管不着,这个对象只能是Tomcat服务器来创建,Tomcat之创建了一个,所以导致了单例。
      • 但是这了的单例模式是假单例模式,不符合单例模式的要求,构造方法没有私有化。
    • **init()**方法也只被调用了一次。

      • **init()**方法被用来初始化某些特殊请求时用。
    • **service()**方法,用户每发一次请求,就调用一次。

      • **service()**方法被用来处理用户的请求。
  • 当我们关闭服务器时:(控制台变化)

    Servlet’s destroy method execute!

  • 根据输出结果我们得出结论:

    • **destroy()**方法也只被调用一次,就是当我们关闭服务器时,执行的。
    • 也就是,当我们关闭服务器的时候,要销毁ServletLife对象的内存,
    • 服务器在销毁ServletLife对象内存之前,Tomcat服务器会自动的调用ServletLife对象的 **destroy()**方法。
    • **destroy()**用来处理资源的释放等最终操作。

第七章 GenericServlet适配器

1. 适配器模式

在实现Servlet接口后,要重写接口中的所有方法,而我们最多用到的只是**service()**方法,其他方法用的很少,这就导致我们写的Servlet类,过于臃肿,代码也不美观,所以没有必要实现这么多方法。

适配器模式,就很好的帮我们解决了这个问题,我们只需要实现我们常用的方法就行,其他方法,用到了就实现,不用就不实现。

适配器模式:

​ 适配器抽象类 去实现 Servlet 接口,重写所有的方法,把我们常用的**service()**方法抽象化,这样当我们写Serevlet方法时,就去继承这个适配器抽象类,只需要重写我们常用的方法就行。

ServletAdapter 适配器抽象类:

public abstract class GenericServlet implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public abstract  void service(ServletRequest servletRequest, ServletResponse servletResponse);

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

编写Servlet类:

public class UserServlet extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) {
        System.out.println("我继承了GenericServlet适配器!");
    }
}

2. 优化GenericServlet适配器

我们知道,在实现Servlet方法后,init()方法有一个参数 ServletConfig ,我们主要用的是service()方法,但是,有时候又要用到init()方法中的ServletConfig 参数,当我们继承**GenericServlet适配器的时候,只继承了service()方法,没有办法使用到init()方法中的ServletConfig** 参数。当然我们也可以在Serlvet类中继续继承**GenericServlet适配器类中的init()**方法,这样又导致代码不美观了,因为,我们只是用到了一个参数,却要重写一个方法。那么怎么解决这个问题呢?

只需要在适配器中添加一个成员变量**ServletConfig,在init()方法中实现初始化就行,就可以把这个ServletConfig**的作用域提高的类的全局变量上了。然后通过 **getServletConfig()**方法就可以获取到了。

这时候遇到一个问题:

​ 如果在我们编写Servlet类的时候,重写了**init()**方法,那么在Tomcat服务器调用 **init()**方法时,就不会去调用适配器 **GenericServlet父类中的init()**方法了,这样,我们之前在适配器 GenericServlet父类中定义的成员变量就会为空。导致,在子类中用的getServletConfig()方法就会为null。所以我们一定不能让适配器 **GenericServlet父类中的init()**方法,被子类修改重写。所以在适配器 **GenericServlet父类中的init()方法上添加一个final**关键字,就可以解决了。

那么又遇到了一个问题:

​ **init()**方法不是用来初始化的吗?有时候我们编写的 Servlet 类需要一些只执行一次的操作时,就要用到 **init()方法,但是,上面的问题使用了final关键字,我们没有办法再我们编写的Servlet类中 重写init()**方法了,怎么办。

​ 解决这个问题,只需要再适配器 **GenericServlet**父类中重载 **init()方法,在final**修饰的 **init()**方法中,调用重载的 **init()方法,这样子类重写父类的init()**方法,就是我们 重载的 **init()**方法了。

public abstract class GenericServlet implements Servlet {
    
    private ServletConfig config;

    @Override
    public final void init(ServletConfig servletConfig) throws ServletException {
        this.config = servletConfig;
        this.init();
    }
    
    public void init(){
        
    }

    @Override
    public ServletConfig getServletConfig() {
        return config;
    }

    @Override
    public abstract  void service(ServletRequest servletRequest, ServletResponse servletResponse);

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

在编写的Servlet类中使用:

public class UserServlet extends GenericServlet {
    
    @Override
    public void init() {
        System.out.println("init方法执行了!");
    }
    
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) {
        System.out.println("我继承了GenericServlet适配器!");
        
        // 使用ServletConfig
        ServletConfig config = this.getServletConfig();
        
       	System.out.println(config);
    }
}

3. ServletConfig接口详解

  • 什么是ServletConfig?

    • Servlet对象的配置信息对象。
    • ServletConfig对象中封装了标签中的配置信息。(web.xml文件中servlet的配置信息)
  • 一个Servlet对应一个ServletConfig对象。

  • Servlet对象是Tomcat服务器创建,并且ServletConfig对象也是Tomcat服务器创建。并且默认情况下,他们都是在用户发送第一次请求的时候创建。

  • Tomcat服务器调用Servlet对象的init方法的时候需要传一个ServletConfig对象的参数给init方法。

  • ServletConfig接口的实现类是Tomcat服务器给实现的。(Tomcat服务器说的就是WEB服务器。)

  • ServletConfig接口有哪些常用的方法?

    public String getInitParameter(String name); // 通过初始化参数的name获取value
    public Enumeration<String> getInitParameterNames(); // 获取所有的初始化参数的name
    public ServletContext getServletContext(); // 获取ServletContext对象
    public String getServletName(); // 获取Servlet的name
    
    • 以上方法在Servlet类当中,都可以使用this去调用。因为GenericServlet实现了ServletConfig接口。
import jakarta.servlet.*;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

/**
 * ServletConfig
 *   1. ServletConfig是什么?
 *      jakarta.servlet.ServletConfig
 *      显然ServletConfig是Servlet规范中的一员。
 *      ServletConfig是一个接口。(jakarta.servlet.Servlet是一个接口。)
 *   2. 谁去实现了这个接口呢? WEB服务器实现了
 *      public class org.apache.catalina.core.StandardWrapperFacade implements ServletConfig {}
 *      结论:Tomcat服务器实现了ServletConfig接口。
 *      思考:如果把Tomcat服务器换成jetty服务器,输出ServletConfig对象的时候,还是这个结果吗?
 *          不一定一样,包名类名可能和Tomcat不一样。但是他们都实现了ServletConfig这个规范。
 *   3. 一个Servlet对象中有一个ServletConfig对象。(Servlet和ServletConfig对象是一对一。)
 *      100个Servlet,就应该有100个ServletConfig对象。
 *   4. ServletConfig对象是谁创建的?在什么时候创建的?
 *      Tomcat服务器(WEB服务器)创建了ServletConfig对象。
 *      在创建Servlet对象的时候,同时创建ServletConfig对象。
 *   5. ServletConfig接口到底是干啥的?有什么用呢?
 *      Config是哪个单词的缩写?
 *          Configuration
 *      ServletConfig对象被翻译为:Servlet对象的配置信息对象。
 *      一个Servlet对象就有一个配置信息对象。
 *      两个Servlet对象就有两个配置信息对象。
 *
 *   6. ServletConfig对象中到底包装了什么信息呢?
 *      <servlet>
 *         <servlet-name>configTest</servlet-name>
 *         <servlet-class>com.bjpowernode.javaweb.servlet.ConfigTestServlet</servlet-class>
 *     </servlet>
 *     ServletConfig对象中包装的信息是:
 *          web.xml文件中<servlet></servlet>标签的配置信息。
 *
 *     Tomcat小猫咪解析web.xml文件,将web.xml文件中<servlet></servlet>标签中的配置信息自动包装到ServletConfig对象中。
 *
 *   7. ServletConfig接口中有哪些方法?
 *      <servlet>
 *         <servlet-name>configTest</servlet-name>
 *         <servlet-class>com.bjpowernode.javaweb.servlet.ConfigTestServlet</servlet-class>
 *         <!--这里是可以配置一个Servlet对象的初始化信息的。-->
 *         <init-param>
 *             <param-name>driver</param-name>
 *             <param-value>com.mysql.cj.jdbc.Driver</param-value>
 *         </init-param>
 *         <init-param>
 *             <param-name>url</param-name>
 *             <param-value>jdbc:mysql://localhost:3306/bjpowernode</param-value>
 *         </init-param>
 *         <init-param>
 *             <param-name>user</param-name>
 *             <param-value>root</param-value>
 *         </init-param>
 *         <init-param>
 *             <param-name>password</param-name>
 *             <param-value>root1234</param-value>
 *         </init-param>
 *     </servlet>
 *     以上<servlet></servlet>标签中的<init-param></init-param>是初始化参数。这个初始化参数信息会自动被小猫咪封装到ServletConfig对象当中。
 *  8. ServletConfig接口中有4个方法:
 *      第1个方法:
 *          public String getInitParameter(String name);
 *      第2个方法:
 *          public Enumeration<String> getInitParameterNames();
 *      第3个方法:
 *          public ServletContext getServletContext();
 *      第4个方法:
 *          public String getServletName();
 *
 *      以上的4个方法,在自己编写的Servlet类当中也可以使用this去调用。(这个Servlet继承了GenericServlet)
 */
public class ConfigTestServlet extends GenericServlet {
    @Override
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        // 获取ServletConfig对象
        ServletConfig config = this.getServletConfig();
        // 输出该对象
        // org.apache.catalina.core.StandardWrapperFacade@aea0d43
        out.print("ServletConfig对象是:" + config.toString());
        out.print("<br>");

        // 获取<servlet-name></servlet-name>
        String servletName = config.getServletName();
        out.print("<servlet-name>"+servletName+"</servlet-name>");
        out.print("<br>");

        // 通过ServletConfig对象的两个方法,可以获取到web.xml文件中的初始化参数配置信息。
        // java.util.Enumeration<java.lang.String>	getInitParameterNames() 获取所有的初始化参数的name
        Enumeration<String> initParameterNames = config.getInitParameterNames();
        // 遍历集合
        while(initParameterNames.hasMoreElements()) { // 是否有更多元素
            String parameterName = initParameterNames.nextElement(); // 取元素
            String parameterVal = config.getInitParameter(parameterName); // 通过name获取value
            out.print(parameterName + "=" + parameterVal);
            out.print("<br>");
        }
        // java.lang.String	getInitParameter(java.lang.String name) 通过初始化参数的name获取value
        /*String driver = config.getInitParameter("driver");
        out.print(driver);*/
		
		// ServletConfig的四个方法在自己编写的Servlet类当中也可以使用this去调用。(这个Servlet继承了GenericServlet,GenericServlet实现了ServletConfig接口)
        // 实际上获取一个Servlet对象的初始化参数,可以不用获取ServletConfig对象。直接通过this也可以。
        Enumeration<String> names = this.getInitParameterNames();
        while(names.hasMoreElements()){
            String name = names.nextElement();
            String value = this.getInitParameter(name);
            // 打印到后台
            System.out.println(name + "=" + value);
        }
    }
}

4. ServletContext接口详解

// 怎么获取ServletContext对象呢?
// 第一种方式:通过ServletConfig对象获取ServletContext对象。
ServletContext application = config.getServletContext();
// 输出
out.print("<br>" + application); //org.apache.catalina.core.ApplicationContextFacade@19187bbb

// 第二种方式:通过this也可以获取ServletContext对象。
ServletContext application2 = this.getServletContext();
out.print("<br>" + application2); //org.apache.catalina.core.ApplicationContextFacade@19187bbb

ServletContext

  1. ServletContext是什么?

    • ServletContext是接口,是Servlet规范中的一员。
  2. ServletContext是谁实现的?

    • Tomcat服务器(WEB服务器)实现了ServletContext接口。

    • public class org.apache.catalina.core.ApplicationContextFacade implements ServletContext {}

  3. ServletContext对象是谁创建的?在什么时候创建的?

    • ServletContext对象在WEB服务器启动的时候创建。

    • ServletContext对象是WEB服务器创建的。

    • 对于一个webapp来说,ServletContext对象只有一个。

    • 一个标签对应一个ServletContext对象

    • ServletContext对象在服务器关闭的时候销毁。

​ 4. ServletContext怎么理解?

  • context是什么意思?

    • Servlet对象的环境对象。(Servlet对象的上下文对象。)

    • ServletContext对象其实对应的就是整个web.xml文件。

    • 50个学生,每个学生都是一个Servlet,这50个学生都在同一个教室当中。那么这个教室就相当于ServletContext对象。

    • 放在ServletContext对象当中的数据,所有Servlet一定是共享的。

    • 比如:一个教室中的空调是所有学生共享的,一个教室中的语文老师是所有学生共享的。

    • Tomcat是一个容器,一个容器当中可以放多个webapp,一个webapp对应一个ServletContext对象。

  • 一个Servlet对象对应一个ServletConfig。100个Servlet对象则对应100个ServletConfig对象。

  • 只要在同一个webapp当中,只要在同一个应用当中,所有的Servlet对象都是共享同一个ServletContext对象的。

  • ServletContext对象在服务器启动阶段创建,在服务器关闭的时候销毁。这就是ServletContext对象的生命周期。ServletContext对象是应用级对象。

  • Tomcat服务器中有一个webapps,这个webapps下可以存放webapp,可以存放多个webapp,假设有100个webapp,那么就有100个ServletContext对象。但是,总之,一个应用,一个webapp肯定是只有一个ServletContext对象。

  • ServletContext被称为Servlet上下文对象。(Servlet对象的四周环境对象。)

    • 一个ServletContext对象通常对应的是一个web.xml文件。
  • ServletContext对应显示生活中的什么例子呢?
    一个教室里有多个学生,那么每一个学生就是一个Servlet,这些学生都在同一个教室当中,那么我们可以把这个教室叫做ServletContext对象。那么也就是说放在这个ServletContext对象(环境)当中的数据,在同一个教室当中,物品都是共享的。比如:教室中有一个空调,所有的学生都可以操作。可见,空调是共享的。因为空调放在教室当中。教室就是ServletContext对象。

  • ServletContext是一个接口,Tomcat服务器对ServletContext接口进行了实现。

    • ServletContext对象的创建也是Tomcat服务器来完成的。启动webapp的时候创建的。
  • ServletContext接口中有哪些常用的方法?

    public String getInitParameter(String name); // 通过初始化参数的name获取value
    public Enumeration<String> getInitParameterNames(); // 获取所有的初始化参数的name
    
    <!--以上两个方法是ServletContext对象的方法,这个方法获取的是什么信息?是以下的配置信息-->
    <!--上下文的初始化参数,以下的这些配置信息,可以通过ServletContext对象来获取-->
    <context-param>
        <param-name>pageSize</param-name>
        <param-value>10</param-value>
    </context-param>
    <context-param>
        <param-name>startIndex</param-name>
        <param-value>0</param-value>
    </context-param>
    <!--注意:以上的配置信息属于应用级的配置信息,一般一个项目中共享的配置信息会放到以上的标签当中。-->
    <!--如果你的配置信息只是想给某一个servlet作为参考,那么你配置到servlet标签当中即可,使用ServletConfig对象来获取。-->
    
       // 获取ServletContext
       ServletContext application = this.getServletContext();
       out.print("ServletContext:"+application);
    	
       // 获取上下文的初始化参数
       Enumeration<String> initParameterNames = application.getInitParameterNames();
       while(initParameterNames.hasMoreElements()){
           String name = initParameterNames.nextElement();
           String value = application.getInitParameter(name);
           out.print(name + "=" + value + "<br>");
       }
    
    
  • 如果配置信息是在整个项目全局中进行使用,则将配置信息配置到全局的上下文参数中,如果你的配置信息只是想给某一个servlet作为参考,那么你配置到servlet标签当中即可,使用ServletConfig对象来获取。

    // 获取应用的根路径(非常重要),因为在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态获取应用的根路径
    // 在java源码当中,不要将应用的根路径写死,因为你永远都不知道这个应用在最终部署的时候,起一个什么名字。
    public String getContextPath();
    //String contextPath = application.getContextPath();
    
    
    // 获取context path (获取应用上下文的根)
    String contextPath = application.getContextPath();
    out.print(contextPath + "<br>");
    
    ----------------------------------------------------------------------------------
    
    // 获取文件的绝对路径(真实路径)
    public String getRealPath(String path);
    
    
    // 获取文件的绝对路径
    // 后面的这个路径,加了一个“/”,这个“/”代表的是web的根
    //String realPath = application.getRealPath("/index.html"); // 可以
    // 你不加“/”,默认也是从根下开始找。
    //String realPath = application.getRealPath("index.html"); // 不加“/”也可以
    //out.print(realPath + "<br>");
    
    // C:\Users\Administrator\IdeaProjects\javaweb\out\artifacts\servlet04_war_exploded\common\common.html
    String realPath = application.getRealPath("/common/common.html");
    out.print(realPath + "<br>");
    
    ----------------------------------------------------------------------------------
    // 通过ServletContext对象也是可以记录日志的
    public void log(String message);
    // 记录日志信息外,还记录异常信息,控制台不会发送异常,只是记录异常信息
    public void log(String message, Throwable t);
    // 这些日志信息记录到哪里了?
    // localhost.2021-11-05.log
    
    // IDEA工具的Tomcat服务器是根据下载的Tomcat服务器生成的副本
    // Tomcat服务器的logs目录下都有哪些日志文件?
    //catalina.2021-11-05.log 服务器端的java程序运行的控制台信息。
    //localhost.2021-11-05.log ServletContext对象的log方法记录的日志信息存储到这个文件中。
    //localhost_access_log.2021-11-05.txt 访问日志
    
    
    ----------------------------------------------------------------------------------
    // log
    // 这个日志会自动记录到哪里呢?
    // CATALINA_HOME/logs目录下。
    //application.log("大家好,我是动力节点杜老师,欢迎大家和我一起学习Servlet规范!");
    
    int age = 17; // 17岁
    // 当年龄小于18岁的时候,表示非法,记录日志
    if(age < 18) {
        application.log("对不起,您未成年,请绕行!", new RuntimeException("小屁孩,快走开,不适合你!"));
    }
    
    
    ----------------------------------------------------------------------------------
    
    // ServletContext对象还有另一个名字:应用域(后面还有其他域,例如:请求域、会话域)
    
    // 如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域中
    
    // 为什么是所有用户共享的数据? 不是共享的没有意义。因为ServletContext这个对象只有一个。只有共享的数据放进去才有意义。
    
    // 为什么数据量要小? 因为数据量比较大的话,太占用堆内存,并且这个对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁。大数据量会影响服务器的性能。占用内存较小的数据量可以考虑放进去。
    
    // 为什么这些共享数据很少的修改,或者说几乎不修改?
    // 所有用户共享的数据,如果涉及到修改操作,必然会存在线程并发所带来的安全问题。所以放在ServletContext对象中的数据一般都是只读的。
    
    // 数据量小、所有用户共享、又不修改,这样的数据放到ServletContext这个应用域当中,会大大提升效率。因为应用域相当于一个缓存,放到缓存中的数据,下次在用的时候,不需要从数据库中再次获取,大大提升执行效率。
    
    // 存(怎么向ServletContext应用域中存数据)
    public void setAttribute(String name, Object value); // map.put(k, v)
    // 取(怎么从ServletContext应用域中取数据)
    public Object getAttribute(String name); // Object v = map.get(k)
    // 删(怎么删除ServletContext应用域中的数据)
    public void removeAttribute(String name); // map.remove(k)
    
    
    ----------------------------------------------------------------------------------
    // 准备数据
    User user = new User("jack", "123");
    // 向ServletContext应用域当中存储数据
    application.setAttribute("userObj", user);
    // 取出来
    //Object userObj = application.getAttribute("userObj");
    // 输出到浏览器
    //out.print(userObj + "<br>");
    
    
  • 注意:以后我们编写Servlet类的时候,实际上是不会去直接继承GenericServlet类的,因为我们是B/S结构的系统,这种系统是基于HTTP超文本传输协议的,在Servlet规范当中,提供了一个类叫做HttpServlet,它是专门为HTTP协议准备的一个Servlet类。我们编写的Servlet类要继承HttpServlet。(HttpServlet是HTTP协议专用的。)使用HttpServlet处理HTTP协议更便捷。但是你需要直到它的继承结构:

    jakarta.servlet.Servlet(接口)【爷爷】
    jakarta.servlet.GenericServlet implements Servlet(抽象类)【儿子】
    jakarta.servlet.http.HttpServlet extends GenericServlet(抽象类)【孙子】
    
    我们以后编写的Servlet要继承HttpServlet类。
    

第八章 Http协议

1. 什么是协议?

  • 协议实际上是某些人,或者某些组织提前制定好的一套规范,大家都按照这个规范来,这样可以做到沟通无障碍。
  • 协议就是一套规范,就是一套标准。由其他人或其他组织来负责制定的。
  • 我说的话你能听懂,你说的话,我也能听懂,这说明我们之间是有一套规范的,一套协议的,这套协议就是:中国普通话协议。我们都遵守这套协议,我们之间就可以沟通无障碍。

2. 什么是Http协议?

  • 什么是HTTP协议?

    • HTTP协议:是W3C制定的一种超文本传输协议。(通信协议:发送消息的模板提前被制定好。)

    • W3C:

      • 万维网联盟组织
      • 负责制定标准的:HTTP HTML4.0 HTML5 XML DOM等规范都是W3C制定的。
      • 万维网之父:蒂姆·伯纳斯·李
    • 什么是超文本?

      • 超文本说的就是:不是普通文本,比如流媒体:声音、视频、图片等。
      • HTTP协议支持:不但可以传送普通字符串,同样支持传递声音、视频、图片等流媒体信息。
        这种协议游走在B和S之间。B向S发数据要遵循HTTP协议。S向B发数据同样需要遵循HTTP协议。这样B和S才能解耦合。
    • 什么是解耦合?

      • B不依赖S。
      • S也不依赖B。
    • B/S表示:B/S结构的系统(浏览器访问WEB服务器的系统)

    • 浏览器 向 WEB服务器发送数据,叫做:请求(request)

    • WEB服务器 向 浏览器发送数据,叫做:响应(response)

  • HTTP协议包括:

    • 请求协议
      • 浏览器 向 WEB服务器发送数据的时候,这个发送的数据需要遵循一套标准,这套标准中规定了发送的数据具体格式。
    • 响应协议
      • WEB服务器 向 浏览器发送数据的时候,这个发送的数据需要遵循一套标准,这套标准中规定了发送的数据具体格式。
  • HTTP协议就是提前制定好的一种消息模板。

    • 不管你是哪个品牌的浏览器,都是这么发。
    • 不管你是哪个品牌的WEB服务器,都是这么发。
    • FF浏览器 可以向 Tomcat发送请求,也可以向Jetty服务器发送请求。浏览器不依赖具体的服务器品牌。
    • WEB服务器也不依赖具体的浏览器品牌。可以是FF浏览器,也可以是Chrome浏览器,可以是IE,都行。
  • HTTP的请求协议(B --> S)

    • HTTP的请求协议包括:4部分

      • 请求行
      • 请求头
      • 空白行
      • 请求体
    • HTTP请求协议的具体报文:GET请求

      --  请求行内容  --
      GET /servlet05/getServlet?username=lucy&userpwd=1111 HTTP/1.1 
      --  请求头内容  --
      Host: localhost:8080                                              
      Connection: keep-alive
      sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"
      sec-ch-ua-mobile: ?0
      sec-ch-ua-platform: "Windows"
      Upgrade-Insecure-Requests: 1
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
      Sec-Fetch-Site: same-origin
      Sec-Fetch-Mode: navigate
      Sec-Fetch-User: ?1
      Sec-Fetch-Dest: document
      Referer: http://localhost:8080/servlet05/index.html
      Accept-Encoding: gzip, deflate, br
      Accept-Language: zh-CN,zh;q=0.9
      --   空白行   --
      
      --   请求体   -- 
      
      
    • HTTP请求协议的具体报文:POST请求

      --    请求行    -- 
      POST /servlet05/postServlet HTTP/1.1    
      --    请求头    --
      Host: localhost:8080                                                                  
      Connection: keep-alive
      Content-Length: 25
      Cache-Control: max-age=0
      sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"
      sec-ch-ua-mobile: ?0
      sec-ch-ua-platform: "Windows"
      Upgrade-Insecure-Requests: 1
      Origin: http://localhost:8080
      Content-Type: application/x-www-form-urlencoded
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
      Sec-Fetch-Site: same-origin
      Sec-Fetch-Mode: navigate
      Sec-Fetch-User: ?1
      Sec-Fetch-Dest: document
      Referer: http://localhost:8080/servlet05/index.html
      Accept-Encoding: gzip, deflate, br
      Accept-Language: zh-CN,zh;q=0.9
      --    空白行   -- 
      
      --    请求体   -- 
      username=lisi&userpwd=123                                                             
      
      
    • 请求行

      • 包括三部分:
        • 第一部分:请求方式(7种)
          • get(常用的)
          • post(常用的)
          • delete
          • put
          • head
          • options
          • trace
        • 第二部分:URI
          • 什么是URI? 统一资源标识符。代表网络中某个资源的名字。但是通过URI是无法定位资源的。
          • 什么是URL?统一资源定位符。代表网络中某个资源,同时,通过URL是可以定位到该资源的。
          • URI和URL什么关系,有什么区别?
            • URL包括URI
              • http://localhost:8080/servlet05/index.html 这是URL。
              • /servlet05/index.html 这是URI。
        • 第三部分:HTTP协议版本号
    • 请求头

      • 请求的主机
      • 主机的端口
      • 浏览器信息
      • 平台信息
      • cookie等信息
    • 空白行

      • 空白行是用来区分“请求头”和“请求体”
    • 请求体

      • 向服务器发送的具体数据。
  • HTTP的响应协议(S --> B)

    • HTTP的响应协议包括:4部分

      • 状态行
      • 响应头
      • 空白行
      • 响应体
    • out.println() 是输出信息到浏览器,最终源代码中换行。(不是网页上有换行效果,网页上有换行效果必须使用<br>

    • HTTP响应协议的具体报文:

      HTTP/1.1 200 ok                                     状态行
      Content-Type: text/html;charset=UTF-8               响应头
      Content-Length: 160
      Date: Mon, 08 Nov 2021 13:19:32 GMT
      Keep-Alive: timeout=20
      Connection: keep-alive
                                                          空白行
      <!doctype html>                                     响应体
      <html>
          <head>
              <title>from get servlet</title>
          </head>
          <body>
              <h1>from get servlet</h1>
          </body>
      </html>
      
    • 状态行

      • 三部分组成
        • 第一部分:协议版本号(HTTP/1.1)
        • 第二部分:状态码(HTTP协议中规定的响应状态号。不同的响应结果对应不同的号码。)
          • 200 表示请求响应成功,正常结束。
          • 404表示访问的资源不存在,通常是因为要么是你路径写错了,要么是路径写对了,但是服务器中对应的资源并没有启动成功。总之404错误是前端错误。
          • 405表示前端发送的请求方式与后端请求的处理方式不一致时发生:
            比如:前端是POST请求,后端的处理方式按照get方式进行处理时,发生405
            比如:前端是GET请求,后端的处理方式按照post方式进行处理时,发生405
          • 500表示服务器端的程序出现了异常。一般会认为是服务器端的错误导致的。
            以4开始的,一般是浏览器端的错误导致的。
            以5开始的,一般是服务器端的错误导致的。
        • 第三部分:状态的描述信息
          • ok 表示正常成功结束。
          • not found 表示资源找不到。
    • 响应头:

      • 响应的内容类型
      • 响应的内容长度
      • 响应的时间
    • 空白行:

      • 用来分隔“响应头”和“响应体”的。
    • 响应体:

      • 响应体就是响应的正文,这些内容是一个长的字符串,这个字符串被浏览器渲染,解释并执行,最终展示出效果。
  • 怎么查看的协议内容?

    • 使用chrome浏览器:F12。然后找到network,通过这个面板可以查看协议的具体内容。
  • 怎么向服务器发送GET请求,怎么向服务器发送POST请求?

    • 到目前为止,只有一种情况可以发送POST请求:使用form表单,并且form标签中的method属性值为:method=“post”。
    • 其他所有情况一律都是get请求:
      • 在浏览器地址栏上直接输入URL,敲回车,属于get请求。
      • 在浏览器上直接点击超链接,属于get请求。
      • 使用form表单提交数据时,form标签中没有写method属性,默认就是get
      • 或者使用form的时候,form标签中method属性值为:method=“get”
  • GET请求和POST请求有什么区别?

    • get请求发送数据的时候,数据会挂在URI的后面,并且在URI后面添加一个“?”,"?"后面是数据。这样会导致发送的数据回显在浏览器的地址栏上。(get请求在“请求行”上发送数据)

      • http://localhost:8080/servlet05/getServlet?username=zhangsan&userpwd=1111
    • post请求发送数据的时候,在请求体当中发送。不会回显到浏览器的地址栏上。也就是说post发送的数据,在浏览器地址栏上看不到。(post在“请求体”当中发送数据)

    • get请求只能发送普通的字符串。并且发送的字符串长度有限制,不同的浏览器限制不同。这个没有明确的规范。

    • get请求无法发送大数据量。

    • post请求可以发送任何类型的数据,包括普通字符串,流媒体等信息:视频、声音、图片。

    • post请求可以发送大数据量,理论上没有长度限制。

    • get请求在W3C中是这样说的:get请求比较适合从服务器端获取数据。

    • post请求在W3C中是这样说的:post请求比较适合向服务器端传送数据。

    • get请求是安全的。get请求是绝对安全的。为什么?因为get请求只是为了从服务器上获取数据。不会对服务器造成威胁。(get本身是安全的,你不要用错了。用错了之后又冤枉人家get不安全,你这样不好(太坏了),那是你自己的问题,不是get请求的问题。)

    • post请求是危险的。为什么?因为post请求是向服务器提交数据,如果这些数据通过后门的方式进入到服务器当中,服务器是很危险的。另外post是为了提交数据,所以一般情况下拦截请求的时候,大部分会选择拦截(监听)post请求。

    • get请求支持缓存。

      • https://n.sinaimg.cn/finance/590/w240h350/20211101/b40c-b425eb67cabc342ff5b9dc018b4b00cc.jpg

      • 任何一个get请求最终的“响应结果”都会被浏览器缓存起来。在浏览器缓存当中:

        • 一个get请求的路径a 对应 一个资源。
        • 一个get请求的路径b 对应 一个资源。
        • 一个get请求的路径c 对应 一个资源。
      • 实际上,你只要发送get请求,浏览器做的第一件事都是先从本地浏览器缓存中找,找不到的时候才会去服务器上获取。这种缓存机制目的是为了提高用户的体验。

      • 有没有这样一个需求:我们不希望get请求走缓存,怎么办?怎么避免走缓存?我希望每一次这个get请求都去服务器上找资源,我不想从本地浏览器的缓存中取。

        • 只要每一次get请求的请求路径不同即可。
        • https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=789789787897898
        • https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=789789787897899
        • https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=系统毫秒数
        • 怎么解决?可以在路径的后面添加一个每时每刻都在变化的“时间戳”,这样,每一次的请求路径都不一样,浏览器就不走缓存了。
    • post请求不支持缓存。(POST是用来修改服务器端的资源的。)
      • post请求之后,服务器“响应的结果”不会被浏览器缓存起来。因为这个缓存没有意义。
  • GET请求和POST请求如何选择,什么时候使用GET请求,什么时候使用POST请求?

    • 怎么选择GET请求和POST请求呢?衡量标准是什么呢?你这个请求是想获取服务器端的数据,还是想向服务器发送数据。如果你是想从服务器上获取资源,建议使用GET请求,如果你这个请求是为了向服务器提交数据,建议使用POST请求。
    • 大部分的form表单提交,都是post方式,因为form表单中要填写大量的数据,这些数据是收集用户的信息,一般是需要传给服务器,服务器将这些数据保存/修改等。
    • 如果表单中有敏感信息,还是建议适用post请求,因为get请求会回显敏感信息到浏览器地址栏上。(例如:密码信息)
    • 做文件上传,一定是post请求。要传的数据不是普通文本。
    • 其他情况都可以使用get请求。
  • 不管你是get请求还是post请求,发送的请求数据格式是完全相同的,只不过位置不同,格式都是统一的:

    • name=value&name=value&name=value&name=value
    • name是什么?
      • 以form表单为例:form表单中input标签的name。
    • value是什么?
      • 以form表单为例 :form表单中input标签的value。

3. HttpServlet类

  • HttpServlet类是专门为HTTP协议准备的。比GenericServlet更加适合HTTP协议下的开发。

    • HttpServlet在哪个包下?
      • jakarta.servlet.http.HttpServlet
    • 到目前为止我们接触了servlet规范中哪些接口?
      • jakarta.servlet.Servlet 核心接口(接口)
      • jakarta.servlet.ServletConfig Servlet配置信息接口(接口)
      • jakarta.servlet.ServletContext Servlet上下文接口(接口)
      • jakarta.servlet.ServletRequest Servlet请求接口(接口)
      • jakarta.servlet.ServletResponse Servlet响应接口(接口)
      • jakarta.servlet.ServletException Servlet异常(类)
      • jakarta.servlet.GenericServlet 标准通用的Servlet类(抽象类)
  • http包下都有哪些类和接口呢?

    • jakarta.servlet.http.*;
    • jakarta.servlet.http.HttpServlet (HTTP协议专用的Servlet类,抽象类)
    • jakarta.servlet.http.HttpServletRequest (HTTP协议专用的请求对象)
    • jakarta.servlet.http.HttpServletResponse (HTTP协议专用的响应对象)
  • HttpServletRequest对象中封装了什么信息?

    • HttpServletRequest,简称request对象。
    • HttpServletRequest中封装了请求协议的全部内容。
    • Tomcat服务器(WEB服务器)将“请求协议”中的数据全部解析出来,然后将这些数据全部封装到request对象当中了。
    • 也就是说,我们只要面向HttpServletRequest,就可以获取请求协议中的数据。
  • HttpServletResponse对象是专门用来响应HTTP协议到浏览器的。
  • 回忆Servlet生命周期?

    • 用户第一次请求

      • Tomcat服务器通过反射机制,调用无参数构造方法。创建Servlet对象。(web.xml文件中配置的Servlet类对应的对象。)

      • Tomcat服务器调用Servlet对象的init方法完成初始化。

      • Tomcat服务器调用Servlet对象的service方法处理请求。

    • 用户第二次请求及之后的请求

      • Tomcat服务器调用Servlet对象的service方法处理请求。
    • 服务器关闭

      • Tomcat服务器调用Servlet对象的destroy方法,做销毁之前的准备工作。

      • Tomcat服务器销毁Servlet对象。

httpservlet 源码分析:

public class HelloServlet extends HttpServlet {
	// 用户第一次请求,创建HelloServlet对象的时候,会执行这个无参数构造方法。
	public HelloServlet() {
    }
    
    //override 重写 doGet方法
    //override 重写 doPost方法
}

public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {
           
	// 用户第一次请求的时候,HelloServlet对象第一次被创建之后,这个init方法会执行。
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
	// 用户第一次请求的时候,带有参数的init(ServletConfig config)执行之后,会执行这个没有参数的init()
	public void init() throws ServletException {
        // NOOP by default
    }
}

// HttpServlet模板类。
public abstract class HttpServlet extends GenericServlet {
    // 用户发送第一次请求的时候这个service会执行
    // 用户发送第N次请求的时候,这个service方法还是会执行。
    // 用户只要发送一次请求,这个service方法就会执行一次。
    @Override
    public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException {

        HttpServletRequest  request;
        HttpServletResponse response;

        try {
            // 将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }
        // 调用重载的service方法。
        service(request, response);
    }
    
    // 这个service方法的两个参数都是带有Http的。
    // 这个service是一个模板方法。
    // 在该方法中定义核心算法骨架,具体的实现步骤延迟到子类中去完成。
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        // 获取请求方式
        // 这个请求方式最终可能是:""
        // 注意:request.getMethod()方法获取的是请求方式,可能是七种之一:
        // GET POST PUT DELETE HEAD OPTIONS TRACE
        String method = req.getMethod();

        // 如果请求方式是GET请求,则执行doGet方法。
        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                } catch (IllegalArgumentException iae) {
                    // Invalid date header - proceed as if none was set
                    ifModifiedSince = -1;
                }
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            // 如果请求方式是POST请求,则执行doPost方法。
            doPost(req, resp);

        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);

        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);

        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);

        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);

            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }
    
    
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException{
        // 报405错误
        String msg = lStrings.getString("http.method_get_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }
    
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        // 报405错误
        String msg = lStrings.getString("http.method_post_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }
    
}

/*
通过以上源代码分析:
	假设前端发送的请求是get请求,后端程序员重写的方法是doPost,子类没有实现doGet方法,会执行父类的doGet方法,父类的doGet方法会报405错误
	假设前端发送的请求是post请求,后端程序员重写的方法是doGet
	会发生什么呢?
		发生405这样的一个错误。
		405表示前端的错误,发送的请求方式不对。和服务器不一致。不是服务器需要的请求方式。
	
	通过以上源代码可以知道:只要HttpServlet类中的doGet方法或doPost方法执行了,必然405.

怎么避免405的错误呢?
	后端重写了doGet方法,前端一定要发get请求。
	后端重写了doPost方法,前端一定要发post请求。
	这样可以避免405错误。
	
	这种前端到底需要发什么样的请求,其实应该后端说了算。后端让发什么方式,前端就得发什么方式。
	
有的人,你会看到为了避免405错误,在Servlet类当中,将doGet和doPost方法都进行了重写。
这样,确实可以避免405的发生,但是不建议,405错误还是有用的。该报错的时候就应该让他报错。
如果你要是同时重写了doGet和doPost,那还不如你直接重写service方法好了。这样代码还能
少写一点。
*/



  • 我们编写的HelloServlet直接继承HttpServlet,直接重写HttpServlet类中的service()方法行吗?
    • 可以,只不过你享受不到405错误。享受不到HTTP协议专属的东西。
  • 到今天我们终于得到了最终的一个Servlet类的开发步骤:

    • 第一步:编写一个Servlet类,直接继承HttpServlet

    • 第二步:重写doGet方法或者重写doPost方法,到底重写谁,javaweb程序员说了算。

    • 第三步:将Servlet类配置到web.xml文件当中。

    • 第四步:准备前端的页面(form表单),form表单中指定请求路径即可。

4. web欢迎页

  • 什么是一个web站点的欢迎页面?

    • 对于一个webapp来说,我们是可以设置它的欢迎页面的。
    • 设置了欢迎页面之后,当你访问这个webapp的时候,或者访问这个web站点的时候,没有指定任何“资源路径”,这个时候会默认访问你的欢迎页面。
    • 我们一般的访问方式是:
      • http://localhost:8080/servlet06/login.html 这种方式是指定了要访问的就是login.html资源。
  • 如果我们访问的方式是:

    • http://localhost:8080/servlet06 如果我们访问的就是这个站点,没有指定具体的资源路径。它默认会访问谁呢?

    • 默认会访问你设置的欢迎页面。

  • 配置web.xml:

    <welcome-file-list>
        <welcome-file>login.html</welcome-file>
    </welcome-file-list>
    
    • 注意:设置欢迎页面的时候,这个路径不需要以“/”开始。并且这个路径默认是从webapp的根下开始查找。
  • 一个webapp是可以设置多个欢迎页面的

    <welcome-file-list>
        <welcome-file>page1/page2/page.html</welcome-file>
        <welcome-file>login.html</welcome-file>
    </welcome-file-list>
    
    • 注意:越靠上的优先级越高。找不到的继续向下找。
  • 你有没有注意一件事:当我的文件名设置为index.html的时候,不需要在web.xml文件中进行配置欢迎页面。这是为什么?

    • 这是因为小猫咪Tomcat服务器已经提前配置好了。

    • 实际上配置欢迎页面有两个地方可以配置:

      • 一个是在webapp内部的web.xml文件中。(在这个地方配置的属于局部配置)
      • 一个是在CATALINA_HOME/conf/web.xml文件中进行配置。(在这个地方配置的属于全局配置)
      <welcome-file-list>
          <welcome-file>index.html</welcome-file>
          <welcome-file>index.htm</welcome-file>
          <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
      
    • Tomcat服务器的全局欢迎页面是:index.html index.htm index.jsp。如果你一个web站点没有设置局部的欢迎页面,Tomcat服务器就会以index.html index.htm index.jsp作为一个web站点的欢迎页面。

    • 注意原则:局部优先原则。(就近原则)

5. WEB-INF目录

  • 在WEB-INF目录下新建了一个文件:welcome.html
  • 打开浏览器访问:http://localhost:8080/servlet07/WEB-INF/welcome.html 出现了404错误。
    • 注意:放在WEB-INF目录下的资源是受保护的。在浏览器上不能够通过路径直接访问。所以像HTML、CSS、JS、image等静态资源一定要放到WEB-INF目录之外。

第九章 HttpServletRequest接口

1. 获取参数方法

Map<String,String[]> getParameterMap() 这个是获取Map
Enumeration<String> getParameterNames() 这个是获取Map集合中所有的key
String[] getParameterValues(String name) 根据key获取Map集合的value
String getParameter(String name)  根据key获取value这个一维数组当中的第一个元素。这个方法最常用。
// 以上的4个方法,和获取用户提交的数据有关系。

思考:如果是你,前端的form表单提交了数据之后,你准备怎么存储这些数据,你准备采用什么样的数据结构去存储这些数据呢?

  • 前端提交的数据格式:username=abc&userpwd=111&aihao=s&aihao=d&aihao=tt
  • 我会采用Map集合来存储:
Map<String,String>
    key存储String
    value存储String
    这种想法对吗?不对。
    如果采用以上的数据结构存储会发现key重复的时候value覆盖。
    key         value
    ---------------------
    username    abc
    userpwd     111
    aihao       s
    aihao       d
    aihao       tt
    这样是不行的,因为map的key不能重复。
Map<String, String[]>
    key存储String
    value存储String[]
    key				value
    -------------------------------
    username		{"abc"}
    userpwd			{"111"}
    aihao			{"s","d","tt"}

  • 注意:前端表单提交数据的时候,假设提交了120这样的“数字”,其实是以字符串"120"的方式提交的,所以服务器端获取到的一定是一个字符串的"120",而不是一个数字。(前端永远提交的是字符串,后端获取的也永远是字符串。)

测试4个方法:

package com.bjpowernode.javaweb.servlet;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.*;
import java.util.*;

/*
username=zhangsan&userpwd=123&interest=s&interest=d
Map<String,String[]>
key				value
---------------------------
"username"		{"zhangsan"}
"userpwd"		{"123"}
"interest"		{"s", "d"}

总结一下:request接口中四个非常重要的方法。
	Map<String,String[]> parameterMap = request.getParameterMap();
	Enumeration<String> names = request.getParameterNames();
	String[] values = request.getParameterValues("name");
	String value = request.getParameter("name");


*/
public class RequestTestServlet extends HttpServlet{

	public void doPost(HttpServletRequest request, HttpServletResponse response)
		throws IOException,ServletException{
		// 面向接口编程:HttpServletRequest接口。
		// 获取前端提交的数据
		// 前端会提交什么数据呢?
		// username=zhangsan&userpwd=123&interest=s&interest=d
		// 以上的数据会被小猫咪封装到request对象中。

		// 获取参数Map集合
		Map<String,String[]> parameterMap = request.getParameterMap();
		// 遍历Map集合(获取Map集合中所有的key,遍历)
		Set<String> keys = parameterMap.keySet();
		Iterator<String> it = keys.iterator();
		while(it.hasNext()){
			String key = it.next();
			//System.out.println(key);
			// 通过key获取value
			String[] values = parameterMap.get(key);
			/*
				username=[Ljava.lang.String;@7cce40b4
				userpwd=[Ljava.lang.String;@7453f0b9
				interest=[Ljava.lang.String;@4063ebb5
			*/
			//System.out.println(key + "=" + values);

			// 遍历一维数组
			System.out.print(key + "=");

			for(String value : values){
				System.out.print(value + ",");
			}
			// 换行
			System.out.println();
		}

		// 直接通过getParameterNames()这个方法,可以直接获取这个Map集合的所有key
		Enumeration<String> names = request.getParameterNames();
		while(names.hasMoreElements()){
			String name = names.nextElement();
			System.out.println(name);
		}

		// 直接通过name获取value这个一维数组。
		String[] usernames = request.getParameterValues("username");
		String[] userpwds = request.getParameterValues("userpwd");
		String[] interests = request.getParameterValues("interest");

		// 遍历一维数组
		for(String username : usernames){
			System.out.println(username);
		}

		for(String userpwd : userpwds){
			System.out.println(userpwd);
		}

		for(String interest : interests){
			System.out.println(interest);
		}

		// 通过name获取value这个一维数组的第一个元素
		// 这个方法使用最多,因为这个一维数组中一般只有一个元素。
		String username = request.getParameter("username");
		String userpwd = request.getParameter("userpwd");
		//String interest = request.getParameter("interest");

		// 既然是checkbox,调用方法:request.getParameterValues("interest")
		String[] interests2 = request.getParameterValues("interest");

		// 获取的都是一维数组当中的第一个元素。
		System.out.println(username);
		System.out.println(userpwd);
		//System.out.println(interest);

		for(String interest : interests2){
			System.out.println(interest);
		}


	}
}

2. 获取路径的方法

有四个方法:

// 获取应用的根路径
public String getContextPath();

// 获取请求路径
public String getRequestURI();

// 获取Servlet路径
public String getServletPaht();

// 获取项目的真实路径
public String getRealPath(String s);

具体使用:

public class CServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.print("contextPath:"+req.getContextPath());
        out.print("<br>");
        out.print("servletPath:"+req.getServletPath());
        out.print("<br>");
        out.print("requestURI:"+req.getRequestURI());
        out.print("<br>");
        out.print("servletContext.getRealPath:"+this.getServletContext().getRealPath("/c"));
    }
}

配置文件:

<servlet>
    <servlet-name>cServlet</servlet-name>
    <servlet-class>com.powernode.javaweb.httpServlet.CServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>cServlet</servlet-name>
    <url-pattern>/aaa/bbb/ccc/c</url-pattern>
</servlet-mapping>

结果:

contextPath:  /Servlet03
servletPath:  /aaa/bbb/ccc/c
requestURI:   /Servlet03/aaa/bbb/ccc/c
servletContext.getRealPath:
E:\Java\JavaSE\JavaWeb\out\artifacts\Servlet03_war_exploded\c

第十章 作用域

request对象实际上又称为“请求域”对象。

1. 应用域对象

  • 应用域是:ServletContext (Servlet上下文对象。)

  • 什么情况下会考虑向ServletContext这个应用域当中绑定数据呢?

    • 第一:所有用户共享的数据。
    • 第二:这个共享的数据量很小。
    • 第三:这个共享的数据很少的修改操作。
    • 在以上三个条件都满足的情况下,使用这个应用域对象,可以大大提高我们程序执行效率。
      实际上向应用域当中绑定数据,就相当于把数据放到了缓存(Cache)当中,然后用户访问的时候直接从缓存中取,减少IO的操作,大大提升系统的性能,所以缓存技术是提高系统性能的重要手段。
  • 你见过哪些缓存技术呢?

    1. 字符串常量池
    2. 整数型常量池 [-128~127],但凡是在这个范围当中的Integer对象不再创建新对象,直接从这个整数型常量池中获取。大大提升系统性能。
    3. 数据库连接池(提前创建好N个连接对象,将连接对象放到集合当中,使用连接对象的时候,直接从缓存中拿。省去了连接对象的创建过程。效率提升。)
    4. 线程池(Tomcat服务器就是支持多线程的。所谓的线程池就是提前先创建好N个线程对象,将线程对象存储到集合中,然后用户请求过来之后,直接从线程池中获取线程对象,直接拿来用。提升系统性能)
    5. 后期你还会学习更多的缓存技术,例如:redis、mongoDB…
  • ServletContext当中有三个操作域的方法:

void setAttribute(String name, Object obj); // 向域当中绑定数据。
Object getAttribute(String name); // 从域当中根据name获取数据。
void removeAttribute(String name); // 将域当中绑定的数据移除

// 以上的操作类似于Map集合的操作。
Map<String, Object> map;
map.put("name", obj); // 向map集合中放key和value
Object obj = map.get("name"); // 通过map集合的key获取value
map.remove("name"); // 通过Map集合的key删除key和value这个键值对。

2. “请求域”对象

  • “请求域”对象要比“应用域”对象范围小很多。生命周期短很多。请求域只在一次请求内有效。

  • 一个请求对象request对应一个请求域对象。一次请求结束之后,这个请求域就销毁了。

  • 请求域对象也有这三个方法:

void setAttribute(String name, Object obj); // 向域当中绑定数据。
Object getAttribute(String name); // 从域当中根据name获取数据。
void removeAttribute(String name); // 将域当中绑定的数据移除
  • 请求域和应用域的选用原则?
    • 尽量使用小的域对象,因为小的域对象占用的资源较少。

第十一章 转发方式

Servlet中三种请求转发的方式:

1、 forward:是指转发,将当前request和response对象保存,交给指定的url处理。并没有表示页面的跳转,所以地址栏的地址不会发生改变。

2、 redirect:是指重定向,包含两次浏览器请求,浏览器根据url请求一个新的页面,所有的业务处理都转到下一个页面,地址栏的地址会变发生改变。

3、 include:意为包含,即包含url中的内容,进一步理解为,将url中的内容包含进当前的servlet当中来,并用当前servlet的request和respose来执行url中的内容处理业务.所以不会发生页面的跳转,地址栏地址不会发生改变。

1. forward请求转发

请求转发是将请求再转发到其他地址,转发过程中使用的是同一个请求,转发后浏览器地址栏内容不变。

在这里插入图片描述

请求转发(服务器端转发):

1、客户端(HTML)向服务器发送一条请求,服务端发现匹配的servlet,并指定它去执行。当这个servlet执行完之后,它要调用getRequestDispacther()方法,返回值类型是RequestDispatcher,设定目标资源jsp(jsp==servlet是服务端)。

2、调用forward(ServletRequest req,ServletResponse res):该方法是RequestDispatcher接口的方法,将请求从一个servlet转发到服务器上另一个资源jsp(目标资源)(jsp==servlet是服务端)

3、目标资源jsp(jsp==servlet是服务端)接收转发过来的请求,并将接收数据后的jsp生成HTML返回给客户端

重点:转发是服务器行为,因此也是在这个web应用内转发,整个过程是一个请求一个响应。可以看做一个request请求里包含了多个servlet,多个servlet共享的是同一个request,所以他们之间可以通过getAttribute()读取前一个servlet的存值。

(因为是一次请求,所以地址栏是不会改变的)

代码演示:

// 第一步:获取请求转发器对象
// 相当于把"/b"这个路径包装到请求转发器当中,实际上是把下一个跳转的资源的路径告知给Tomcat服务器了。
RequestDispatcher dispatcher = request.getRequestDispatcher("/b");

// 第二步:调用请求转发器RequestDispatcher的forward方法。进行转发。
// 转发的时候:这两个参数很重要。request和response都是要传递给下一个资源的。
dispatcher.forward(request, response);

// 一行代码搞定转发。
request.getRequestDispatcher("/b").forward(request, response);

// 转发到一个Servlet,也可以转发到一个HTML,只要是WEB容器当中的合法资源即可。

不过要注意的是,我们通过request对象的getRequestDispatcher()方法进行资源跳转的路径是不需要添加项目名的,就像web.xml文件中的Servlet关联路径一样,不需要以项目名开头

2. redirect重定向

由原请求地址重新定位到某个新地址,原有的请求失效,客户端看到的是新的请求返回的响应结果,客户端浏览器地址栏变为新的请求地址。其中第二次请求是由客户端浏览器自动发出

在这里插入图片描述

从上图中看出,知道为什么要用response而不用request了吧?

由response执行重定向操作: response.sendRedirect();

把上面两个图简化后为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

重定向(客户端跳转):

1、客户端(HTML)发送一个请求到服务器,服务器匹配servlet,这都和请求转发一样。

2、servlet处理完之后调用了sendRedirect()这个方法,这个方法是response的方法,所以,当这个servlet处理完之后,看到**response.senRedirect()**方法,立即向客户端返回这个响应,响应行告诉客户端你的目标资源在哪里你必须要再发送一个请求,去访问你的目标资源jsp(jsp==servlet客户端)。

3、紧接着客户端收到这个请求后,立刻发出一个新的请求,去请求你的目标资源jsp(这里两个请求互不干扰,相互独立,在前面request里面setAttribute()的任何东西,在后面的request里面都获得不了)

4、目标资源jsp把自己生成HTML返回给客户端。

重点:重定向是客户端行为,也就注定可以向任何地址发送请求,客户端行为的改变是服务器所给的指示,亦即是response的行为返回,每次请求都是新的行为,request不保留上次的内容。

(因为是两次请求,所以地址栏会改变)

3. forward和redircet区别

从地址栏显示来说:

forward是服务器内部的重定向,服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。

redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址。所以地址栏显示的是新的URL。

所以redirect等于客户端向服务器端发出两次request,同时也接受两次response;而forword只有一次请求。

从数据共享来说

forward:forward方法只能在同一个Web应用程序内的资源之间转发请求,是服务器内部的一种操作。由于在整个定向的过程中用的是同一个request,因此forward会将request的信息带到被重定向的jsp或者servlet中使用,所以可以共享数据。

redirect:redirect是服务器通知客户端,让客户端重新发起请求。redirect不仅可以重定向到当前应用程序的其他资源,还可以重定向到同一个站点上的其他应用程序中的资源,甚至是使用绝对URL重定向到其他站点的资源。所以不能共享数据。

从应用场景来说

forward:一般适用于用户登陆的时候,根据角色转发到相应的模块。

redirect:一般适用于用户注销登陆时返回主页面和跳转到其它的网站等.

从效率来说

forward:效率高。

redirect:效率低.

从本质来说:

forword转发是服务器上的行为,而redirect重定向是客户端的行为。

第十二章 数据库CRUD操作

使用Sevlet完成单表的增删改查操作。(B/S结构)

第一步 新建数表

use javawebdb;

drop table if exists t_department;

create table if not exists t_department(
    depno int primary key,
    dname varchar(255),
    dlocation varchar(255)
);

insert into t_department (depno,dname,dlocation) 
values (1,'财务部','上海'),(2,'研发部','北京'),(3,'人事部','天津');

select * from t_department;

第二步 准备前端页面

  • 欢迎页面 : index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>欢迎使用</title>
</head>
<body>
    <h1 align="center" >欢迎来到部门管理系统ao</h1>
    <hr>
    <h2 align="center"><a href="list.html">部门列表</a></h2>
</body>
</html>
  • 新增页面: add.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>新增部门</title>
</head>
<body>
    <h1 align="center">新增部门</h1>
    <hr>
    <form action="list.html" method="get" align="center">

        部门编号:<input type="text" name="depno" > <br>
        部门名称:<input type="text" name="dname" > <br>
        部门位置:<input type="text" name="dlocation" > <br>
        <input type="submit" value="保存">
    </form>
</body>
</html>
  • 修改页面: edit.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>修改部门</title>
</head>
<body>
    <h1 align="center">修改部门</h1>
    <hr>
    <form action="list.html" method="get" align="center">

        部门编号:<input type="text" name="depno" value="" readonly> <br>
        部门名称:<input type="text" name="dname" value=""> <br>
        部门位置:<input type="text" name="dlocation" value=""> <br>
        <input type="submit" value="修改">
    </form>
</body>
</html>
  • 查看页面: show.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>部门详情</title>
    <style>
        td {
            text-align: center;
        }
    </style>
</head>
<body>
    <h1 style="text-align: center;">部门详情</h1>
    <hr>
    <table border="1px" align="center" width="50%" cellspacing="0px" cellpadding="5px">
        <tr>
            <th>序号</th>
            <th>部门编号</th>
            <th>部门名称</th>
            <th>部门位置</th>
        </tr>
        <tr>
            <td>1</td>
            <td>10</td>
            <td>财务部</td>
            <td>北京</td>
        </tr>
        <tr colspan="4">
            <input type="button" value="后退" onclick="window.history.back()">
        </tr>
    </table>
</body>
</html>
  • 部门页面:list.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>部门</title>
    <style>
        td {
            text-align: center;
        }
    </style>
</head>
<body>
    <h1 style="text-align: center;">部门列表</h1>
    <hr>
    <table border="1px" align="center" width="50%" cellspacing="0px" cellpadding="5px">
        <tr>
            <td colspan="4"><a href="add.html">新增</a></td>
        </tr>
        <tr>
            <th>序号</th>
            <th>部门编号</th>
            <th>部门名称</th>
            <th>操作</th>
        </tr>
        <tr>
            <td>1</td>
            <td>10</td>
            <td>财务部</td>
            <td>
                <a href="javascript:void(0)" onclick="window.confirm('确定删除吗?')" >删除</a>
                <a href="edit.html">修改</a>
                <a href="show.html">详情</a>
            </td>
        </tr>
    </table>
</body>
</html>

第三步 IDEA搭建开发环境

  • 新建项目
  • 添加模块
  • 添加web框架
  • 添加依赖 Servlet-api.jar 和 jsp-api.jar
  • 导入数据库连接jar包到 WEB-INF\lib
  • 准备工具类(JDBC类)
package com.powernode.javaweb.utils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
import java.util.ResourceBundle;

/**
 * @Date: 2023/4/27-4
 * @Author: HGA
 * @Class: DBUtil
 * Description:
 */

public class DBUtil {

    private static ResourceBundle bundle = ResourceBundle.getBundle("resources.jdbc");
    private static String driver = bundle.getString("JDBC.driver");
    private static String url = bundle.getString("JDBC.url");
    private static String user = bundle.getString("JDBC.user");
    private static String password = bundle.getString("JDBC.password");

    static {
        // 注册驱动
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    // 获取连接
    public static Connection getConnection() throws SQLException {
        Connection conn = DriverManager.getConnection(url, user, password);
        return conn;
    }

    //释放资源  
    public static void close(Statement stmt, Connection conn) {
        close(null, stmt, conn);
    }

    //重载释放资源  
    public static void close(ResultSet rs, Statement stmt, Connection conn) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}
  • 将所有的页面导入到web目录下
  • 在web.xml配置文件中设置欢迎页
<welcome-file-list>
    <welcome-file>index.html</welcome-file>
</welcome-file-list>

第五步 实现部门列表功能

  1. 需改前端页面index.html超链接
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>欢迎使用</title>
</head>
<body>
    <h1 align="center" >欢迎来到部门管理系统ao</h1>
    <hr>
    <!-- 注意这里的链接是跳转到 ListServlet 类-->
    <!-- 注意这里的路径 是要加上项目名的 并且以 ’/‘ 开头-->
    <h2 align="center"><a href="/oa/dept/list">部门列表</a></h2>
</body>
</html>
  1. web.xml 配置文件中添加servlet路径
<servlet>
    <servlet-name>list</servlet-name>
    <servlet-class>com.powernode.javaweb.crud.ListServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>list</servlet-name>
    <!-- 注意这里的路径: 必须以’/‘ 开头,不用加项目名-->
    <url-pattern>/dept/list</url-pattern>
</servlet-mapping>
  1. 编写 ListServlet 类继承HttpServlet类,重写 **doGet()**方法
package com.powernode.javaweb.crud;

import resources.utils.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @Date: 2023/4/27-17:02
 * @Author: HGA
 * @Class: ListServlet
 * Description:
 */

public class ListServlet extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 连接数据库,查询所有的部门
        Connection conn = null;
        PreparedStatement ps =null;
        ResultSet rs = null;

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        out.print(" <!DOCTYPE html>");
        out.print(" <html lang='en'>");
        out.print(" <head>");
        out.print("     <meta charset='UTF-8'>");
        out.print("     <meta http-equiv='X-UA-Compatible' content='IE=edge'>");
        out.print("     <meta name='viewport' content='width=device-width, initial-scale=1.0'>");
        out.print("     <title>部门</title>");
        out.print("     <style>");
        out.print("         td {");
        out.print("             text-align: center;");
        out.print("         }");
        out.print("     </style>");
        out.print(" </head>");
        out.print(" <body>");
        out.print("     <h1 style='text-align: center;'>部门列表</h1>");
        out.print("     <hr>");
        out.print("     <table border='1px' align='center' width='50%' cellspacing='0px' cellpadding='5px'>");
        out.print("         <tr>");
        out.print("             <td colspan='4'><a href='add.html'>新增</a></td>");
        out.print("         </tr>");
        out.print("         <tr>");
        out.print("             <th>序号</th>");
        out.print("             <th>部门编号</th>");
        out.print("             <th>部门名称</th>");
        out.print("             <th>操作</th>");
        out.print("         </tr>");

        try {
            conn = DBUtil.getConnection();
            String sql = "select depno,dname,dlocation from t_department";
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();

            int i = 0;
            while(rs.next()){
                String depno = rs.getString("depno");
                String dname = rs.getString("dname");
                out.print("         <tr>");
                out.print("             <td>"+(++i)+"</td>");
                out.print("             <td>"+depno+"</td>");
                out.print("             <td>"+dname+"</td>");
                out.print("             <td>");
                out.print("                 <a href='javascript:void(0)' οnclick='window.confirm('确定删除吗?')' >删除</a>");
                out.print("                 <a href='edit.html'>修改</a>");
                out.print("                 <a href='show.html'>详情</a>");
                out.print("             </td>");
                out.print("         </tr>");
            }

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(rs,ps,conn);
        }
        out.print("     </table>");
        out.print(" </body>");
        out.print(" </html>");
    }
}

第六步 实现部门详情功能

  1. ListServlet类中,修改详情链接地址(第80行左右)
  • 这个路径是超链接,是前端实现跳转的,要加项目名,这里使用动态添加项目名。
out.print("                 <a href='"+request.getContextPath()+"/dept/show?depno="+depno+"'>详情</a>");
  1. web.xml 配置文件中添加servlet路径
<servlet>
    <servlet-name>show</servlet-name>
    <servlet-class>com.powernode.javaweb.crud.ShowServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>show</servlet-name>
    <url-pattern>/dept/show</url-pattern>
</servlet-mapping>
  1. 编写 ShowServlet 类继承HttpServlet类,重写 **doGet()**方法
package com.powernode.javaweb.crud;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.powernode.javaweb.utils.DBUtil;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @Date: 2023/4/27-18:24
 * @Author: HGA
 * @Class: ShowServlet
 * Description:
 */


public class ShowServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        out.print(" <!DOCTYPE html>");
        out.print(" <html lang='en'>");
        out.print(" <head>");
        out.print("     <meta charset='UTF-8'>");
        out.print("     <meta http-equiv='X-UA-Compatible' content='IE=edge'>");
        out.print("     <meta name='viewport' content='width=device-width, initial-scale=1.0'>");
        out.print("     <title>部门详情</title>");
        out.print("     <style>");
        out.print("         td {");
        out.print("             text-align: center;");
        out.print("         }");
        out.print("     </style>");
        out.print(" </head>");
        out.print(" <body>");
        out.print("     <h1 style='text-align: center;'>部门详情</h1>");
        out.print("     <hr>");
        out.print("     <table border='1px' align='center' width='50%' cellspacing='0px' cellpadding='5px'>");
        out.print("         <tr>");
        out.print("             <th>部门编号</th>");
        out.print("             <th>部门名称</th>");
        out.print("             <th>部门位置</th>");
        out.print("         </tr>");

        // 获取请求参数
        String depno = request.getParameter("depno");

        Connection conn =null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            conn = DBUtil.getConnection();
            String sql = "select depno,dname,dlocation from t_department where depno = ?";
            ps = conn.prepareStatement(sql);
            ps.setInt(1,Integer.parseInt(depno));
            rs = ps.executeQuery();
            if(rs.next()){
                String dname = rs.getString("dname");
                String dlocation = rs.getString("dlocation");
                out.print("         <tr>");
                out.print("             <td>"+depno+"</td>");
                out.print("             <td>"+dname+"</td>");
                out.print("             <td>"+dlocation+"</td>");
                out.print("         </tr>");
            }
            
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(rs,ps,conn);
        }
        
        out.print("         <tr> ");
        out.print("             <td colspan='3'>");
        out.print("                 <input type='button' value='后退' οnclick='window.history.back()'>");
        out.print("             </td>");
        out.print("         </tr>");
        out.print("     </table>");
        out.print(" </body>");
        out.print(" </html>");
    }
}

第七步 实现删除部门功能

  1. 实现删除功能,注意事项
  • 删除功能,一定要有提示信息,避免用户误操作。

  • 如何提示呢?(在 ListServlet 类中,找到删除超链接)

    • 修改删除超链接 href 属性,

      <a href='javascript:void(0)'>删除</a>");
      
      • 这样设置,可以保留超链接的性质,任然是超链接。只是点击后,不进行页面的跳转。
    • 给超链接添加点击事件:(传参 部门编号)

      <a href='javascript:void(0)' onclick='del("+depno+")' >删除</a>");
      
      • 编写 script 脚本
      <script type="text/javascript">
          function del(depno){
              if(window.confirm('确定删除吗?')){
                  document.location.href='/oa/dept/delete?depno='+depno;
              }
          }
      </script>
      
    • 将编写的 script 脚本添加到 ListServlet 类

              out.print("     <script type='text/javascript'>");
              out.print("         function del(depno){");
              out.print("             if(window.confirm('确定删除吗?')){");
              out.print("                  document.location.href='"+request.getContextPath()+"/dept/delete?depno='+depno;");
              out.print("             }");
              out.print("         }");
              out.print("     </script>");
      
  1. 编写web.xml配置文件
<servlet>
    <servlet-name>delete</servlet-name>
    <servlet-class>com.powernode.javaweb.crud.DeleteServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>delete</servlet-name>
    <url-pattern>/dept/delete</url-pattern>
</servlet-mapping>
  1. 编写 DeleteServlet 类继承HttpServlet类,重写 **doGet()**方法
package com.powernode.javaweb.crud;

import com.powernode.javaweb.utils.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @Date: 2023/4/27-19:33
 * @Author: HGA
 * @Class: DeleteServlet
 * Description:
 */


public class DeleteServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        // 获取请求参数
        String depno = request.getParameter("depno");

        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = DBUtil.getConnection();
            conn.setAutoCommit(false);
            String sql = "delete from t_department where depno = ?";
            ps = conn.prepareStatement(sql);
            ps.setInt(1,Integer.parseInt(depno));
            int i = ps.executeUpdate();
            if(i == 0) {
                // 删除失败!请求转发到 Error.html
 request.getRequestDispatcher("/Error.html").forward(request,response);
            }else {
                // 删除成功! 重定向到 ListServlet 类
                response.sendRedirect(request.getContextPath()+"/dept/list");
            }
        } catch (SQLException e) {
            if(conn != null){
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    throw new RuntimeException(ex);
                }
            }
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(ps,conn);
        }


    }
}
  1. 在web 目录下,新建错误页面 Error.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>错误</title>
</head>
<body>
  <h1 style="text-align: center">出错了~!</h1>
</body>
</html>

第八步 实现新增部门功能

  1. 修改 ListServlet 类中的新增超链接:(第53行左右)

    <td colspan='4'><a href='"+request.getContextPath()+"/add.html'>新增</a></td>
    
  2. add.html中修改form表单

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>新增部门</title>
    </head>
    <body>
        <h1 align="center">新增部门</h1>
        <hr>
        <form action="/oa/dept/save" method="post" align="center">
    
            部门编号:<input type="text" name="depno" > <br>
            部门名称:<input type="text" name="dname" > <br>
            部门位置:<input type="text" name="dlocation" > <br>
            <input type="submit" value="保存">
        </form>
    </body>
    </html>
    
  3. 编写 web.html 配置文件

    <servlet>
        <servlet-name>save</servlet-name>
        <servlet-class>com.powernode.javaweb.crud.SaveServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>save</servlet-name>
        <url-pattern>/dept/save</url-pattern>
    </servlet-mapping>
    
  4. 编写SaveServlet 类继承HttpServlet类,重写 **doPost()**方法

    package com.powernode.javaweb.crud;
    
    import com.powernode.javaweb.utils.DBUtil;
    import jakarta.servlet.RequestDispatcher;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    
    import java.io.IOException;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @Date: 2023/4/27-21:05
     * @Author: HGA
     * @Class: SaveServlet
     * Description:
     */
    
    
    public class SaveServlet extends HttpServlet {
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            request.setCharacterEncoding("UTF-8");
    
            Connection conn = null;
            PreparedStatement ps = null;
    
            // 获取请求参数
            String depno = request.getParameter("depno");
            String dname = request.getParameter("dname");
            String dlocation = request.getParameter("dlocation");
    
            try {
                // 定义获取连接
                conn = DBUtil.getConnection();
                conn.setAutoCommit(false);
                // 定义sql
                String sql = "insert into t_department (depno,dname,dlocation) values(?,?,?)";
                // 获取执行sql对象
                ps = conn.prepareStatement(sql);
    
                // 给? 赋值
                ps.setString(1,depno);
                ps.setString(2,dname);
                ps.setString(3,dlocation);
                // 执行sql,更新数据库
                int res = ps.executeUpdate();
                // 判断结果
                // 返回结果为1:跳转到 ListServlet
                if(res == 1){
                    conn.commit();
                    response.sendRedirect(request.getContextPath()+"/dept/list");
                }else {
                    // 返回结果为0:跳转到 Error.html
                    request.getRequestDispatcher("/Error.html").forward(request,response);
                }
    
            } catch (SQLException e) {
                if (conn != null) {
                    try {
                        conn.rollback();
                    } catch (SQLException ex) {
                        throw new RuntimeException(ex);
                    }
                }
                throw new RuntimeException(e);
            } finally {
                // 释放资源
                DBUtil.close(ps,conn);
            }
        }
    }
    
  5. 添加成功后跳转用的是重定向,如果是请求转发,会出现问题。

    • ListServlet 类中,我们只重写了 **doGet()**方法,在 SaveServlet类中 这里的请求是 post请求, 如果请求转发,那么就会出现 405 错误

    • 解决方法:

      • ListServlet 类中重写 **doPost()方法,在doPost()**方法中调用 **doGet()**方法。
      @Override
      protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          this.doGet(req,resp);
      }
      

第九步 实现修改部门功能

  1. ListServlet类 中,编辑 修改超链接:(第90行左右)

    <a href='"+request.getContextPath()+"/dept/modify?depno="+depno+"'>修改</a>
    
  2. 编写 web.xml配置文件:

    <servlet>
        <servlet-name>modify</servlet-name>
        <servlet-class>com.powernode.javaweb.crud.ModifyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>modify</servlet-name>
        <url-pattern>/dept/modify</url-pattern>
    </servlet-mapping>
    
  3. 编写 ModifyServlet类继承 HttpServlet类,重写 doGet方法:

    package com.powernode.javaweb.crud;
    
    import com.powernode.javaweb.utils.DBUtil;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    
    import java.io.IOException;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @Date: 2023/4/27-22:41
     * @Author: HGA
     * @Class: ModifyServlet
     * Description:
     */
    
    
    public class ModifyServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
            // 获取请求参数
            String depno = request.getParameter("depno");
            
            // 连接数据库,
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            
            try {
                // 获取连接
                conn = DBUtil.getConnection();
                // 定义sql
                String sql = "select depno,dname,dlocation from t_department where depno = ?";
                // 获取执行sql对象
                ps = conn.prepareStatement(sql);
                // 给? 赋值
                ps.setString(1,depno);
                // 执行sql
                rs = ps.executeQuery();
                if(rs.next()){
                    String dname = rs.getString("dname");
                    String dlocation = rs.getString("dlocation");
                    // 查到了数据,将数据放到请求域中
                    request.setAttribute("depno",depno);
                    request.setAttribute("dname",dname);
                    request.setAttribute("dlocation",dlocation);
                    // 请求转发到 EditServlet 类中
                    request.getRequestDispatcher("/dept/edit").forward(request,response);
                }else {
                    // 没有查到就跳转到出错页面
                    request.getRequestDispatcher("/Error.html").forward(request,response);
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                // 释放资源
                DBUtil.close(rs,ps,conn);
            }
    
        }
    }
    
  4. 编写 web.xml配置文件

    <servlet>
        <servlet-name>edit</servlet-name>
        <servlet-class>com.powernode.javaweb.crud.EditServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>edit</servlet-name>
        <url-pattern>/dept/edit</url-pattern>
    </servlet-mapping>
    
  5. 编写 EditServlet类继承 HttpServlet类 ,重写 **doGet()**方法

    package com.powernode.javaweb.crud;
    
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    /**
     * @Date: 2023/4/27-23:00
     * @Author: HGA
     * @Class: EditServlet
     * Description:
     */
    
    
    public class EditServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            
            // 设置响应头
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            
            // 获取请求参数
            String depno = (String) request.getAttribute("depno");
            String dname = (String) request.getAttribute("dname");
            String dlocation = (String) request.getAttribute("dlocation");
    
            out.print("<!DOCTYPE html>");
            out.print("<html lang='en'>");
            out.print("<head>");
            out.print("    <meta charset='UTF-8'>");
            out.print("    <meta http-equiv='X-UA-Compatible' content='IE=edge'>");
            out.print("    <meta name='viewport' content='width=device-width, initial-scale=1.0'>");
            out.print("    <title>修改部门</title>");
            out.print("</head>");
            out.print("<body>");
            out.print("    <h1 align='center'>修改部门</h1>");
            out.print("    <hr>");
            out.print("    <form action='"+request.getContextPath()+"/dept/update' method='post' align='center'>");
            out.print("                部门编号:<input type='text' name='depno' value='"+depno+"' readonly> <br>");
            out.print("                部门名称:<input type='text' name='dname' value='"+dname+"'> <br>");
            out.print("                部门位置:<input type='text' name='dlocation' value='"+dlocation+"'> <br>");
            out.print("        <input type='submit' value='修改'>");
            out.print("    </form>");
            out.print("</body>");
            out.print("</html>");
        }
    }
    
  6. 编写 web.xml配置文件

    <servlet>
        <servlet-name>update</servlet-name>
        <servlet-class>com.powernode.javaweb.crud.UpdateServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>update</servlet-name>
        <url-pattern>/dept/update</url-pattern>
    </servlet-mapping>
    
  7. 编写 UpdateServlet类继承 HttpServlet类,并重写 **doPost()**方法

    package com.powernode.javaweb.crud;
    
    import com.powernode.javaweb.utils.DBUtil;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    
    import java.io.IOException;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    /**
     * @Date: 2023/4/27-23:21
     * @Author: HGA
     * @Class: UpdateServlet
     * Description:
     */
    
    
    public class UpdateServlet extends HttpServlet {
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            
            // 获取请求参数
            String depno = request.getParameter("depno");
            String dname = request.getParameter("dname");
            String dlocation = request.getParameter("dlocation");
            
            // 连接数据库,
            Connection conn = null;
            PreparedStatement ps = null;
            
            try {
                // 获取连接
                conn = DBUtil.getConnection();
                conn.setAutoCommit(false);
                // 定义sql
                String sql = "update t_department set dname = ?,dlocation = ? where depno =?";
                // 获取执行sql对象
                ps = conn.prepareStatement(sql);
                // 给? 赋值
                ps.setString(1,dname);
                ps.setString(2,dlocation);
                ps.setString(3,depno);
                // 执行sql
                int count = ps.executeUpdate();
                if(count == 1){
                    // 更新成功跳转到 ListServlet 类
                    conn.commit();
                    response.sendRedirect(request.getContextPath()+"/dept/list");
                }else {
                    // 请求转发到 EditServlet 类
                    request.getRequestDispatcher("/dept/edit").forward(request,response);
                }
            } catch (SQLException e) {
                if (conn != null) {
                    try {
                        conn.rollback();
                    } catch (SQLException ex) {
                        throw new RuntimeException(ex);
                    }
                }
                throw new RuntimeException(e);
            } finally {
                // 释放资源
                DBUtil.close(ps,conn);
            }
        }
    }
    

第十三章 Servlet 注解开发

  • 分析oa项目中的web.xml文件

    • 现在只是一个单标的CRUD,没有复杂的业务逻辑,很简单的一丢丢功能。web.xml文件中就有如此多的配置信息。如果采用这种方式,对于一个大的项目来说,这样的话web.xml文件会非常庞大,有可能最终会达到几十兆。
    • 在web.xml文件中进行servlet信息的配置,显然开发效率比较低,每一个都需要配置一下。
    • 而且在web.xml文件中的配置是很少被修改的,所以这种配置信息能不能直接写到java类当中呢?可以的。
  • Servlet3.0版本之后,推出了各种Servlet基于注解式开发。优点是什么?

    • 开发效率高,不需要编写大量的配置信息。直接在java类上使用注解进行标注。

    • web.xml文件体积变小了。

  • 并不是说注解有了之后,web.xml文件就不需要了:

    • 有一些需要变化的信息,还是要配置到web.xml文件中。一般都是 注解+配置文件 的开发模式。
    • 一些不会经常变化修改的配置建议使用注解。一些可能会被修改的建议写到配置文件中。

1. 第一个Servlet注解

  • 我们的第一个注解:

    • jakarta.servlet.annotation.WebServlet
      
    • 在Servlet类上使用:@WebServlet,WebServlet注解中有哪些属性呢?
      • name属性:用来指定Servlet的名字。等同于:类名
      • urlPatterns属性:用来指定Servlet的映射路径。可以指定多个字符串。
      • loadOnStartUp属性:用来指定在服务器启动阶段是否加载该Servlet。等同于:
      • value属性:当注解的属性名是value的时候,使用注解的时候,value属性名是可以省略的。
    //@WebServlet(urlPatterns = {"/welcome1", "/welcome2"})
    // 注意:当注解的属性是一个数组,并且数组中只有一个元素,大括号可以省略。
    //@WebServlet(urlPatterns = "/welcome")
    // 这个value属性和urlPatterns属性一致,都是用来指定Servlet的映射路径的。
    //@WebServlet(value = {"/welcome1", "/welcome2"})
    // 如果注解的属性名是value的话,属性名也是可以省略的。
    //@WebServlet(value = "/welcome1")
    //@WebServlet({"/wel", "/abc", "/def"})
    @WebServlet("/wel")
    public class WelcomeServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.print("欢迎学习Servlet。");
        }
    }
    
    • 注意:不是必须将所有属性都写上,只需要提供需要的。(需要什么用什么。)
    • 注意:属性是一个数组,如果数组中只有一个元素,使用该注解的时候,属性值的大括号可以省略。
    package com.bjpowernode.javaweb.servlet;
    
    import jakarta.servlet.ServletException;
    import jakarta.servlet.annotation.WebInitParam;
    import jakarta.servlet.annotation.WebServlet;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.util.Enumeration;
    
    @WebServlet(name = "hello",
            urlPatterns = {"/hello1", "/hello2", "/hello3"},
            loadOnStartup = 1, // 服务器部署启动时加载该类
            // 初始化参数
     initParams = {@WebInitParam(name="username", value="root"), @WebInitParam(name="password", value="123")})
    public class HelloServlet extends HttpServlet {
    
        // 无参数构造方法
    
        public HelloServlet() {
            System.out.println("无参数构造方法执行,HelloServlet加载完成");
        }
    
    
        /*@WebServlet
        private String name;*/
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            // 获取Servlet Name
            String servletName = getServletName();
            out.print("servlet name = " + servletName + "<br>");
    
            // 获取servlet path
            String servletPath = request.getServletPath();
            out.print("servlet path = " + servletPath + "<br>");
    
            // 获取初始化参数
            Enumeration<String> names = getInitParameterNames();
            while (names.hasMoreElements()) {
                String name = names.nextElement();
                String value = getInitParameter(name);
                out.print(name + "=" + value + "<br>");
            }
        }
    }
    
  • 注解对象的使用格式:

    • @注解名称(属性名=属性值, 属性名=属性值, 属性名=属性值…)

2. 反射获取注解

package com.powernode.javaweb.annotation;

import jakarta.servlet.annotation.WebServlet;

/**
 * @Date: 2023/4/28-11:21
 * @Author: HGA
 * @Class: ReflectAnnotation
 * Description: 通过反射机制,获取类注解
 */

public class ReflectAnnotation {

    public static void main(String[] args) throws ClassNotFoundException {
        // 获取类
        Class<?> clazz = Class.forName("com.powernode.javaweb.annotation.HelloServlet");

        // 获取这个类上面的注解对象
        // 是否有这个WebServlet注解
        if(clazz.isAnnotationPresent(WebServlet.class)){
            // 获取这个注解对象
            WebServlet annotation = clazz.getAnnotation(WebServlet.class);

            // 获取 value 值
            String[] value = annotation.value();

            for (String s : value) {
                System.out.println(s);
            }
        }
    }
}

3. 模板方法设计模式解决类爆炸

  • 上面的注解解决了配置文件的问题。但是现在的oa项目仍然存在一个比较臃肿的问题。
    • 一个单标的CRUD,就写了6个Servlet。如果一个复杂的业务系统,这种开发方式,显然会导致类爆炸。(类的数量太大。)
  • 怎么解决这个类爆炸问题?
    • 可以使用模板方法设计模式。
  • 怎么解决类爆炸问题?
    • 以前的设计是一个请求一个Servlet类。1000个请求对应1000个Servlet类。导致类爆炸。
    • 可以这样做:一个请求对应一个方法。一个业务对应一个Servlet类。
    • 处理部门相关业务的对应一个DeptServlet。处理用户相关业务的对应一个UserServlet。处理银行卡卡片业务对应一个CardServlet。
package com.bjpowernode.oa.web.action;

import com.bjpowernode.oa.utils.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

// 模板类
@WebServlet({"/dept/list", "/dept/save", "/dept/edit", "/dept/detail", "/dept/delete", "/dept/modify"})
// 模糊匹配
// 只要请求路径是以"/dept"开始的,都走这个Servlet。
//@WebServlet("/dept/*")
public class DeptServlet extends HttpServlet {

    // 模板方法
    // 重写service方法(并没有重写doGet或者doPost)

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取servlet path
        String servletPath = request.getServletPath();
        if("/dept/list".equals(servletPath)){
            doList(request, response);
        } else if("/dept/save".equals(servletPath)){
            doSave(request, response);
        } else if("/dept/edit".equals(servletPath)){
            doEdit(request, response);
        } else if("/dept/detail".equals(servletPath)){
            doDetail(request, response);
        } else if("/dept/delete".equals(servletPath)){
            doDel(request, response);
        } else if("/dept/modify".equals(servletPath)){
            doModify(request, response);
        }
    }

    private void doList(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取应用的根路径
        String contextPath = request.getContextPath();

        // 设置响应的内容类型以及字符集。防止中文乱码问题。
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("	<head>");
        out.print("		<meta charset='utf-8'>");
        out.print("		<title>部门列表页面</title>");

        out.print("<script type='text/javascript'>");
        out.print("    function del(dno){");
        out.print("        if(window.confirm('亲,删了不可恢复哦!')){");
        out.print("            document.location.href = '"+contextPath+"/dept/delete?deptno=' + dno");
        out.print("        }");
        out.print("    }");
        out.print("</script>");

        out.print("	</head>");
        out.print("	<body>");
        out.print("		<h1 align='center'>部门列表</h1>");
        out.print("		<hr >");
        out.print("		<table border='1px' align='center' width='50%'>");
        out.print("			<tr>");
        out.print("				<th>序号</th>");
        out.print("				<th>部门编号</th>");
        out.print("				<th>部门名称</th>");
        out.print("				<th>操作</th>");
        out.print("			</tr>");
        /*上面一部分是死的*/

        // 连接数据库,查询所有的部门
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 获取连接
            conn = DBUtil.getConnection();
            // 获取预编译的数据库操作对象
            String sql = "select deptno as a,dname,loc from dept";
            ps = conn.prepareStatement(sql);
            // 执行SQL语句
            rs = ps.executeQuery();
            // 处理结果集
            int i = 0;
            while(rs.next()){
                String deptno = rs.getString("a");
                String dname = rs.getString("dname");
                String loc = rs.getString("loc");

                out.print("			<tr>");
                out.print("				<td>"+(++i)+"</td>");
                out.print("				<td>"+deptno+"</td>");
                out.print("				<td>"+dname+"</td>");
                out.print("				<td>");
                out.print("					<a href='javascript:void(0)' οnclick='del("+deptno+")'>删除</a>");
                out.print("					<a href='"+contextPath+"/dept/edit?deptno="+deptno+"'>修改</a>");
                out.print("					<a href='"+contextPath+"/dept/detail?fdsafdsas="+deptno+"'>详情</a>");
                out.print("				</td>");
                out.print("			</tr>");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            DBUtil.close(conn, ps, rs);
        }

        /*下面一部分是死的*/
        out.print("		</table>");
        out.print("		<hr >");
        out.print("		<a href='"+contextPath+"/add.html'>新增部门</a>");
        out.print("	</body>");
        out.print("</html>");
    }

    private void doSave(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取部门的信息
        // 注意乱码问题(Tomcat10不会出现这个问题)
        request.setCharacterEncoding("UTF-8");
        String deptno = request.getParameter("deptno");
        String dname = request.getParameter("dname");
        String loc = request.getParameter("loc");

        // 连接数据库执行insert语句
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DBUtil.getConnection();
            String sql = "insert into dept(deptno, dname, loc) values(?,?,?)";
            ps = conn.prepareStatement(sql);
            ps.setString(1, deptno);
            ps.setString(2, dname);
            ps.setString(3, loc);
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, null);
        }

        if (count == 1) {
            // 保存成功跳转到列表页面
            // 转发是一次请求。
            //request.getRequestDispatcher("/dept/list").forward(request, response);

            // 这里最好使用重定向(浏览器会发一次全新的请求。)
            // 浏览器在地址栏上发送请求,这个请求是get请求。
            response.sendRedirect(request.getContextPath() + "/dept/list");

        }else{
            // 保存失败跳转到错误页面
            //request.getRequestDispatcher("/error.html").forward(request, response);

            // 这里也建议使用重定向。
            response.sendRedirect(request.getContextPath() + "/error.html");
        }
    }

    private void doEdit(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 获取应用的根路径。
        String contextPath = request.getContextPath();

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("	<head>");
        out.print("		<meta charset='utf-8'>");
        out.print("		<title>修改部门</title>");
        out.print("	</head>");
        out.print("	<body>");
        out.print("		<h1>修改部门</h1>");
        out.print("		<hr >");
        out.print("		<form action='"+contextPath+"/dept/modify' method='post'>");

        // 获取部门编号
        String deptno = request.getParameter("deptno");
        // 连接数据库,根据部门编号查询部门的信息。
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = DBUtil.getConnection();
            String sql = "select dname, loc as location from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, deptno);
            rs = ps.executeQuery();
            // 这个结果集中只有一条记录。
            if(rs.next()){
                String dname = rs.getString("dname");
                String location = rs.getString("location"); // 参数"location"是sql语句查询结果列的列名。
                // 输出动态网页。
                out.print("                部门编号<input type='text' name='deptno' value='"+deptno+"' readonly /><br>");
                out.print("                部门名称<input type='text' name='dname' value='"+dname+"'/><br>");
                out.print("                部门位置<input type='text' name='loc' value='"+location+"'/><br>");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, rs);
        }

        out.print("			<input type='submit' value='修改'/><br>");
        out.print("		</form>");
        out.print("	</body>");
        out.print("</html>");
    }

    private void doDetail(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("	<head>");
        out.print("		<meta charset='utf-8'>");
        out.print("		<title>部门详情</title>");
        out.print("	</head>");
        out.print("	<body>");
        out.print("		<h1>部门详情</h1>");
        out.print("		<hr >");

        // 获取部门编号
        // /oa/dept/detail?fdsafdsas=30
        // 虽然是提交的30,但是服务器获取的是"30"这个字符串。
        String deptno = request.getParameter("fdsafdsas");

        // 连接数据库,根据部门编号查询部门信息。
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = DBUtil.getConnection();
            String sql = "select dname,loc from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, deptno);
            rs = ps.executeQuery();
            // 这个结果集一定只有一条记录。
            if(rs.next()){
                String dname = rs.getString("dname");
                String loc = rs.getString("loc");

                out.print("部门编号:"+deptno+" <br>");
                out.print("部门名称:"+dname+"<br>");
                out.print("部门位置:"+loc+"<br>");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, rs);
        }

        out.print("		<input type='button' value='后退' οnclick='window.history.back()'/>");
        out.print("	</body>");
        out.print("</html>");
    }

    private void doDel(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 根据部门编号,删除部门。
        // 获取部门编号
        String deptno = request.getParameter("deptno");
        // 连接数据库删除数据
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DBUtil.getConnection();
            // 开启事务(自动提交机制关闭)
            conn.setAutoCommit(false);
            String sql = "delete from dept where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, deptno);
            // 返回值是:影响了数据库表当中多少条记录。
            count = ps.executeUpdate();
            // 事务提交
            conn.commit();
        } catch (SQLException e) {
            // 遇到异常要回滚
            if (conn != null) {
                try {
                    conn.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, null);
        }

        // 判断删除成功了还是失败了。
        if (count == 1) {
            //删除成功
            //仍然跳转到部门列表页面
            //部门列表页面的显示需要执行另一个Servlet。怎么办?转发。
            //request.getRequestDispatcher("/dept/list").forward(request, response);
            response.sendRedirect(request.getContextPath() + "/dept/list");
        }else{
            // 删除失败
            //request.getRequestDispatcher("/error.html").forward(request, response);
            response.sendRedirect(request.getContextPath() + "/error.html");
        }
    }

    private void doModify(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 解决请求体的中文乱码问题。
        request.setCharacterEncoding("UTF-8");

        // 获取表单中的数据
        String deptno = request.getParameter("deptno");
        String dname = request.getParameter("dname");
        String loc = request.getParameter("loc");
        // 连接数据库执行更新语句
        Connection conn = null;
        PreparedStatement ps = null;
        int count = 0;
        try {
            conn = DBUtil.getConnection();
            String sql = "update dept set dname = ?, loc = ? where deptno = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, dname);
            ps.setString(2, loc);
            ps.setString(3, deptno);
            count = ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtil.close(conn, ps, null);
        }

        if (count == 1) {
            // 更新成功
            // 跳转到部门列表页面(部门列表页面是通过Java程序动态生成的,所以还需要再次执行另一个Servlet)
            //request.getRequestDispatcher("/dept/list").forward(request, response);

            response.sendRedirect(request.getContextPath() + "/dept/list");
        }else{
            // 更新失败
            //request.getRequestDispatcher("/error.html").forward(request, response);
            response.sendRedirect(request.getContextPath() + "/error.html");
        }
    }
}

4. 分析使用纯粹Servlet开发web应用的缺陷

  • 在Servlet当中编写HTML/CSS/JavaScript等前端代码。存在什么问题?

    • java程序中编写前端代码,编写难度大。麻烦。

    • java程序中编写前端代码,显然程序的耦合度非常高。

    • java程序中编写前端代码,代码非常不美观。

    • java程序中编写前端代码,维护成本太高。(非常难于维护)

      • 修改小小的一个前端代码,只要有改动,就需要重新编译java代码,生成新的class文件,打一个新的war包,重新发布。
  • 思考一下,如果是你的话,你准备怎么解决这个问题?

    • 思路很重要。使用什么样的思路去做、去解决这个问题
    • 上面的那个Servlet(Java程序)能不能不写了,让机器自动生成。我们程序员只需要写这个Servlet程序中的“前端的那段代码”,然后让机器将我们写的“前端代码”自动翻译生成“Servlet这种java程序”。然后机器再自动将“java”程序编译生成"class"文件。然后再使用JVM调用这个class中的方法。

第十三章 JSP技术

1. 第一个JSP程序

  • 在WEB-INF目录之外创建一个index.jsp文件,然后这个文件中没有任何内容。

在这里插入图片描述

  • 将上面的项目部署之后,启动服务器,打开浏览器,访问以下地址:

    • http://localhost:8080/jsp/index.jsp 展现在大家面前的是一个空白。

    • 实际上访问以上的这个:index.jsp,底层执行的是:index_jsp.class 这个java程序。

    • 这个index.jsp会被tomcat翻译生成index_jsp.java文件,然后tomcat服务器又会将index_jsp.java编译生成index_jsp.class文件

    • 访问index.jsp,实际上执行的是index_jsp.class中的方法。

在这里插入图片描述

在这里插入图片描述

2. JSP概述

  • JSP实际上就是一个Servlet。

    • ndex.jsp访问的时候,会自动翻译生成index_jsp.java,会自动编译生成index_jsp.class,那么index_jsp 这就是一个类。
    • index_jsp 类继承 HttpJspBase,而HttpJspBase类继承的是HttpServlet。所以index_jsp类就是一个Servlet类。
    • jsp的生命周期和Servlet的生命周期完全相同。完全就是一个东西。没有任何区别。
      jsp和servlet一样,都是单例的。(假单例。)
  • jsp文件第一次访问的时候是比较慢的,为什么?

    • 第一次比较麻烦:
      • 要把jsp文件翻译生成java源文件
      • java源文件要编译生成class字节码文件
      • 然后通过class去创建servlet对象
      • 然后调用servlet对象的init方法
      • 最后调用servlet对象的service方法。
    • 第二次就比较快了,为什么?
      • 因为第二次直接调用单例servlet对象的service方法即可。
  • JSP是什么?

    • JSP是java程序。(JSP本质还是一个Servlet)
    • JSP是:JavaServer Pages的缩写。(基于Java语言实现的服务器端的页面。)
    • Servlet是JavaEE的13个子规范之一,那么JSP也是JavaEE的13个子规范之一。
    • JSP是一套规范。所有的web容器/web服务器都是遵循这套规范的,都是按照这套规范进行的“翻译”
    • 每一个web容器/web服务器都会内置一个JSP翻译引擎。
  • 对JSP进行错误调试的时候,还是要直接打开JSP文件对应的java文件,检查java代码。
  • 开发JSP的最高境界:
    • 眼前是JSP代码,但是脑袋中呈现的是java代码。
  • JSP既然本质上是一个Servlet,那么JSP和Servlet到底有什么区别呢?
    • 职责不同:
      • Servlet的职责是什么:收集数据。(Servlet的强项是逻辑处理,业务处理,然后链接数据库,获取/收集数据。)
      • JSP的职责是什么:展示数据。(JSP的强项是做数据的展示)

3. JSP的基本语法

学习路径:JSP 语法 | 菜鸟教程 (runoob.com)

4. 四种基本语法

  • 注释:<%- -%>

    • jsp注释语法的格式是:<%– 这里是注释 –%>
    • jsp的注释内容仅仅提供开发过程的提示作用,最后面输出到客户端的html代码中是无法看见jsp注释的。这有别于html代码的注释,html的注释是可以在客户端的源码。
  • 声明:<%! %>

    • jsp声明的语法格式是:<%! 这里是声明内容 %>
    • 之前提到,jsp会在运行的时候由容器编译成servlet文件,而servlet是一个java 对象,因此在jsp中进java变量或者方法的声明和在servlet中的声明是一样的。容器会在编译的时候将jsp中声明的变量和方法编译到对应的servlet中去,且接受private,public,static等修饰符。值得注意的是,每个servlet在容器中只存在一个实例。
  • 输出:<%= %>

    • jsp输出表达式的语法格式:<%=表达式(注意jsp表达式后面无需添加分号表示结束)%>
      jsp中的表达式语句在对应的servlet中将会编译为out.print()语句。因此起到的作用就是简化jsp的输出语法。
  • 脚本:<% %>

    • jsp脚本的语法格式是:<% 这里是java程序 %>
      嵌套在<% %>中的java代码就是jsp中的java脚本,jsp中的java脚本将会被容器编译成service()方法中的可执行代码,因此对于jsp脚本来说,不能在其中定义方法,因为在java中不允许在方法中定义方法。

5. 四大作用域

  • application:整个web。作用于整个Web应用,多个用户之间共享。
  • session:单会话。作用于整个Web应用,单个用户之间共享。
  • request:单个请求。作用于请求,转发间请求共享。
  • page:当前页。作用于当前页面,当前页面可见。

总结一下到目前位置我们所了解的域对象:

  • request(对应的类名:HttpServletRequest)
    • 请求域(请求级别的)
  • session(对应的类名:HttpSession)
    • 会话域(用户级别的)
  • application(对应的类名:ServletContext)
    • 应用域(项目级别的,所有用户共享的。)
  • 这三个域对象的大小关系
    • request < session < application
  • 他们三个域对象都有以下三个公共的方法:
    • setAttribute(向域当中绑定数据)
    • getAttribute(从域当中获取数据)
    • removeAttribute(删除域当中的数据)
  • 使用原则:尽量使用小的域。

6. 九大内置对象

  • jakarta.servlet.jsp.PageContext pageContext 页面作用域

  • jakarta.servlet.http.HttpServletRequest request 请求作用域,一次请求可以跨多页页面

  • jakarta.servlet.http.HttpSession session 会话作用域

  • jakarta.servlet.ServletContext application 应用作用域

    • pageContext < request < session < application

      • 以上四个作用域都有:setAttribute、getAttribute、removeAttribute方法。

      • 以上作用域的使用原则:尽可能使用小的域。

  • java.lang.Throwable exception

  • jakarta.servlet.ServletConfig config

  • java.lang.Object page (其实是this,当前的servlet对象)

  • jakarta.servlet.jsp.JspWriter out (负责输出)

  • jakarta.servlet.http.HttpServletResponse response (负责响应)

jsp总共包含9个内置对象。内置对象可以不要通过声明(new),就可以直接在jsp的脚本中进行使用;而在servlet中,仍需要new,所以jsp只是将这些对象内嵌了。等编译成servlet对象后,这9个内置对象要么就是编译后生成servlet的形参,要么就是servlet中service()方法的局部变量,因此在jsp的脚本中可以直接使用。

对象名对象作用
application代表当前整个web本身,存放在application中的数据可以供所有的servlet和jsp共享,无论哪次请求,无论哪次会话都有效。
configconfig代表jsp配置信息的实例。一般来说,jsp中的该对象用的比较少,在servlet中用到config比较多,但是因为servlet中没有内置对象,因此要通过getServletConfig()方法来获取当前servlet的config实例对象。
exception当当前的jsp页面是错误处理页面时,也就是isError属性设为true时,exception对象才会被实例化。常用于输出错误信息和错误栈
out对应了servlet中的输出流对象,常常用于输出变量值和常亮。在之前我们提到过jsp的表达式输出,表达式输出的实质就是out对象的输出过程,因此在jsp中能够使用表达式输出的地方都能用到out对象来输出,但表达式输出更加的简洁。
page代表页面本身
pageContext代表页面上下文。注意页面上下文和页面本身不同。页面上下文可以用于页面之间的数据共享。通过这个对象可以获取page,request,session,application对象;还可以为参数设置的scope,从而使得参数可以在不同的scope之间共享。但一般page,request,session,application四个对象在jsp中已经内置,因此往往是在servlet中才会使用pageContext来获取我们想要的对象.
request代表当前请求。客户端的所有请求信息都被封装在这个对象当中,这也是我们最经常使用的一个对象。
response代表服务器对客户端请求的响应,一般响应分为输出流和重定向。在servlet中输出流通过response获取上面的out对象进行输出,重定向表示生成新的request向一个新的页面发出请求
session代表一次会话。当客户端浏览器与站点连接,会话建立;客户端关闭浏览器,会话结束。

6.1 out (PrintWriter)

out对象是一个缓冲的输出流,用来向客户端返回信息。由于向客户端输出信息总是要连接,所以向客户端输出总是采用缓冲的形式。当缓冲区满足如下条件是才会写入Servlet引擎提供的缓冲区:设置page指令的buffer属性关闭了out对象的缓存功能;out对象的缓冲区已满。整个JSP页面结束。

<%
   out.println("aaa");
   response.getWriter().write("bbb");
%>
"bbb会比aaa先输出"

常用方法:

void println() // 向客户端打印字符串
void clear() // 清除缓冲区的内容,如果在flush之后调用会抛出异常。
void clearBuffer() // 清除缓冲区的内容,如果在flush之后调用不会抛出异常
void flush() // 将缓冲区内容输出到客户端
int getBufferSize() // 返回缓冲区以字节数的大小,如不设缓冲区则为0
int getRemaining() // 返回缓冲区还剩余多少可用
boolean isAutoFlush() // 返回缓冲区满是,是自动清空还是抛出异常
void close() // 关闭输出流

6.3 request (ServletRequest)

request对象用于封装请求数据。一般用户在表单填写的数据会被封装成request对象,通过post方法传递给目标页面。request还相应提供了一些方法用于访问这些数据。主要方法有以下几个:

String getParameter() // 返回指定参数的参数值
String[] getParameterValues() //返回指定参数的所有值
void setAttribute() // 存储此请求中的属性
object getAttribute() // 返回指定属性的属性值
String getContentType() // 得到请求体的MIME类型
String getProtocol() // 返回请求用的协议及版本号
String getServerName() // 返回接受请求的服务器主机名
int getServerPort() // 返回服务器接受此请求的端口号
String getCharacterEncoding() // 返回字符编码方式
void setContentEncoding() // 设置请求的字符编码方式
int getContentLength() // 返回请求体的长度(以字节数)
String getRemoteAddr() // 返回发送此请求的客户端IP地址
String getRealPath(String path) // 返回一个虚拟路径的真实路径或相对路径的绝对路径
StringgetContextPath() // 返回上下文路径,即项目的根目录

中文乱码问题:request.setCharacterEncoding(“UTF-8”);URL中中文乱码问题:Tomcat的/conf/server.xml 的添加<Connector URIEncoding="UTF-8">属性。

6.3 response (ServletResponse)

response对象用于封装响应数据,其作用域是本页面。相关方法如下:

String getCharacterEncoding() // 返回响应用的是何种字符编码
void setContentType() // 设置响应的MIME类型,一般为"text/html, charset=UTF-8"
PrintWriter getWriter() // 返回可以向客户端输出字符的一个对象
						// PrintWriter与内置out对象的区别:PrintWriter对象的其输出总是提前于内置out对象,或者在out中手动flush
sendRedirect(loaction) // 重新定向客户端的请求

6.4 session (HttpSession)

会话对象,用来记录每个客户端的访问状态。常用方法如下

long getCreationTime() // 返回session创建时间
String getId() // 返回session创建时JSP引擎为它设的唯一ID号
Object setAttribute() // 使用指定名称将对象绑定到此会话
Object getAttribute() // 返回与此会话中的指定名称绑定在一起的对象,没有返回null
String[] getValueNames() // 返回一个包含此session中所有可用属性的数组
int getMaxInactiveInterval() // 返回两次强求间隔多长时间此session被取消(单位秒)
void setMaxInactiveInterval() // 设置session存活时间

session的生命周期:

  • 创建:当客户端第一次访问某个jsp或者Servlet时候,服务器会为当前会话创建一个SessionId,每次客户端向服务端发送请求时,都会将此SessionId携带过去,服务端会对此SessionId进行校验。
  • 活动: 某次会话当中通过超链接打开的新页面属于同一次会话。只要当前会话页面没有全部关闭,重新打开新的浏览器窗口访问同一项目资源时属于同一次会话。除非本次会话的所有页面都关闭后,在重新访问某个jsp或者Servlet将会创建新的会话(但此时原有会话还存在,这个就的SessionId仍然存在于服务端,只不过再也没有客户端会携带它然后教育服务端校验,直到该会话超时。
  • 销毁:有三种销毁方式
    • 调用了session.invalidate()方法
    • Session过期(超时)可以在web.xml中配置Session超时时间1单位是分钟
    • 服务器重新启动

6.5 application (ServltContext)

application对象用于获取和设置servlet相关信息,application的生命周期是从服务器启动直到服务器关闭

  • application对象实现了用户间数据的共享,可存放全局变量。
  • application开始于服务器的启动,终止于服务器的关闭。
  • 在用户的前后连接或不同用户之间的连接中,可以对application对象的同一属性进行操作。
  • 在任何地方对application对象属性的操作,都将影响到其他用户对此的访问。
  • 服务器的启动和关闭决定了application对象的生命。
  • application对象是ServletContext类的实例。
void setAttribute() // 使用指定名称将对象绑定到此会话
Object getAttribute() // 返回与此会话中的指定名称绑定在一起的对象,如果没有,返回null
Enumeration getAttributeNames() // 返回所有可用属性名的枚举
String getServerInfo() // 返回JSP(Servlet)引擎名及版本号

6.6 page (Object)

page对象就是指向当前jsp页面本身,有点像类中的this指针,它是java.lang.Object类的实例。常用方法就是Object类的成员方法。

class getClass() // 返回当前的类名。
int getHashCode() // 返回当前类的哈希码。
String toString() // 将此对象转换成字符串。
boolean equals(object) // 比较两个对象是否是相等。
void copy(object) // 将该对象复制到指定的object对象中。

6.7 pageContext (PageContext)

pageContext对象是JSP技术中最重要的一个对象,它代表JSP页面的运行环境,这个对象不仅封装了对其他8个隐式对象的引用,其自身还是一个域对象,可以用来保存数据。并且,这个对象还封装了web开发中经常涉及到的一些常用操作,例如include,forward其他资源、检索其他域对象中的属性等。

pageContext对象提供了对jsp页面内所有的对象及名字空间的访问。该对象可以访问到本页所在的Session,也可以取本页所在的application中的属性值。该对象相当也页面中所有功能的集大成者。该对象的本类名也叫pageContext

JspWriter getOut() // 返回当前客户端响应被使用的out对象
HttpSession getSession() // 返回当前页中的Session对象
Object getPage() // 返回当前页的page对象
ServletRequest getRequest() // 返回当前页的Request对象
ServletResponse getResponse() // 返回当前页的Response对象
void setAttribute() // 设置属性及属性值
Object getAttribute() // 在指定范围内取属性的值
int getAttributeScope() // 返回某属性的作用范围
void forward() // 跳转到另一个页面,地址栏不变
void include() // 包含另一个页面,

PageContext.APPLICATION_SCOPE 代表各个域的常量
PageContext.SESSION_SCOPE
PageContext.REQUEST_SCOPE
PageContext.PAGE_SCOPE
findAttribute() // 查找各个域中的属性

6.8 Config (ServletConfig)

Config对象是在一个Servlet初始化时,JSP引擎向它传递信息用的,此信息包括Servlet初始化时所要用到的参数(通过属性名和属性值构成)以及服务器的有关信息(通过传递一个ServletContext对象)

ServletContext getServletContext() // 返回含有服务器信息的ServletContext对象
String getInitParameter() // 返回初始化参数的值
Enumeration getInitparameterNames() // 返回Servlet初始化所需所有参数的枚举

6.9 Exception (Throwable)

Exception对象是一个异常对象,当一个页面在运行过程中发生了异常,就产生这个对象。如果一个jsp页面要应用此对象,就必须报isErrorPage设为true,否则无法编译。

它实际上是java.lang.Throwable的对象。页面要想在有异常时得到处理,就要指定异常处理页面<% page errorPage=“exception.jsp”%>

String getMessage() // 返回描述异常的消息
String toString() // 返回关于异常的简短描述消息
void printStackTrace() // 显示异常及其栈轨迹
ThrowableFillInStackTrace() // 重写异常的执行栈轨迹

6.10 Cookie

除此之外,在web开发过程中,还有一个重要的概念是cookie。

  • 因为当客户端断开与服务器的连接时,会话就会结束,当前会话的所有信息就被丢失。如果我们想保存某些信息,可以利用cookie将相关信息保存到我们的机器上,直到cookie失效。
  • 往客户端机器添加cookie也是response内置对象的责任。可以通过response.addCookie()的方法来添加cookie。cookie的使用很简单,利用即将存储的信息实例化一个cookie对象,
  • 一定记得设置cookie的失效时间,否则cookie会默认会话结束,cookie失效,最后由response对象添加即可。
  • 那么如何获得当前客户端机器的cookie呢,这就是request对象的任务了,通过getCookies()对象就可以获取当前客户端机器的所有cookie了。

7. 三大编译指令

jsp的编译指令不直接生成输出,这些编译指令会通知jsp引擎或者servlet引擎对当前的jsp文件在进行编译的过程中起作用。

  • 常用的编译指令有:page, include和taglib
  • 在jsp中插入编译指令的语法是:<%@ 编译指令名 属性名1=“属性值1” 属性名2=“属性值2” …%> 注意,各属性定义之间不要用符号分隔开

7.1 包含指定页:<%@include ...%>

include 用于指定当前页面包含另一个页面,通常是静态包含:

  • include指令可以将一个外部文件静态导入到当前的jsp文件当中,使得当前jsp文件在生成servlet后会嵌入外部文件所生成的代码
  • include指令可以包含一个静态的文本,也可以导入动态的jsp文件。注意,这里说的静态导入和动态jsp文件不是指的一回事,导入动态jsp文件并不意味着动态导入,而依然是静态导入。指令格式如下:
<%@ include file=”xxx.txt”%>
<%@ include file=”xxx.jsp”%>

jsp的include静态导入对象如果是一个jsp文件的话,静态导入的结果还会把目标jsp文件的编译指令导入进来,因此如果两个jsp文件的编译指令发生冲突,那么在容器生成servlet的过程中就会发生错误。

7.2 编译当前页:<%@ page ...%>

page 的编译指令是针对当前页面,常用的属性有:

属性名作用
language声明当前页面脚本的语言,默认为java
extends指定jsp编译成Servlet之后所需要继承的类,或者所实现的接口
import常用的,用于导入当前脚本中可能使用到的其他包里面的类
info包含jsp的信息,一般作为当前jsp文件的说明用。可以通过getServletInfo()来获取
errorPage常用。指定当前jsp文件发生错误时,自动调用改属性值指定的jsp文件。如果不指定当前属性值,当发生错误时,会抛出异常信息给客户。
contentType指定生成网页的文件格式和编码字符集
isErrorPage用于指定当前jsp文件是否为错误处理jsp文件

一个常见的jsp编译指令的使用可能是:

<%--指定脚本语言是java,指定生成网页的文件格式和编码字符集--%>
<%@ page language="java" contentType="text/html; charset=GBK" %>

<%--指定导入的包的位置--%>
<%@ page import="java.package.*" %>

<%--指定当前jsp文件发生错误时,处理错误的jsp文件--%>
<%@ page errorPage="error.jsp" %>

<%--说明当前jsp文件不是用来处理错的jsp文件--%>
<%@ page isErrorPage="false"%>

7.3 导入标签库: <%@ taglib ...%>

taglib 的编译指令作用由名字也可以知道,就是导入一个标签库,指定标签库的uri以及在当前jsp中使用该标签库中标签的prefix(前缀),就可以完成标签库的导入了。语法如下:

<%@ taglib uri=”xxx/yyy/zzz” prefix=”myTag” %>
<%-使用导入标签库的方式-%>
<myTag:tagName/>

8. 三大动作元素

jsp的动作指令有区别于编译指令,编译指令是jsp在通知容器进行编译的过程中起作用的,而动作指令则是在编译之后运行过程中起作用的。常见的jsp的动作指令有:

8.1 跳转:<jsp:forward ...>

forward动作指令是将当前页面的响应转到一个新的页面,这个新的页面会继续相应。注意:forward指令并不会生成一次新的请求request,这也就是说请求参数不会丢失,在新的页面中还是能够通过request.getParameter()方法获取参数值。除此之外,在转向新的页面之前,还可以通过jsp:param指令向request中添加额外参数。其完整使用语法如下:

<jsp:forward page="URL|<%=expression%>">  
	<jsp:param name="" value="" />  
</jsp:forward>

转到的目标page不仅可以是相对路径下的合法url,还可以是jsp的表达式输出。

8.2 导入: <jsp:include ...>

include动作指令和编译指令中的include指令作用相似,都是用于导入某个jsp文件。但include动作指令是一个动态导入指令,不同于静态include导入指令,区别在于:

  • 动态导入指令只会导入被导入文件的主体body部分,其余部分不会导入,也就是说编译指令不会被导入,而静态在导入也会一并把编译指令导入;
  • 动态导入指令并不是真正将被导入文件内容插入到自身,而是调用编译后的servlet中的include方法将被导入文件的主体body部分导入进来,而静态指令是两者合二为一生成一个servlet;
  • 动态导入还可以利用jsp的param动作指令为被导入文件添加而外的参数;

jsp的include指令的语法如下:

<jsp:include page="{url|<%=expression%>}">
    <jsp:param name="" value="" />
</jsp:include>

8.3 传递参数:<jsp:param ...>

当使用<jsp:include><jsp:forward>标签引入或将请求转发给其它资源时,可以使用<jsp:param>标签向这个资源传递参数。

  • <jsp:param>标签的name属性用于指定参数名,value属性用于指定参数值。
  • <jsp:include><jsp:forward>标签中可以使用多个<jsp:param>标签来传递多个参数。
<jsp:include page="relativeURL | <%=expression%>">
	<jsp:param name="parameterName" value="parameterValue|<%= expression %>" />
</jsp:include>
<jsp:include page="relativeURL | <%=expression%>">
	<jsp:param name="parameterName" value="parameterValue|<%= expression %>" />
</jsp:include>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1678329.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录Day 47|Leetcode|Python|392.判断子序列 ● 115.不同的子序列

392.判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的…

Lobe Chat–在线AI对话聊天机器人,一键部署,免费开源

现代化设计的开源 ChatGPT/LLMs 聊天应用与开发框架 支持语音合成、多模态、可扩展的&#xff08;function call&#xff09;插件系统 一键免费拥有你自己的 ChatGPT/Gemini/Claude/Ollama 应用 项目演示 支持多种模型接口 支持语音输入输出 支持云端同步 丰富多彩非常实用的应…

第二证券今日投资参考:美宣布对华电动汽车等加征关税 AI应用或加速落地

昨日&#xff0c;两市股指早盘一度拉升&#xff0c;随后震动回落&#xff0c;盘中保持窄幅震动收拾走势。截至收盘&#xff0c;沪指微跌0.07%报3145.77点&#xff0c;深证成指跌0.05%报9668.73点&#xff0c;创业板指跌0.26%报1855.6点&#xff0c;北证50指数涨0.74%&#xff1…

istio资源字段参考文档

virtual service&#xff1a; Istio / Virtual ServiceConfiguration affecting label/content routing, sni routing, etc.https://istio.io/latest/docs/reference/config/networking/virtual-service/

opencv4.8.0 GPU版本各平台编译

一、opencv4.8.0 ubuntu22.04上编译&#xff1a; 用cmake进行编译,需要配置三次。选中world选项&#xff0c;输入opencv_contrib_module路径。 ubuntu22.04上编译&#xff1a; cmake \ -D CMAKE_BUILD_TYPERELEASE \ -D CMAKE_INSTALL_PREFIX/usr/local \ -D BUILD_opencv_p…

1:硬件测试面试

1&#xff1a;板级测试 . JTAG和Boundary Scan 对于硬件板级测试&#xff0c;我使⽤JTAG和Boundary Scan技术进⾏⾃动化测试。这些技术可以帮助我访问PCB 上的芯⽚引脚&#xff0c;从⽽进⾏信号测量、连通性测试和故障诊断。 2&#xff1a;整机测试 3&#xff1a;测试准备 4…

(Java面试题——基础版)JVM、JRE和JDK的关系

JVM Java Virtual Machine是Java虚拟机 &#xff0c;Java程序需要运行在虚拟机上 &#xff0c;不同的平台有自己的虚拟机 &#xff0c;因此Java语言可以 实现跨平台。JVM 负责将 Java 字节码&#xff08;即编译后的 .class 文件&#xff09;翻译成特定平台上的机器码&#xff0…

Blender雕刻建模_笔刷

1.雕刻模式 雕刻Scuplt&#xff0c;一种常用的建模方式 -选中物体&#xff0c;进入雕刻模式 -重构网格&#xff08;修改体素大小&#xff0c;点击重构网格&#xff09;给物体添加更多面 -选择笔刷&#xff0c;雕刻 -退出雕刻模式 2.重构网格 一种按体积的细分方式&#xf…

uniapp地图电子围栏(多边形)绘制和编辑

uniapp地图电子围栏&#xff08;多边形&#xff09;绘制和编辑 背景实现思路代码实现注意事项尾巴 背景 最近项目中需要在地图上进行电子围栏的绘制和编辑&#xff0c;这里将实现的思路给大家分享下。由于uniapp官方提供的map组件功能不全&#xff0c;还有在APP端&#xff08;…

echarts去掉网格线

柱子后面白色的线太丑了&#xff01; 去掉他 x轴平行的线&#xff0c;就写在yAxis下面 yAxis: {type: value,splitLine: {lineStyle: {type: dashed, // 虚线样式color: rgba(255, 255, 255, 0.15) // 虚线颜色}},//去除网格线}, 这个颜色一定要加‘’&#xff0c;不然不生效…

使用python开发的闭运算调试器

使用python开发的开运算调试器 简介效果代码 简介 用来调试闭运算效果的小工具&#xff0c;滑动条可以控制滤波核的大小&#xff0c;用来查看不同滤波核下的闭运算效果。 效果 代码 import sys from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayou…

2024新零售行业多元化用工报告

来源&#xff1a;君润人力 近期历史回顾&#xff1a;

国外客户怀疑我们产品质量要如何应对

经常有外贸小伙伴问我&#xff0c;国外客户怀疑我们的产品质量要如何应对&#xff1f; 这个问题应该算是外贸经常遇到的一个问题&#xff0c;今天就简单来给大家分享几个我认为可以去入手跟客户回复解决的这个问题的点。 首先&#xff0c;我们要知道&#xff0c;不管你做啥产品…

Stable Diffusion入门使用技巧及个人试用实例分享--生成稳定人物及姿势篇

上节我们主要讲解了SD提示词的实践篇及ControlNet常用模型篇&#xff0c;本节主要想给大家分享一下如何在不自己单独训练lora的情况下尽量稳定的控制生成的人物的脸及姿势。欢迎阅读。 一、如何稳定生成相同的人物&#xff08;脸部&#xff09; 1、瞎编名字法&#xff1a; d…

第八篇 Asciidoc 输出 All In One HTML 解决图片无法显示问题

问题:我的图片显示不出来了 小明使用 Asciidoc 来记笔记,他将笔记输出为 HTML 文件。小丽向小明借笔记。小明将 Asciidoc 笔记输出为 HTML文件,并拷贝给了小丽。 但是,小丽发现,图片都显示不出来了。 小丽:小明,你给我的笔记,图片都显示不出来啊。 小明:是我给你的…

H2-FDetector模型解析

文章目录 1. H2FDetector_layer 类2. RelationAware 类3. MultiRelationH2FDetectorLayer 类4. H2FDetector 类 这个实现包括三个主要部分&#xff1a;H2FDetector_layer、MultiRelationH2FDetectorLayer 和 H2FDetector。每个部分都有其独特的功能和职责。下面是这些组件的详细…

thinkphp8扩展think-swoole4.0-事件监听代码

首先服务端配置监听 swoole.php <?phpreturn [http > [enable > true,host > 0.0.0.0,port > 8000,worker_num > swoole_cpu_num(),options > [],],websocket > [enable > true,handler > \think\swo…

【达梦数据库】搭建 DM->mysql dblink

DM->mysql dblink 1安装mysql odbc rpm -ivh mysql-connector-odbc-5.3.14-1.el7.x86_64.rpm2mysql创建远程用户与远程数据库 mysql> show databases; ------------------------- | Database | ------------------------- | information_schema | …

行测练习题

、、 【任意直角三角形&#xff0c;斜边的中点到三个顶点的距离相等。】 因此无人机的投影点一定为直角三角形斜边中点&#xff0c;之后根据勾股定理可以求得高度为500. 、、

桌椅3D模型素材从哪下载比较好?

对于室内设计师而言&#xff0c;经常需要用到桌椅3D模型来完成自己的设计方案&#xff0c;那么从哪里能下载高质量的桌椅3D模型素材呢? 1、建e网&#xff1a;建e网的3D模型库不仅数量庞大&#xff0c;而且质量上乘。模型制作精细&#xff0c;纹理清晰&#xff0c;可以直接用于…