预备知识

C/S架构

Client / Server(客户端 / 服务器)

C/S架构的特点:需要安装特定的客户端软件

B/S架构

Browser / Server(浏览器 / 服务器)

B/S结构的系统,其实就是开发网站,开发一个WEB系统

Tomcat服务器

安装

官网tomcat.apache.org下载解压

配置环境变量CATALINA_HOMEPath

启动和关闭

startup		//启动服务器
shutdown.bat	//关闭服务器(建议改为stop)

Servlet

Servlet规范

  • 规范了哪些接口
  • 规范了哪些类
  • 规范了一个web应用中应该有哪些配置文件
  • 规范了一个web应用中配置文件的名字
  • 规范了一个web应用中配置文件存放的路径
  • 规范了一个web应用中配置文件的内容
  • 规范了一个合法有效的web应用它的目录结构应该是怎么样的。

开发一个带有Servlet的Web App

  1. webapps目录下新建一个目录,起名crm(webapp的名字)

  2. webapp的根下新建一个目录:WEB-INF,必须一模一样,下同

  3. WEB-INF目录下新建一个目录:classes,存放的是java程序编译之后的class文件(字节码文件)。

  4. WEB-INF目录下新建一个目录:lib,用来放第三方jar包(如果没有可以不创建)

  5. WEB-INF目录下新建一个文件web.xml,配置文件中描述了请求路径和Servlet类之间的对照关系,最好拷贝

  6. 编写一个java程序,实现Servlet接口

  7. 编上一步的java程序HelloServlet.java,这里必须配置环境变量CLASSPATH=.;C:\Tomcat\apache-tomcat-10.0.27\lib\servlet-api.jar

  8. 将编译之后的HelloServlet.class文件拷贝到WEB-INF\classes目录下

  9. web.xml文件中编写配置信息,让“请求路径”和”Servlet类名”关联在一起(在web.xml文件中注册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_5_0.xsd"
      version="5.0"
      metadata-complete="true">
        <!--servlet描述信息-->
        <!--任何一个servlet都对应一个servlet-mapping-->
        <servlet>
          <servlet-name>helloworld</servlet-name>
          <!--这个位置必须带有包名的全限定类名-->
          <servlet-class>top.zhengru.servlet.HelloServlet</servlet-class>
        </servlet>
        <!--servlet映射信息-->
        <servlet-mapping>
        <!--写的内容要和上面的一样-->
          <servlet-name>helloworld</servlet-name>
          <!--这里需要一个以 / 开始的路径-->
          <url-pattern>/aaa/bbb</url-pattern>
        </servlet-mapping>
    </web-app>
  10. 启动Tomcat服务器,打开浏览器,在浏览器地址栏上输入与url.pattern一致的请求路径(加项目名)

  11. Tomcat服务器负责调用main方法,Tomcat服务器启动的时候执行的就是main方法。我们只需要编写Servlet接口的实现类,然后将其注册到web.xml文件中即可

踩坑记录(?)

这里第八步的时候注意是把整个包复制到classes目录下而不是单单只复制一个文件,在这里卡500找了一个早上,换了好几次jdk和tom的版本都没解决。。。。。。。。(如图所示复制)

image-20230118142904136

浏览器发送请求到最终服务器调用Servlet中的方法的过程

  • 用户输入URL,或者直接点击超链接:127.0.0.1:8080/crm/aaa/bbb
  • 然后Tomcat服务器接收到请求,截取路径:/aaa/bbb
  • Tomcat服务器在web.xml文件中查找/aaa/bbb对应的Servlet是:top.zhengru.servlet.HelloServlet
  • Tomcat服务器通过反射机制,创建top.zhengru.servlet.HelloServlet的对象
  • Tomcat服务器调用top.zhengru.servlet.HelloServlet对象的Service方法

解决Tomcat服务器在DOS命令窗口中乱码的问题

\Tomcat\apache-tomcat-10.0.27\conf打开logging.properties文件

找到java.util.logging.ConsoleHandler.encoding = UTF-8这一行,将UTF-8改成GBK

java.util.logging.ConsoleHandler.encoding = GBK

向浏览器相应一段HTML代码

public void service(ServletRequest request, ServletResponse response){
    // 向控制台输出hello, servlet
    System.out.println("hello, servlet");

    // 设置响应体的内容类型:普通文本或html
    response.setContentType("text/html;charset=utf-8");

    //将信息输出到浏览器上需要使用ServletResponse接口:response
    //response表示从服务器向浏览器发送数据叫做响应
    PrintWriter out = response.getWriter();
    out.print("hello servlet, you are my first servlet!!!");
    
    // 向浏览器中响应一段html代码
    out.print("<h1>hello world!</h1><br><h1>这是我第一个webapp!</h1>");
}

Servlet编写JDBC连接数据库

编写程序StudentServlet.java

package top.zhengru.servlet;

import jakarta.servlet.Servlet;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.ServletException;

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 {
        	//JDBC
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/db1";
            String user = "账号";
            String password = "密码";
            conn = DriverManager.getConnection(url, user, password);
            //创建预编译数据库对象
            String sql = "select * from emp";
            ps = conn.prepareStatement(sql);
            //执行sql
            rs = ps.executeQuery();
            //处理查询结果集
            while (rs.next()) {
                String id = rs.getString("id");
                String name = rs.getString("ename");
                out.print("id:" + id + " name:" + name +"<br>");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally{
        	//释放资源
            if (rs!=null){
                try{
                    rs.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            if (ps!=null){
                try{
                    ps.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            if (conn!=null){
                try{
                    conn.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
    public void destroy(){

    }
    public String getServletInfo(){
        return "";
    }
    public ServletConfig getServletConfig(){
        return null;
    }
}

导入mysql驱动jar包

将驱动放在lib目录下

image-20230118171210341

启动Tomcat

运行结果如图:

image-20230118171301122

使用IDEA开发Servlet

新建一个空项目

new一个empty project

新建一个模块

找到File-New-Module添加一个java se的模块

image-20230118204105706

给模块添加框架支持

右键项目文件选择Add Framwork Support,勾选Web Applicantion

image-20230118204242434

添加Servlet所需的jar包

找到File-Project Structure,给模块添加依赖

image-20230118205341495

这里添加了两个jar包

image-20230118205442170

配置Tomcat服务器

进入Edit Configurations,添加local Tomcat,选择Tomcat的根目录

image-20230118205544080

开始编写Java程序

如果有需要可以手动在WEB-INF目录下创建lib文件夹

image-20230118205826574

运行

Servlet对象的生命周期

Servlet对象更像一个人的一生

  • Servlet的无参数构造方法执行:标志着出生
  • Servlet对象的init方法的执行:标志着正在接受教育
  • Servlet对象的service方法的执行:标志着开始工作
  • Servlet对象的destroy方法的执行:标志着临终

让服务器启动的时候创建Servlet对象

在servlet标签中添加子标签,在该子标签中填写整数,越小的整数优先级越高

<load-on-startup>1</load-on-startup>

Servlet对象谁来维护

  • Servlet对象的创建,对象上方法的调用,对象最终的销毁,Javaweb程序员无权干预
  • Servlet对象的生命周期是由Tomcat服务器全权负责的
  • Tomcat服务器通常我们又称为:WEB容器(WEB Container
  • WEB容器来管理Servlet对象的死活
  • 自己newServlet对象不受WEB容器管理
  • WEB容器创建的Servlet对象,这些Servlet对象都会被放到一个集合当中(HashMap),能够被WEB容器管理
  • 容器底层应该有一个HashMap这样的集合,在这个集合当中存储了Servlet对象和请求路径之间的关系

结论

  • 默认情况下,服务器在启动的时候Servlet对象并不会被实例化
  • 用户在发送第一次请求的时候Servlet对象被实例化
  • 对象被创建出来之后,先对象的init方法(仅仅调用一次),然后调用service方法
  • Servlet对象是单例的,Servlet对象并没有新建,多次请求还是使用之前创建好的Servlet对象,直接调用该对象的service方法(Servlet类并不符合单例模式,属于假单例,真单例构造器是私有化的)
  • destroy方法在服务器关闭的时候被调用(仅仅调用一次),在销毁AServlet对象的内存之前,自动调用destroy方法
  • 当我们Servlet类中编写一个有参数的构造方法,如果没有手动编写无参数构造方法会出现500错误,无法实例化Servlet对象(500错误一般情况下是因为服务器端的Java程序出现了异常)

适配器设计模式Adapter

编写GenericServlet类(适配器)

编写一个GenericServlet类,实现Servlet接口,GenericServlet是一个抽象类,其中有一个抽象方法service,我们创建的类去继承GenericServlet抽象类,并完成service方法的编写

public class LoginServlet extends GenericServlet{
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("正在处理用户请求...");
    }
}

优化GenericServlet类

主要优化了init中的config

public abstract class GenericServlet implements Servlet {

    private ServletConfig config;	//让service方法也能够调用config

    @Override
    public final void init(ServletConfig servletConfig) throws ServletException {
        this.config = servletConfig;
        this.init();	//调用新写的init
    }		// 加入final修饰,防止子类重写破坏config

    public void init(){}	//写一个新的init给子类重写 

    @Override
    public ServletConfig getServletConfig() {
        return config;	////让service方法也能够调用config
    }

    @Override
    public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException;	// 将service方法设置为抽象,子类就可以只完成service方法

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

    @Override
    public void destroy() {

    }
}

自带的GenericServlet类

事实上GenericServlet类已经存在不需要我们编写

image-20230119112227511

ServletConfig接口

ServletConfig是什么?

  • ServletConfig是Servlet规范中的一员,是一个接口,WEB服务器实现了这个接口
  • ServletConfig对象中封装了web.xml文件中servlet的配置信息,一个Servlet对应一个ServletConfig对象
  • Servlet对象和ServletConfig对象都是Tomcat服务器创建的,默认情况下,都是在用户发送第一次请求的时候创建

配置优化

可以选择更改一些配置,这样可以避免每次更新代码都需要重启服务器

image-20230119191923624

获取web.xml中的初始化参数信息

getInitParameterNames

获取所有初始化参数的name

getInitParameter

<servlet>
    <servlet-name>configTest</servlet-name>
    <servlet-class>top.zhengru.javaweb.servlet.ConfigTestServlet</servlet-class>
    <init-param>
        <param-name>driver</param-name>
        <param-value>com.mysql.jdbc.Driver</param-value>
    </init-param>
    <init-param>
        <param-name>url</param-name>
        <param-value>jdbc:mysql://localhost:3306/db1</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>密码</param-value>
    </init-param>
</servlet>
Enumeration<String> initParameterNames = config.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
    String parameterName = initParameterNames.nextElement();
    out.print(parameterName);
    out.print(" : ");
    String parameter = config.getInitParameter(parameterName);
    out.print(parameter);
    out.print("<br>");
}

image-20230119195227635

ServletContext接口

ServletContext是什么?

ServletContext是接口,servlet规范中的一员,WEB服务器实现了这个接口

Tomcat服务器启动webapp的时候创建ServletContext对象

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

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

一个ServletContext对象通常对应的是一个web.xml文件

ServletContext对象的生命周期

ServletContext对象在服务器启动阶段创建,在服务器关闭的时候销毁,ServletContext对象是应用级对象

该接口中常用的方法

getInitParameter方法

一般一个项目中共享的配置信息会放到context-param标签当中

public String getInitParameter(String name);	// 通过初始化参数的name获取value
public Enumeration<String> getInitParameterNames();		// 获取所有的初始化参数的name

Enumeration<String> initParameterNames = application.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
    String name = initParameterNames.nextElement();
    String value = application.getInitParameter(name);
    out.print(name+"="+value);
    out.print("<br>");
}
<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>

getContextPath方法

// 获取应用的根路径(非常重要),因为在java源代码当中有一些地方可能会需要应用的根路径,这个方法可以动态获取应用的根路径
public String getContextPath();

String contextPath = application.getContextPath();

getRealPath方法

// 获取文件的绝对路径(真实路径)
public String getRealPath(String path);

//在这个括号里面的path中,填写的路径可以不加“/”
String realPath = application.getRealPath("/index.html");

log方法

// 通过ServletContext对象也是可以记录日志的
public void log(String message);
public void log(String message, Throwable t);

application.log("用log方法来记日志");

ServletContext应用域

注意事项

  • 如果所有的用户共享一份数据,并且这个数据很少的被修改,并且这个数据量很少,可以将这些数据放到ServletContext这个应用域中,数据量比较大的话,太占用堆内存,并且这个对象的生命周期比较长,服务器关闭的时候,这个对象才会被销毁。大数据量会影响服务器的性能,占用内存较小的数据量可以考虑放进去
  • 所有用户共享的数据,如果涉及到修改操作,必然会存在线程并发所带来的安全问题。所以放在ServletContext对象中的数据一般都是只读的
  • 数据量小、所有用户共享、又不修改,这样的数据放到ServletContext这个应用域当中,会大大提升效率。因为应用域相当于一个缓存,放到缓存中的数据,下次在用的时候,不需要从数据库中再次获取,大大提升执行效率

应用域的存、取、删(操作域)

// 存
public void setAttribute(String name, Object value);
// 取
public Object getAttribute(String name);
// 删
public void removeAttribute(String name);

HttpServlet类

B/S结构的系统是基于HTTP超文本传输协议的,在Servlet规范当中,提供了一个类叫做HttpServlet,它是专门为HTTP协议准备的一个Servlet类。我们编写的Servlet类要继承HttpServlet。(HttpServletHTTP协议专用的)使用HttpServlet处理HTTP协议更便捷

jakarta.servlet.Servlet//(接口)
jakarta.servlet.GenericServlet implements Servlet//(抽象类)
jakarta.servlet.http.HttpServlet extends GenericServlet//(抽象类)

目前为止接触过的缓存机制

  • 堆内存当中的字符串常量池

  • 堆内存当中的整数型常量池

  • 连接池

    • 这里所说的连接池中的连接是java语言连接数据库的连接对象:java.sql.Connection对象。

    • JVM是一个进程。MySQL数据库是一个进程。进程和进程之间建立连接,打开通道是很费劲的。是很耗费资源的。可以提前先创建好N个Connection连接对象,将连接对象放到一个集合当中,我们把这个放有Connection对象的集合称为连接池。每一次用户连接的时候不需要再新建连接对象,省去了新建的环节,直接从连接池中获取连接对象,大大提升访问效率

    • 连接池

      • 最小连接数

      • 最大连接数

      • 连接池可以提高用户的访问效率。当然也可以保证数据库的安全性

  • 线程池

    • Tomcat服务器本身就是支持多线程的

    • Tomcat服务器启动的时候,会先创建好N多个线程Thread对象,然后将线程对象放到集合当中,称为线程池。用户发送请求过来之后需要有一个对应的线程来处理这个请求,这个时候线程对象就会直接从线程池中拿,效率比较高

    • 所有的WEB服务器,或者应用服务器,都是支持多线程的,都有线程池机制

  • redis

    • NoSQL数据库,非关系型数据库,缓存数据库
  • ServletContext应用域中存储数据,也等于是将数据存放到缓存cache当中

HTTP协议

什么是HTTP协议?

  • W3C制定的一种超文本传输协议

  • 游走在B和S之间。B向S发数据要遵循HTTP协议。S向B发数据同样需要遵循HTTP协议。这样B和S才能解耦合

  • 什么是解耦合?

    • B不依赖S
    • S也不依赖B
  • 浏览器向WEB服务器发送数据,叫做:请求(request)

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

  • HTTP协议包括请求协议、响应协议

  • HTTP协议就是提前制定好的一种消息模板

HTTP的请求协议(B–>S)

请求协议包括什么?

  • 请求行

    • 第一部分:请求方式(7种)

      • get(常用)

      • post(常用)

      • delete

      • put

      • head

      • options

      • trace

    • 第二部分:URI

      • 代表网络中某个资源的名字。但是通过URI是无法定位资源的。

      • URL包括URI

      • http://localhost:8080/servlet05/index.html 这是URL

      • /servlet05/index.html 这是URI

    • 第三部分:HTTP协议版本号

  • 请求头

    • 请求的主机

    • 主机的端口

    • 浏览器信息

    • 平台信息

    • cookie等信息

  • 空白行

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

    • 向服务器发送的具体数据

具体报文

GET请求

GET /servlet05/getServlet?username=jack&userpwd=123 HTTP/1.1	//请求行
//请求头(本身没有这个换行)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Connection: keep-alive
Cookie: Idea-75fd4672=73a4fda9-20c5-4f1a-9192-a68c6d64678a
Host: localhost:8080
Referer: http://localhost:8080/servlet05/index.html
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.55
sec-ch-ua: "Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
	//空白行
username=jack&userpwd=123		//请求体

POST请求

POST /servlet05/postServlet HTTP/1.1		//请求行
//请求头(本身没有这个换行)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cache-Control: max-age=0
Connection: keep-alive
Content-Length: 25
Content-Type: application/x-www-form-urlencoded
Cookie: Idea-75fd4672=73a4fda9-20c5-4f1a-9192-a68c6d64678a
Host: localhost:8080
Origin: http://localhost:8080
Referer: http://localhost:8080/servlet05/index.html
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.55
sec-ch-ua: "Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
	//空白行
username=lucy&userpwd=123		//请求体

怎么发送GET和POST请求?

  • 到目前为止,只有一种情况可以发送POST请求:使用form表单,并且form标签中的method属性值为:method=“post”

  • 其他所有情况一律都是get请求:

    • 在浏览器地址栏上直接输入URL,回车,属于get请求。

    • 在浏览器上直接点击超链接,属于get请求。

    • 使用form表单提交数据时,没写method,默认就是get

    • 或者使用form的时候,form标签中method属性值为:method=“get”

GET请求和POST请求的区别

  • get请求发送数据的时候,数据会挂在URI的后面(请求行)
  • post请求发送数据的时候,在请求体当中发送(请求体)
  • get请求只能发送普通的字符串,且无法发送大数据量
  • post请求可以发送任何类型的数据,且可以发送大数据量
  • get请求比较适合从服务器端获取数据
  • post请求比较适合向服务器端传送数据
  • get请求是安全的,因为get请求只是为了从服务器上获取数据。不会对服务器造成威胁
  • post请求是危险的,因为post请求是向服务器提交数据,数据有可能通过后门进入到服务器
  • post是为了提交数据,一般情况下拦截请求的时候,大部分会选择拦截(监听)post请求
  • get请求支持缓存,任何一个get请求最终的“响应结果”都会被浏览器缓存起来
  • 只要发送get请求,浏览器先从本地缓存中找,找不到才会去服务器上获取
  • 在路径的后面添加一个时间戳,可以避免get走缓存
  • post请求不支持缓存(用来修改服务器端的资源的)
  • post请求之后,服务器“响应的结果”不会被浏览器缓存起来

GET请求和POST请求怎么选择?

  • get请求比较适合从服务器端获取数据
  • post请求比较适合向服务器端传送数据
  • 大部分的form表单提交,都是post方式
  • 敏感信息用post请求
  • 文件上传,一定是post请求
  • get请求和post请求发送的请求数据格式是统一的,只是位置不同

HTTP的响应协议(S–>B)

响应协议包括什么?

  • 状态行

    • 第一部分:协议版本号(HTTP/1.1)

    • 第二部分:状态码(HTTP协议中规定的响应状态号。不同的响应结果对应不同的号码)

      • 200 表示请求响应成功,正常结束

      • 404表示访问的资源不存在

      • 405表示前端发送的请求方式与后端请求的处理方式不一致时发生:

        • 比如:前端是POST请求,后端的处理方式按照get方式进行处理时,发生405

        • 比如:前端是GET请求,后端的处理方式按照post方式进行处理时,发生405

      • 500表示服务器端的程序出现了异常。一般会认为是服务器端的错误导致的

      • 4开始的,一般是浏览器端的错误导致的

      • 5开始的,一般是服务器端的错误导致的

    • 第三部分:状态的描述信息

      • ok 表示正常成功结束

      • not found 表示资源找不到

  • 响应头

    • 响应的内容类型

    • 响应的内容长度

    • 响应的时间

  • 空白行

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

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

具体报文

HTTP/1.1 200 ok		//状态行
Content-Type: text/html;charset=UTF-8	//响应头
Content-Length: 160
Date: Fri, 20 Jan 2023 13:29:18 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>

模板方法设计模式

设计模式的种类

  • GoF设计模式(23种设计模式)
    • 单例模式
    • 工厂模式
    • 代理模式
    • 门面模式
    • 责任链设计模式
    • 观察者模式
    • 模板方法设计模式
  • JavaEE设计模式
    • DAO
    • DTO
    • VO
    • PO
    • pojo

什么是设计模式?

  • 在模板类的模板方法当中定义核心算法骨架,具体的实现步骤可以延迟到子类当中完成
  • 模板类通常是一个抽象类,模板类当中的模板方法定义核心算法,这个方法通常是final的(但也可以不是final的)
  • 模板类当中的抽象方法就是不确定实现的方法,这个不确定怎么实现的事儿交给子类去做
  • 方法添加final关键字后,这个方法无法被覆盖,这样核心的算法也能够得到保护。通常模板类中定义核心算法的方法基本上都需要添加final关键字

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方法。
        //METHOD_GET这个就相当于“GET”,主要是用equals来进行判断
        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);
    }
    
}

405错误

  • 405表示前端的错误,发送的请求方式不对(和服务器不一致)

  • 假设前端发送的请求是get请求,后端程序员重写的方法是doPost

  • 假设前端发送的请求是post请求,后端程序员重写的方法是doGet

  • 如何避免?

    • 后端重写了doGet方法,前端一定要发get请求
    • 后端重写了doPost方法,前端一定要发post请求

Servlet类的最终开发步骤

  • 编写一个Servlet类,直接继承HttpServlet
  • 重写doGet方法或者重写doPost方法
  • Servlet类配置到web.xml文件当中
  • 准备前端的页面(form表单),form表单中(action中指定)指定请求路径即可

WEB欢迎页

WEB欢迎页即访问http://localhost:8080/servlet07 默认访问设置的欢迎页面

添加方法

  • web.xml文件中加入welcome-file-list信息
  • 路径不需要以“/”开始,并且这个路径默认是从webapp的根下开始查找
<welcome-file-list>
    <welcome-file>login.html</welcome-file>
</welcome-file-list>
  • 如果在新建的目录下则这样添加
<welcome-file-list>
	<welcome-file>page1/page2/page.html</welcome-file>
</welcome-file-list>
  • 一个webapp可以设置多个欢迎页面
  • 越靠上的优先级越高,找不到的继续向下找
<welcome-file-list>
	<welcome-file>login.html</welcome-file>
	<welcome-file>page1/page2/page.html</welcome-file>
</welcome-file-list>
  • Tomcat服务器已经提前配置欢迎页面了,如果没有设置局部的欢迎页面,则默认是index.html

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

    • 一个是在webapp内部的web.xml文件中(局部配置)

    • 一个是在CATALINA_HOME/conf/web.xml文件中进行配置(全局配置)

      • ```xml index.html index.htm index.jsp
        
          - 局部优先原则
        
        - 欢迎页可以是一个`Servlet`
          - 在`web.xml`文件中配置`servlet`和欢迎页即可
        
        ## 关于WEB-INF目录
        
        - 放在`WEB-INF`目录下的资源是受保护的
        - 在浏览器上不能够通过路径直接访问
        - `HTML`、`CSS`、`JS`、`image`等静态资源一定要放到`WEB-INF`目录之外
        
        # HttpServletRequest接口详解
        
        `HttpServletRequest`是一个接口,继承了`ServletRequest`
        
        ```java
        public interface HttpServletRequest extends ServletRequest {}

HttpServletRequest对象中封装了HTTP的请求协议

request和response对象的生命周期

  • request对象和response对象,一个是请求对象,一个是响应对象
  • 这两个对象只在当前请求中有效
  • 一次请求对应一个request
  • 两次请求则对应两个request

HttpServletRequest接口中常用的方法

Map<String,String[]> getParameterMap() //获取Map
Enumeration<String> getParameterNames() //获取Map集合中所有的key
String[] getParameterValues(String name) //根据key获取Map集合的value
String getParameter(String name)  //获取value这个一维数组当中的第一个元素

request请求域对象

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

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

  • 向应用域当中绑定数据,就相当于把数据放到了缓存当中,减少IO的操作,大大提升系统的性能

  • “请求域”对象要比“应用域”对象范围小很多,生命周期短很多

void setAttribute(String name, Object value);
Object getAttribute(String name);
void removeAttribute(String name);
  • 请求域只在一次请求内有效,一个请求对象request对应一个请求域对象,一次请求结束之后,这个请求域就销毁了

  • 如何选择请求域和应用域?

    • 尽量使用小的域对象,因为小的域对象占用的资源较少

Request对象的常用方法

转发(一次请求)

写法一:

//第一步:获取请求转发器对象
RequestDispatcher dispatcher = request.getRequestDispatcher("/b");
//第二步:调用转发器的forward方法完成跳转/转发
dispatcher.forward(request,response);

写法二:

request.getRequestDispatcher("/b").forward(request,response);

Servlet共享数据

  • ServletContext应用域当中,但是应用域范围太大,占用资源太多,不建议使用。

  • 可以将数据放到request域当中,然后AServlet转发到BServlet

  • 转发的下一个资源可以是Tomcat服务器当中的合法资源,例如:html…

    • 转发的路径以“/”开始,不加项目名

HttpServletRequest接口的其他常用方法

// 获取客户端的IP地址
String remoteAddr = request.getRemoteAddr();

// 设置请求体的字符集(处理POST请求的乱码问题,不能解决get请求的乱码问题)
// Tomcat9和9以前,执行以下代码解决乱码
request.setCharacterEncoding("UTF-8");

// Tomcat9和9以前,解决响应乱码
response.setContentType("text/html;charset=UTF-8");

// get请求乱码怎么解决
// 方案:修改CATALINA_HOME/conf/server.xml配置文件
<Connector URIEncoding="UTF-8" />
// 注意:从Tomcat8之后,URIEncoding的默认值就是UTF-8,所以GET请求也没有乱码问题了。
    
// 获取应用的根路径
String contextPath = request.getContextPath();

// 获取请求方式
String method = request.getMethod();

// 获取请求的URI
String uri = request.getRequestURI();  // /aaa/testRequest

// 获取servlet path
String servletPath = request.getServletPath(); //   /testRequest

Servlet实现单表CRUD

准备一张数据库表

# 部门表
drop table if exists dept;
create table dept(
	deptno int primary key,
    dname varchar(255),
    loc varchar(255)
);
insert into dept(deptno, dname, loc) values(10, 'XiaoShouBu', 'BEIJING');
insert into dept(deptno, dname, loc) values(20, 'YanFaBu', 'SHANGHAI');
insert into dept(deptno, dname, loc) values(30, 'JiShuBu', 'GUANGZHOU');
insert into dept(deptno, dname, loc) values(40, 'MeiTiBu', 'SHENZHEN');
commit;
select * from dept;

准备一套HTML页面(项目原型)

  • 欢迎页面:index.html
  • 列表页面:list.html(核心)
  • 新增页面:add.html
  • 修改页面:edit.html
  • 详情页面:detail.html

分析系统的功能

  • 查看部门列表
  • 新增部门
  • 删除部门
  • 查看部门详情
  • 跳转到修改页面
  • 修改部门

DBUtil工具类代码

src目录下创建resources文件夹放置配置文件jdbc.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db2
user=root
password=密码

创建工具类DBUtil

public class DBUtil {
    private static ResourceBundle bundle = ResourceBundle.getBundle("resources.jdbc");
    private static String driver = bundle.getString("driver");
    private static String url = bundle.getString("url");
    private static String user = bundle.getString("user");
    private static String password = bundle.getString("password");
    
    static {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    public static Connection GetConnection() throws SQLException {
        Connection conn = DriverManager.getConnection(url,user,password);
        return conn;
    }
    
    public static void close(Connection conn, Statement ps, ResultSet rs){
        if (rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null){
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}

查看部门列表以及详情页

修改前端超链接

<a href="/oa/dept/list.html">查看部门列表</a>

编写web.xml

<servlet>
    <servlet-name>listDept</servlet-name>
    <servlet-class>top.zhengru.oa.web.action.DeptListServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>listDept</servlet-name>
    <url-pattern>/dept/list</url-pattern>
</servlet-mapping>

完成DeptListServlet编写

public class DeptListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        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 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>");
        String contextPath = request.getContextPath();
        Connection conn = null;
        PreparedStatement ps= null;
        ResultSet rs = null;
        try {
            conn = DBUtil.GetConnection();
            String sql = "select deptno,dname,loc from dept";
            ps = conn.prepareStatement(sql);
            rs = ps.executeQuery();
            int i=0;
            while(rs.next()){
                i++;
                String deptno = rs.getString("deptno");
                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=''>删除</a>");
                out.print("                <a href='edit.html'>修改</a>");
                out.print("                <a href='"+contextPath+"/dept/detail?deptno="+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='add.html'>新增部门</a>");
        out.print("    </body>");
        out.print("</html>");
    }
}

详情页的方法类似,获取get请求然后进行输出

实现部门删除

js中返回部门编号给服务器,再执行删除的sql语句,和上面大同小异

<script type='text/javascript'>
    function del(dno){
        if(window.confirm('确定删除此部门吗?删除后将无法恢复')){
       		window.location = '/oa/dept/delete?deptno='+dno
        }
    }
</script>

<a href="javascript:void(0)" onclick="del(30)">删除</a>

????魔幻的js

原来这个del是这样写的,结果方法死活不能生效??

function del(dno){
    if(window.confirm('确定删除此部门吗?删除后将无法恢复')){
        alert('正在删除数据,请稍后...')
        window.location = '/oa/dept/delete?deptno='+dno
    }
}

本来还以为是a标签的问题,后来把alert给去掉了之后del方法又生效了

image-20230127214509300

后来给两条语句都加上分号,发现又能执行了

alert('正在删除数据,请稍后...');
window.location = '/oa/dept/delete?deptno='+dno;

牛逼的是

本地打开这个没加分号的页面,是没有任何问题的

所以js到底要不要加分号?????

实现部门新增和修改

保存完转发回list的时候,应该在DeptListServlet里重写doPost方法并且调用doGet,否则会报错

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
}

修改分为查询和修改两个步骤,其实都差不多

最终项目结构

image-20230127214315965

转发和重定向深度剖析

代码区别

//转发
request.getRequestDispatcher("/b").forward(request,response);
//重定向
response.sendRedirect(request.getContextPath()+"/b");

本质区别

  • 转发:是由WEB服务器来控制的,Tomcat服务器完成跳转动作
  • 重定向:是浏览器完成的

如何选择?

  • 如果在上一个Servlet当中向request域当中绑定了数据,希望从下一个Servlet当中把request域里面的数据取出来,使用转发机制

  • 剩下所有的请求均使用重定向(重定向使用较多)

  • 跳转的资源不一定是Servlet

Servlet注解式开发

注解+配置文件的开发模式

  • 有一些需要变化的信息,配置到web.xml文件中
  • 一些不会经常变化修改的配置建议使用注解

使用方法

  • @注解名称(属性名=属性值, 属性名=属性值, 属性名=属性值…)
  • 如果数组中只有一个元素,可以省略大括号
@WebServlet(name = "helloServlet",urlPatterns = {"/hello1","/hello2","/hello3"},
            //loadOnStartup = 0,
initParams = {@WebInitParam(name = "username",value = "root"),@WebInitParam(name = "password",value = "密码")})
public class HelloServlet extends HttpServlet {
}

使用模板方法设计模式优化oa项目

写法

一个请求对应一个方法,一个业务对应一个Servlet

@WebServlet({"/dept/list","/dept/save","/dept/edit","/dept/detail","/dept/delete","/dept/modify"})
//@WebServlet("/dept/*")
public class DeptServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        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 {

    }

    private void doSave(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    }

    private void doEdit(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    }

    private void doDetail(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    }

    private void doDel(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    }
    private void doModify(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

	}

优化完后的效果

image-20230129140604906

JSP

JSP是什么?

  • JSP实际上就是一个Servlet
  • Servlet的职责:收集数据
  • JSP的职责:展示数据

原理剖析

  • index.jsp访问的时候,会自动翻译生成index_jsp.java,会自动编译生成index_jsp.class
  • index_jsp 类继承 HttpJspBase,而HttpJspBase类继承的是HttpServlet。所以index_jsp类就是一个Servlet
  • jsp的生命周期和Servlet的生命周期完全相同
  • jspservlet一样,都是单例的(假单例)

基础语法

  • 在JSP中编写的HTML CSS JS代码,这些代码对于JSP来说只是一个普通的字符串。但是JSP把这个普通的字符串一旦输出到浏览器,浏览器就会对HTML CSS JS进行解释执行。展现一个效果。

  • 通过page指令来设置响应的内容类型,在内容类型的最后面添加:charset=UTF-8

    • <%@page contentType=“text/html;charset=UTF-8”%>,表示响应的内容类型是text/html,采用的字符集UTF-8

    • <%@page import=“java.util.List,java.util.ArrayList”%>

  • <% java语句; %>

    • 在这个符号里面的java语句是可以拆分的

    • 被翻译到Servlet类的service方法内部

    • 方法体中不能编写静态代码块,不能用访问修饰符修饰定义变量,不能编写方法

    • service方法当中不能写静态代码块,不能写方法,不能定义成员变量

  • 注释

    <%--jsp的注释,不会被翻译--%>
  • <%! %>

    • 自动翻译到service方法之外

    • 不建议使用,因为在service方法外面写静态变量和实例变量,都会存在线程安全问题

  • JSP的输出语句

    • 向浏览器上输出一个java变量

      • <% String name = “jack”; **out.write("name = " + name);** %>
    • <%= %>

      • =的后面编写要输出的内容
      • 翻译到service方法当中,翻译成:out.print();

语法总结

  • JSP中直接编写普通字符串

    • 翻译到service方法的out.write(“这里”)
  • <%%>

    • 翻译到service方法体内部,里面是一条一条的java语句
  • <%! %>

    • 翻译到service方法之外
  • <%= %>

    • 翻译到service方法体内部,翻译为:out.print();
  • <%@page contentType=“text/html;charset=UTF-8”%>

    • page指令,通过contentType属性用来设置响应的内容类型

使用JSP优化OA项目

list.jsp页面为例

<%@ page import="top.zhengru.oa.bean.Dept" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>部门列表</title>
		<script type="text/javascript">
			function del(dno){
				if(window.confirm("确定删除此部门吗?删除后将无法恢复")){
					window.location = "<%=request.getContextPath()%>/dept/del?deptno="+dno
				}
			}
		</script>
	</head>
	<body>
		<h1 align="center">部门列表</h1>
		<hr>
		<table border="1px" align="center" width="50%">
			<tr>
				<th>序号</th>
				<th>部门编号</th>
				<th>部门名称</th>
				<th>操作</th>
			</tr>
			<%
				List<Dept> deptList = (List<Dept>)request.getAttribute("deptList");
				int i = 0;
				for(Dept dept:deptList) {
			%>
			<tr>
				<td><%=++i%></td>
				<td><%=dept.getDeptno()%></td>
				<td><%=dept.getDname()%></td>
				<td>
					<a href="javascript:void(0)" onclick="del(<%=dept.getDeptno()%>)">删除</a>
					<a href="<%=request.getContextPath()%>/dept/edit?deptno=<%=dept.getDeptno()%>">修改</a>
					<a href="<%=request.getContextPath()%>/dept/detail?deptno=<%=dept.getDeptno()%>">详情</a>
				</td>
			</tr>
			<%
				}
			%>
		</table>
		<hr>
		<a href="<%=request.getContextPath()%>/add.jsp">新增部门</a>
	</body>
</html>

其余页面类似…

OA登录页面

没什么好说的,就是拿到post过来的数据然后去数据库查一下

查到了则登陆成功

一般采用密文

session机制(会话机制)

  • session对应的类名:HttpSession
  • request < session < application

实现原理

  • JSESSIONID=xxxxxxCookie的形式保存在浏览器的内存中,浏览器只要关闭,cookie就没了
  • session列表是一个Mapmapkeysessionidmapvaluesession对象
  • 用户第一次请求,服务器生成session对象,同时生成id发送给浏览器
  • 用户第二次请求,自动将浏览器内存中的id发送给服务器,服务器根据id查找``session对象
  • 关闭浏览器,内存消失,cookie消失,sessionid消失,会话等同于结束
  • Cookie禁用如何实现session
    • 使用URL重写机制
    • http://localhost:8080/servlet12/test/session;jsessionid=19D1C99560DCBF84839FA43D58F56E16
    • jsessionid=19D1C99560DCBF84839FA43D58F56E16这个就是获取到的session id

用法

session.setAttribute();		//存
session.invalidate();		//删

JSP的指令

作用

指导JSP的翻译引擎如何工作(指导当前的JSP翻译引擎如何翻译JSP文件)

三大指令

  • include指令:包含指令,在JSP中完成静态包含,很少用
  • taglib指令:引入标签库的指令,JSTL标签库的时候再学习
  • page指令:目前重点学 习一个page指令

使用语法

<%@指令名 属性名=属性值 属性名=属性值 属性名=属性值…%>

page指令常用属性

<%@page session="true|false" %>
true表示启用JSP的内置对象session,表示一定启动session对象。没有session对象会创建。
如果没有设置,默认值就是session="true"
session="false" 表示不启动内置对象session。当前JSP页面中无法使用内置对象session
<%@page contentType="text/json" %>
contentType属性用来设置响应的内容类型
但同时也可以设置字符集
<%@page contentType="text/json;charset=UTF-8" %>
<%@page pageEncoding="UTF-8" %>
pageEncoding="UTF-8" 表示设置响应时采用的字符集
<%@page import="java.util.List, java.util.Date, java.util.ArrayList" %>
<%@page import="java.util.*" %>
import语句,导包
<%@page errorPage="/error.jsp" %>
当前页面出现异常之后,跳转到error.jsp页面
errorPage属性用来指定出错之后的跳转位置
<%@page isErrorPage="true" %>
表示启用JSP九大内置对象之一:exception
默认值是false

JSP九大内置对象

  • 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 (负责响应)

Cookie

用法

发送cookie

Cookie cookie = new Cookie("productid","13572468");		//创建cookie
cookie.setMaxAge(60*60);	//设置cookie的有效时间(秒)
cookie.setPath(/servlet13”);	//手动设置cookie的path
response.addCookie(cookie);		//向浏览器发送cookie

接收cookie

Cookie[] cookies = request.getCookies(); // 这个方法可能返回null
	if(cookies != null){
	for(Cookie cookie : cookies){
		String name = cookie.getName();
		String value = cookie.getValue();
	}
}

EL表达式

作用

  • EL有预处理,当取不出数据的时候后会显示空白,不会显示null也不会报错

  • 使用EL表达式不会出现数组下标越界的事情,这就是EL表达式的功效

  • 作用:

    • 从某个域中取数据

    • 将取出的数据转成字符串,会自动调用java对象的toString方法将其转换成字符串

  • 基本的语法格式:${表达式}

  • 表达式里面的内容:这里写的一定是存储到域对象当中时的name

  • 这个语法实际上调用了底层的getXxx()方法

  • 注意:如果没有对应的get方法,则报500错误

在这里注意到了一个问题,比如一个方法是getEmail,那么EL表达式里应该是小写emai而不是Emai,否则会报异常。还有一个很有趣的事,如果是getagegetAge不会报异常,但getAGE就会报异常,也许跟驼峰也有些关系(?

读取数据

  • 没指定范围的前提下,EL表达式优先从小范围中读取数据

    //范围指定
    ${pageContext.data}
    ${requestScope.data}
    ${sessionScope.data}
    ${applicationScope.data}
  • EL表达式对null进行了预处理,向浏览器输出一个空字符串

  • 取数据的时候有两种形式:

    • 第一种:. (大部分使用这种方式)

    • 第二种:[ ] (如果存储到域的时候,这个name中含有特殊字符,可以使用 [ ])

      • request.setAttribute(“abc.def”, “zhangsan”);

      • 应该是${requestScope[“abc.def”]}

  • 掌握使用EL表达式,怎么从Map集合中取数据:

    • ${map.key}
  • 掌握使用EL表达式,怎么从数组和List集合中取数据:

    • ${数组[0]}
    • ${数组[1]}
    • ${list[0]}

其他语法

  • page指令当中,有一个属性,可以忽略EL表达式

    <%@page contentType="text/html;charset=UTF-8" isELIgnored="true" %> 全局控制忽略EL表达式
    局部控制:\${username} 也可以忽略EL表达式
  • EL表达式获取应用的根:

    • ${pageContext.request.contextPath}
  • 其他的隐式对象:

    • pageContext:通过这个pageContext可以在EL表达式中获取request等其他jsp的九大内置对象

      • param:取一个值,如果是多个值的话,只取第一个数值

      • paramValues:取多个值,用数组下标可以进行访问

      • initParam:获取Servlet上下文对象ServletContext中的属性内容

EL表达式的运算符

  • 算术运算符

    • +、-、*、/、%

    • 这里的“+”永远只能用于求和,非数字则转为数字

  • 关系运算符

    • == eq != > >= < <=

    • == 和eq底层都是调用的equals方法

  • 逻辑运算符

    • ! && || not and or
  • 条件运算符

    • ? :
  • 取值运算符

    • [ ].
  • empty运算符

    • empty运算符的结果是boolean类型
    • ${empty param.username}

注意事项

  • ServletContext对象是Servlet上下文对象,对应的是JSP九大内置对象的application

  • pageContext.getRequest()获取的是SerevletRequest

  • getContextPath只有HttpServlet

JSTL标签库

导入jar包

  • tomcat10之后引入的jar包是:

    • jakarta.servlet.jsp.jstl-2.0.0.jar
    • jakarta.servlet.jsp.jstl-api-2.0.0.jar
  • jar包下载地址

image-20230215140712853

引入标签库

核心标签库

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

标签的原理

  • uri后面的路径实际上指向了一个xxx.tld文件,实际上是一个xml配置文件
  • 在tld文件中描述了“标签”和“java类”之间的关系

tld配置文件解析

<tag>
    <description>对该标签的描述</description>
    <name>catch</name> 标签的名字
    <tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class> 标签对应的java类。
    <body-content>JSP</body-content> 标签体当中可以出现的内容,如果是JSP,就表示标签体中可以出现符合JSP所有语法的代码。例如EL表达式。
    <attribute>
        <description>
        	对这个属性的描述
        </description>
        <name>var</name> 属性名
        <required>false</required> false表示该属性不是必须的。true表示该属性是必须的。
        <rtexprvalue>false</rtexprvalue> 这个描述说明了该属性是否支持EL表达式。false表示不支持。true表示支持EL表达式。
    </attribute>
  </tag>

<c:catch var="">
	JSP....
</c:catch>

core标签库中常用的标签

  • c:if

    • <c:if test=“boolean类型,支持EL表达式”></c: if>
  • c:forEach

    • <c:forEach items=“集合,支持EL表达式” var=“集合中的元素” varStatus=“元素状态对象”> ${元素状态对象.count} </c: forEach>
    • <c:forEach var=“i” begin=“1” end=“10” step=“2”> ${i} </c: forEach>
  • c:choose c:when c:otherwise

    <c:choose>
        <c:when test="${param.age < 18}">
            青少年
        </c:when>
        <c:when test="${param.age < 35}">
            青年
        </c:when>
        <c:when test="${param.age < 55}">
            中年
        </c:when>
        <c:otherwise>
            老年
        </c:otherwise>
    </c:choose>

Filter过滤器

作用

可以在Servlet这个目标程序执行之前添加代码,也可以在目标Servlet执行之后添加代码

之前之后都可以添加过滤规则,一般情况下,都是在过滤器当中编写公共代码

使用方法

  1. 实现jarkata.servlet.Filter接口

    • init方法:在Filter对象第一次被创建之后调用,并且只调用一次

    • doFilter方法:只要用户发送一次请求,则执行一次。发送N次请求,则执行N次。在这个方法中编写过滤规则

    • destroy方法:在Filter对象被释放/销毁之前调用,并且只调用一次

  2. 在web.xml文件中对Filter进行配置,或者使用注解@WebFilter({“*.do”})

    <filter>
        <filter-name>filter1</filter-name>
        <filter-class>top.zhengru.javaweb.servlet.Filter1</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filter1</filter-name>
        <url-pattern>/abc</url-pattern>
    </filter-mapping>

注意事项

  • Servlet对象默认情况下,在服务器启动的时候是不会新建对象的

  • Filter对象默认情况下,在服务器启动的时候会新建对象

  • Servlet是单例的。Filter也是单例的(单实例)

  • 目标Servlet是否执行,取决于两个条件:

    • 在过滤器当中是否编写了:chain.doFilter(request, response);
    • 用户发送的请求路径是否和Servlet的请求路径一致。
  • chain.doFilter(request, response); 的作用

    • 执行下一个过滤器,如果下面没有过滤器了,执行最终的Servlet
  • Filter的生命周期

    • Servlet对象生命周期一致
    • 区别在Filter默认情况下,在服务器启动阶段就实例化

路径配置

  • 关于Filter的配置路径:

    • /a.do/b.do/dept/save。这些配置方式都是精确匹配
    • /* 匹配所有路径
    • *.do 后缀匹配,不要以 / 开始(模糊匹配的扩展匹配)
    • /dept/* 前缀匹配

执行顺序

  • Filter的优先级,天生的就比Servlet优先级高

    • /a.do 对应一个Filter,也对应一个Servlet。那么一定是先执行Filter,然后再执行Servlet
  • web.xml文件中进行配置的时候,Filter的执行顺序

    • 依靠filter-mapping标签的配置位置,越靠上优先级越高
    • 过滤器的调用顺序,遵循栈数据结构
  • 使用@WebFilter的时候,Filter的执行顺序

    • 在字典中比较Filter类名
    • 但是一般情况都是使用web.xml进行配置

责任链设计模式

  • Filter过滤器这里有一个设计模式:
    • 责任链设计模式
    • 过滤器最大的优点:
      • 在程序编译阶段不会确定调用顺序。因为Filter的调用顺序是配置到web.xml文件中的,只要修改web.xml配置文件中filter-mapping的顺序就可以调整Filter的执行顺序。
      • Filter的执行顺序是在程序运行阶段动态组合的。那么这种设计模式被称为责任链设计模式,符合开发的ocp原则(开闭原则:对拓展开放,对修改关闭)
    • 责任链设计模式最大的核心思想:
      • 在程序运行阶段,动态的组合程序的调用顺序。
  • 使用过滤器改造OA项目
    • 注意在Filter中的reqresponse需要进行强制类型转换

Listener监听器

什么是监听器?

  • 监听器是Servlet规范中的一员
  • 在Servlet中,所有的监听器接口都是以“Listener”结尾

作用

  • 特殊的时刻如果想执行这段代码,你需要想到使用对应的监听器

有哪些监听器

  • jakarta.servlet包下:
    • ServletContextListener 监听ServletContext的状态
    • ServletContextAttributeListener
    • ServletRequestListener
    • ServletRequestAttributeListener
  • jakarta.servlet.http包下:
    • HttpSessionListener
    • HttpSessionAttributeListener
      • 该监听器需要使用@WebListener注解进行标注
      • 该监听器监听session域中数据的变化(添加、删除、替换)
    • HttpSessionBindingListener
      • 该监听器不需要使用@WebListener进行标注
      • 假设User类实现了该监听器,那么User对象在被放入session的时候触发bind事件,User对象从session中删除的时候,触发unbind事件
      • 假设Customer类没有实现该监听器,那么Customer对象放入session或者从session删除的时候,不会触发bindunbind事件
    • HttpSessionIdListener
      • sessionid发生改变的时候,监听器被调用
    • HttpSessionActivationListener
      • 监听session对象的钝化和活化
      • 钝化:session对象从内存存储到硬盘文件
      • 活化:从硬盘文件把session恢复到内存

实现步骤

  • 编写一个类实现ServletContextListener接口,并实现方法

    void contextInitialized(ServletContextEvent event)
    void contextDestroyed(ServletContextEvent event)
  • 在web.xml文件中对ServletContextListener进行配置

    <listener>
    	<listener-class>top.zhengru.javaweb.listener.MyServletContextListener</listener-class>
    </listener>

完结

完结撒花…

image-20230216142929649