Java Web

可以通过浏览器对Java编写的内容进行访问

Java Web基于请求响应开发的

请求request:浏览器或者客户端给服务器发送数据

响应response:服务器给客户端返回数据,读音为rɪˈspɑːns

请求和响应是成对出现的,有请求就有响应

image-20210921110141496

Tomcat

下载地址

下载后选择一个目录解压

以下为相关的目录结构

├───bin 存放可执行程序
├───conf 存放服务器的配置文件
├───lib 存放jar包
├───logs 存放tomcat运行时的日志
├───temp 存放运行时的临时数据
├───webapps 存放web部署的工程
└───work 工作的目录,放一些翻译的源码

启动服务器:执行bin目录下的startup.bat

测试是否启动成功:打开浏览器,打开地址localhost:8080进行测试

关闭服务器:

  • 点击命令行的关闭按钮
  • 再打开的命令行,按CTRL + C
  • 执行bin目录下的shutdown.bat

修改端口号:

修改以下标签中的port,范围1-65535

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

修改完之后,重启tomcat才会生效

将内容部署到服务器

访问:localhost:端口号/文件夹名称

方式1

在tomcat的webapps中新建一个目录,将文件拷贝进去即可

方式2

\conf\Catalina\localhost下新建一个xml配置文件,名称随意

<Context path="/映射名称" docBase="本地路径" />

例如<Context path="/book" docBase="C:/Users/singx/OneDrive/Java/IDEA/JavaWeb/Book" />

访问:localhost:端口号/映射名称

使用IP地址+端口号和直接输入本地路径进行访问的不同

输入本地路径进行访问时,使用的协议是file://协议,表示告诉浏览器直接读取后边的路径,一切都在本地执行,不走网络

如果使用http://ip+端口号访问

image-20210921153016894

如果仅输入http://localhost:端口号,代表此时访问的是Tomcat下的webapps文件夹中的ROOT文件

----webapps下的文件夹---
├───Book
├───docs
├───examples
├───host-manager
├───manager
└───ROOT

idea也可以绑定tomcat

文件 | 设置 | 构建、执行、部署 | 应用程序服务器

image-20210921154133339

idea创建动态的web工程

在创建的模块或者项目上右击,选择添加框架(framework 读音ˈfrāmˌwərk)支持,勾选web应用程序,勾选创建web.xml

image-20210921154835858

此时创建好的动态的web工程为image-20210921155027924

  • src目录存放自己编写的Java代码
  • web目录放置web工程的资源文件,比如html css javascript文件
  • web目录下的WEB-INF是一个受服务器保护的目录,浏览器无法直接访问此目录的内容
  • web.xml文件是保存着项目配置的描述文件
  • 可以新建一个lib文件,里边存放jar

如果没有配置文件web.xml,可以选择 文件-项目结构-facet-当前的web模块,点击+

image-20210921155634547

image-20210921162943468

可以编辑tomcat配置,之后点击运行按钮可以正常运行tomcat实例,可以修改为热部署,当资源修改时,自动更新,不需要再次重启服务器进行更新资源

image-20210921163604001

Servlet

小服务程序

Java EE规范之一,规范就是接口,也是Java Web的三大组件之一

servlet是运行在服务器上的一个Java小程序,主要的作用是:接收客户端发来的请求,并响应数据给客户端

实现servlet

  • 首先需要将tomcat\lib下的servlet-api.jar导入到项目中

  • import javax.servlet.*;
    
  • 新建一个类,使其实现Servlet接口

    • public class ServletTest implements Servlet{
      
      }
      
  • 主要是实现其中的service()方法,处理请求,响应数据

    • @Override
      public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
          System.out.println("Service Test类被使用了");
      }
      
  • web.xml配置文件中配置Servlet程序的访问地址,一个文件中可以有多个<servlet>、<servlet-mapping>标签,因为一个页面中可能有不同的需求

    • <?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">
      <!--    属于上下文参数,输入整个工程,可以有多组-->
          <context-param>
              <param-name>paramname</param-name>
              <param-value>paramvalue</param-value>
          </context-param>
          <!--    servlet标签给Tomcat配置servlet程序-->
          <servlet>
              <!--        为servlet取一个别名,一般为类名,还需要指定映射地址-->
              <servlet-name>HttpServletTest</servlet-name>
              <!--        servlet程序的全类名,包含包名-->
              <servlet-class>HttpServletTest</servlet-class>
              <!--        初始化参数,为键(name)值(value)对的形式,可以有多组-->
              <init-param>
                  <!--            参数名-->
                  <param-name>param</param-name>
                  <!--            参数值-->
                  <param-value>key-value</param-value>
              </init-param>
      <!--        比方说MySQL的用户名和地址可以写到这里-->
              <init-param>
                  <!--            参数名-->
                  <param-name>name2</param-name>
                  <!--            参数值-->
                  <param-value>value2</param-value>
              </init-param>
          </servlet>
          <!--    指定映射地址-->
          <servlet-mapping>
              <!--        告诉服务器,这个映射地址要给哪个servlet程序使用,一般和前边的别名相同-->
              <servlet-name>HttpServletTest</servlet-name>
              <!--        配置访问的地址-->
              <!--        / 斜杠代表```http://ip:端口号/工程路径``` 工程路径为运行/调试配置中的url-->
              <!--        /test代表```http://ip:端口号/工程路径/test-->
              <url-pattern>/test</url-pattern>
          </servlet-mapping>
      
      <!--    servlet标签可以有多个-->
          <servlet>
              <servlet-name>servlet</servlet-name>
              <servlet-class>ServletTest</servlet-class>
          </servlet>
          <servlet-mapping>
              <servlet-name>servlet</servlet-name>
              <url-pattern>/tests</url-pattern>
          </servlet-mapping>
      </web-app>
      
      
    • pattern,中文为图案、模式、格局,读音为ˈpadərn

  • 可以尝试着运行tomcat,默认打开工程路径,在上例中,此时如果打开自行指定的<url-pattern>/test</url-pattern>中的地址,程序会自动执行指定的类中重写的service()方法

    开始运行
    image-20210922100339894.png
    打开自行指定的<url-pattern>/test</url-pattern>中的地址后,自动执行了servlet方法,在控制台最后一行可以看到
    image-20210922100436386

如果自行指定的<url-pattern></url-pattern>标签中不加/,运行时会报错,提示无效地址

执行过程

image-20210922165642423

Servlet的生命周期

只有进入到http://ip:端口号/工程路径/自行制定的地址后才会触发servlet的各个方法,第1、2步仅在第一次进入到http://ip:端口号/工程路径/自行制定的地址创建servlet对象时才会调用

执行顺序为:

  1. Servlet实现类的构造器,仅第一次访问才会调用
  2. init()初始化方法,仅第一次访问才会调用
  3. service()方法,每次访问都会调用
  4. destroy()销毁方法,只有在程序停止时才会执行

destroy,中文为摧毁、销毁、破坏,读音为dɪˈstrɔɪ

处理get请求和post请求

servlet的实现类中,有一个service方法需要手动实现,该方法有两个参数,一个是请求参数requestServletRequest类型)和响应参数response

在请求参数类型中,它的子接口HttpServletRequest接口中有一个String getMethod()的方法可以获取到它的请求的方法(对应着HTML表单中的method属性)

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    System.out.println("第三被执行的service方法,请求方式为" + request.getMethod());

}

在实际开发中,如果对使用get或者post方法进行处理时,可能要进行很多的操作,可以将相关的操作都放在一个方法中,可以直接调用相关的方法进行处理

继承子类实现Servlet

在实际的开发中,很少直接实现Servlet接口,通常都是继承其子类

可以继承HttpServlet类,根据需要重写相关的方法,比方说doPost()doGet()方法

idea可以快速生成servlet的配置文件

image-20210922203804354

image-20210922210803829

GenericServlet为抽象类,实现了Servlet接口,但仍需重写service()方法,而HttpServlet类继承于GenericServlet,所以重写了service()方法,在此基础上,又添加了许多方法,大部分的方法都是负责抛出异常的,比方说doGet()doPost()这两个方法只会抛出异常,所以要有一个自己写的类继承自HttpServlet类,而自己的类可以根据需要重写相关的方法(如果用得到的话,必须要重写,否则无法正常的使用)

ServletConfig类

Servlet程序的配置信息类

param中文为参数

由Tomcat负责创建,默认是第一次访问时创建

功能:

  • 可以获取Servlet的别名,即<servlet-name></servlet-name>的值
  • 获取初始化参数init-param
  • 获取ServletContext对象,context中文为语境、景况

在手动实现的Servlet接口中有一个public void init(ServletConfig config)方法,同样的,在HttpServlet类的父类中也有这个方法(需要重写),在这个形式参数中,有一个ServletConfig,可以通过该参数进行相关的配置信息获取等操作

获取别名

public void init(ServletConfig config) throws ServletException {
    System.out.println("别名为:" + config.getServletName());
}

获取初始化值

public void init(ServletConfig config) throws ServletException {
    System.out.println("初始化参数为param的值为:" + config.getInitParameter("param"));
    System.out.println("初始化参数为name的值为:" + config.getInitParameter("name2"));
    System.out.println("初始化参数为不存在的值为:" + config.getInitParameter("不存在"));
}

通过getInitParameter(String name)方法,获取<init-param>标签中的<param-name><param-value>,方法的参数name<param-name>中的值相对应,返回<param-value>的值,如果没有相关的键值对,则返回null

		<init-param>
            <!--            参数名-->
            <param-name>param</param-name>
            <!--            参数值-->
            <param-value>key-value</param-value>
        </init-param>

获取ServletContext对象:getServletContext()

可能出现的错误

HttpServlet类中的其方法中,也可以使用servletConfig的(前提是自己的实现类中没有重写init()方法)但需要通过getServletConfig()方法进行获取

因为在GenericServlet类中,定义了 ServletConfig,以下是相关的方法和属性

	private transient ServletConfig config;
    public ServletConfig getServletConfig() {
        return this.config;
    }
	public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }

由此可见,再执行init(ServletConfig config)方法时,会将获取到的ServletConfig config给第一行的private transient ServletConfig config;中的config,而getServletConfig()方法返回的是this.config,即第一行中的config,该属性在没有执行在GenericServlet类中的init(ServletConfig config)方法前是空的,如果子类中重写了init()方法 而不去处理config属性,此时的config会一直为空,解决办法:

  • 子类中重写getServletConfig()方法
  • 调用父类的init(ServletConfig config)方法
  • 在子类的init(ServletConfig config)方法中给config属性赋值,该操作无法实现,因为config的权限修饰符为private
  • 仅重写init()方法的空参重载的方法,因为在init(ServletConfig config)方法中,调用了空参的init()方法

如果在自定义类中重写了init()方法,那么在其他方法中调用getServletConfig()方法时,会抛出空指针异常的错误

public class HttpServletTest extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post请求");
 
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //因为重写了init方法,此时调用getServletConfig()方法会报空指针异常
        System.out.println("get请求000");
        System.out.println("初始化参数为param的值为:" + getServletConfig().getInitParameter("param"));
        System.out.println("初始化参数为name的值为:" + getServletConfig().getInitParameter("name2"));
        System.out.println("初始化参数为不存在的值为:" + getServletConfig().getInitParameter("不存在"));
    }

    //重写了init方法
    public void init(ServletConfig config) throws ServletException {
        
    }
}

image-20210923105013337

为避免此问题,可以在重写的方法中,加上super.init(config);,默认还是调用父类的

public void init(ServletConfig config) throws ServletException {
    super.init(config);
}

ServletContext类

  • 是一个接口
  • 一个web工程只有一个ServletContext对象实例
  • 是一个域对象

域对象:可以和map一样存、取、删除数据

image-20210923113814721

作用:

  • 获取web.xml中的context-param
  • 获取当前的工程路径
  • 获取工程部署后在磁盘的绝对路径
  • 像map一样存取数据

只能得到

<context-param>
        <param-name>键</param-name>
        <param-value>值</param-value>
</context-param>

中的内容

工程重启后自动销毁,数据也消失,可以在整个工程中存取数据

public void init() throws ServletException {
//       获取web.xml中的<context-param>
        System.out.println("获取web.xml中的<context-param>" + getServletContext().getInitParameter("paramname"));
        System.out.println("获取web.xml中的<context-param>的不存在的值" + getServletContext().getInitParameter("not find"));
//       获取当前的工程路径
        System.out.println("当前的工程路径" + getServletContext().getContextPath());
//       获取工程部署后在磁盘的绝对路径
//        "/"代表http://ip:port/工程名/
//        为运行时idea拷贝的路径
        System.out.println("当前在磁盘的绝对路径" + getServletContext().getRealPath("/"));

//       像map一样存取数据
        System.out.println("key的值为:" + getServletContext().getAttribute("key"));
//        存
        getServletContext().setAttribute("key", "value");
//        取
        System.out.println("key的值为:" + getServletContext().getAttribute("key"));
        System.out.println("key的值为:" + getServletContext().getAttribute("key"));
    }
}

请求

分为get请求和post请求

常用的请求头:

  • Accept:客户端可以接收的数据类型
  • Accept-Language:客户端可以接收的语言类型
  • User-Agent:浏览器的信息
  • Host:请求时服务器的IP和端口号

get请求

请求行:

  • 请求的资源路径:+?+请求参数

请求头

  • key:value组成,不同的键值对表示不同的含义

image-20210923153032317

post请求

分为:

  • 请求行
  • 请求头
  • 请求体
image-20210923153801057

image-20210923154349200


分类

Get请求:

  • form标签的method="get"
  • a标签
  • link标签引入css
  • srcipt标签引入js文件
  • img标签插入图片
  • iframe引入html页面
  • 浏览器地址栏输入地址敲回车

post请求:

  • form标签的method="post"

响应的HTTP协议的格式

image-20210923172151199

  • 响应行
    • 响应的协议和版本号 HTTP1.1
    • 响应状态码 200
    • 响应状态描述符 ok
  • 响应头
    • 键值对形式,不同的响应头有不同的含义
  • 响应体
    • 回传给客户端的数据

常见的响应状态码

状态码含义
200表示请求成功
302表示请求重定向
404表示请求服务器已经收到了,但是所要的数据不存在,也就是请求地址错误
500表示服务器已经收到请求了,但服务器内部错误(代码错误)

HttpServletRequest类

只要有请求进入Tomcat服务器,Tomcat就会把请求过来的HTTP协议信息解析好封装在Request对象中,然后传递到service()方法中以供使用,可以通过HttpServletRequest对象获取所有的请求信息

方法含义
getRequestURI()获取请求资源的路径
getRequestURL()获取请求资源的绝对路径
getRemoteHost()获取客户端的IP地址,中文为远程、远端、偏僻,读音为rəˈmōt
getHeader()获取请求头
getParameter(key)获取请求的参数,例如表单里边的提交的内容可以查到,parameter,读音为pəˈræmɪtər,中文为参数
getParameterValues()获取请求的参数,在有多个值的时候使用
getMethod()获取请求的方法,Get或者POST
setAttrbute(key, value)设置域数据,其中,value类型是Object
getAttribute(key)获取域数据
getRequestDispatcher("path")获取请求转发对象,dispatcher读音dɪˈspætʃər,中文 调度员,dispatch,中文为派遣、调度、发送
setCharacterEncoding("字符集编码")设置字符集编码,解决post提交中文乱码问题character读音为ˈkærəktər,一定要在获取请求之前调用!否则不生效
getScheme()获取协议,比如HTTP协议,返回字符串,读音skiːm,中文计划/方案
getServerName()获取服务器IP地址
getServerPort()获取服务器端口号
getContextPath()获取工程路径

URI为设置的部署路径 + <url-pattern>中指定的地址

URL为http://IP:port/部署路径/<url-pattern>

URI = /web2/api
URL = http://localhost:8080/web2/api
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("URI = " + req.getRequestURI());
        System.out.println("URL = " + req.getRequestURL());
//        如果使用local host进行访问得到的是0:0:0:0:0:0:0:1,即IPV6的IP地址
//        如果使用的是127.0.0.1进行访问,得到的是127.0.0.1
//        如果使用的是本机的IP地址,得到的是本机的IP地址
        System.out.println("客户端的IP地址 = " + req.getRemoteHost());
        System.out.println("请求头中的浏览器标识为:" + req.getHeader("user-agent"));
        System.out.println("请求方式:" + req.getMethod());

    }

获取get请求的内容

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String ip = req.getRemoteHost();
    String equipment = req.getHeader("user-agent");
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    equipment = equipment.substring(equipment.indexOf("(") + 1, equipment.indexOf(";"));
    System.out.println("---------------");
    System.out.println("ip = " + ip);
    System.out.println("equipment = " + equipment);
    System.out.println("username = " + username);
    System.out.println("password = " + password);
    System.out.println("---------------");
}

相应的HTML代码

<form action="/web2/api" method="get">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <button type="submit">提交</button>
</form>

控制台输出的信息

---------------
ip = 0:0:0:0:0:0:0:1
equipment = Linux
username = 123
password = 123
---------------

如果表单中的相应值没有请求,则取出来的相应的值为null

如果请求的数据一个键有多个值时(例如表单使用checkbox复选框)可以使用getParameterValues(key),此时返回值是String[]

获取Post请求的内容

基本代码和Get请求一致

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //出现了中文乱码的情况,需要设置一下字符集编码,一定要在获取请求之前调用!
    req.setCharacterEncoding("UTF-8");
    String ip = req.getRemoteHost();
    String equipment = req.getHeader("user-agent");
    String username = req.getParameter("username");
    String[] password = req.getParameterValues("password");
    equipment = equipment.substring(equipment.indexOf("(") + 1, equipment.indexOf(";"));
    System.out.println("----Post请求-----");
    System.out.println("ip = " + ip);
    System.out.println("equipment = " + equipment);
    System.out.println("username = " + username);
    System.out.println("password = ");
    for (String s : password) {
        System.out.println(s);
    }
    System.out.println("---------------");
}

请求转发

服务器收到请求后,服务器收到请求后,从一个资源跳转到另一个资源的操作叫做请求转发

image-20210924151203297

在RequestDispatcher类中有一个转发的方法,为forward(request, response),中文为向前、发送,读音为ˈfôrwərd

可以进行的操作:

  • 获取传递进来的参数
  • 根据传递进来的参数,进行处理,在域对象中进行插入一个值做一个标记
  • 获取转发的对象
  • 进行转发
  • 处理转发后的内容

默认进行访问的页面

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
</head>
<body>
<form action="/web2/requests" method="get">
    用户名:<input type="text" name="username"><br>
    <button type="submit">提交</button>
</form>
</body>
</html>

进行请求和转发的类

package package1;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class requests extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        象征意义的看一下传递进来的参数
        System.out.println("用户名" + req.getParameter("username"));
//        做一个标记,代表已通过
        req.setAttribute("flag", "success");
//        获取请求转发对象,参数中填写要转发进入的地址,要以"/"开头
        RequestDispatcher requestDispatcher = req.getRequestDispatcher("/forward");
//        进行转发
        requestDispatcher.forward(req, resp);
    }
}

转发后进行处理的类

package package1;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class forward extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        象征性的再次取出数据
        System.out.println("转发后获取的用户名为:" + req.getParameter("username"));
//        看一下转发前的标记的内容
        System.out.println("flag = " + req.getAttribute("flag"));
        System.out.println("转发结束");
    }
}

特点:

  • 转发后的地址不变
  • 只进行一次请求
  • 共享域中的数据,即设置的键值对共享
  • 可以转发到WEB-INF目录下
  • 所设置的转发的路径中可以带有文件名(.html
  • 不可以跳转到外部地址,例如http://www.google.com

base标签

在使用请求转发的跳转过程中,如果这个页面中有如果有相对路径进行跳转到其他页面的操作时,若直接用相对路径跳转会出现问题。原因是请求转发后,转发后浏览器的地址不变,而相对路径就是针对当前浏览器的地址进行转发的,为解决这个问题,引入了<base href="为基准的地址">标签,该标签可以尝试写在<head></head>

在base标签中,最后的具体文件名可以省略

比如<base href="http://localhost:8080/aaa/bbb/ccc.html">等价于

<base href="http://localhost:8080/aaa/bbb/",最后的/不可以省略,即bbb/不能写为bbb,省略了最后的/bbb不会被认为是路径,而是被当作资源文件进行处理的

image-20210924171852061

Web中/的含义

image-20210924173525688

在web中是一个绝对路径,在服务器和浏览器中有不同的含义

被浏览器解析:

  • <a href = "/">斜杠</a>
  • 这里的/代表http://ip:port

被服务器解析:

  • /代表http://ip:port/工程名/
  • image-20210924174215297

HttpServletResponse类

每次请求完毕,Tomcat都会创建一个response对象传递给servlet使用,如果设置返回给客户端的信息,可以对HttpServletResponse对象进行设置

两个输出流

字节流:getOutputStream()

  • 常用于下载,传递二进制数据

字符流:getWriter()

  • 常用于回传字符串

  • 返回PrintWriter

    • PrintWriter类中,有类似于System.out.printXXX()的方法,这些方法作用效果是在浏览器网页中输出相关的内容

    • protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
          resp.getWriter().println("1234");
      }
      

两个流只能同时用一个

image-20210925103742420

直接输出中文会产生乱码,默认的字符集编码为ISO-8859-1

若将resp.setCharacterEncoding("UTF-8");设置为UTF-8后,中文还是会乱码,这是因为浏览器不知道需要解析的字符编码,还需要设置浏览器的响应头为UTF-8resp.setHeader("Content-Type", "text/html;charset=UTF-8");

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("resp.getCharacterEncoding() = " + resp.getCharacterEncoding());
    resp.setCharacterEncoding("UTF-8");
    resp.setHeader("Content-Type", "text/html;charset=UTF-8");
    resp.getWriter().println("测试中文");
}

其中text/html代表HTML格式,charset代表字符集

也可以使用resp.setContentType("text/html;charset=UTF-8");直接设置,此时将服务器和客户端以及响应头都设置为了UTF-8

一定要在获取流之前调用才有效

请求重定向

方式1

客户端给服务器发送一个请求,服务器重定向一个地址让客户端访问

image-20210925113140591

过程:

  • 设置响应状态码为302

    • resp.setStatus(302);
      
      image-20210925113740760
  • 设置响应头的Location为重定向后的地址

    • resp.setHeader("Location", "重定向后的地址");
      
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    System.out.println("resp.getCharacterEncoding() = " + resp.getCharacterEncoding());
    resp.setContentType("text/html;charset=UTF-8");
    resp.setStatus(302);
    resp.setHeader("Location", "地址");
}

image-20210925115125643

上图就是设置响应状态码为302,响应头中的Locationhttps://www.google.com

特点:

  • 浏览器地址栏的地址有变化
  • 是两次请求(第一次给服务器发送请求,第二次为服务器给出的响应
  • 不共享Request域中的数据,即使用request.setAttribute(key, value)的值不共享
  • 无法访问WEB-INF文件夹下的东西
  • 可以重定向至外部地址,例如Google

方式2

直接使用resp.sendRedirect("地址")

redirect中文为重定向、重新使用、读音为ˌriːdəˈrekt

direct中文为直接的

Java EE三层框架

image-20210925141552285

结构

  • service为接口包
  • implement实现接口的包
  • utils为工具类的包
  • test为测试的包

  • 先创建相应地数据库
  • 编写相应的JavaBean

image-20210926113940135

可以尝试进行解决image-20210926121721564

如果出现所有的类找不到了,可以尝试清除缓存image-20210926195808088

image-20210926195820792

Debug

  • 步过image-20210926200933437:将代码往下执行一行
  • 步入image-20210926200955409:可以进入自定义的方法体内(第三方包属于自定义的范畴)
  • 步出image-20210926201014774:跳出当前所在的方法
  • 强制步入image-20210926201026774:对于所有的方法,无论是内置的还是自己写的都可以进入
  • 运行到光标处image-20210926201040157:无论代码在哪里,只要光标之前没有断点,代码会直接运行到光标所在的行,相当于临时断点
  • 恢复程序image-20210926201938548:执行到下一个断点处停止,如果没有下一个断点,则执行完程序

变量视图可以查看当前方法内所有有效的变量

JSP

全称为Java server pages,即为Java服务器页面

代替Servlet程序回传html页面的数据,Servlet回传数据比较麻烦,开发和维护成本都极高

例如

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class Res extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = resp.getWriter();
        writer.write("<!DOCTYPE html>");
        writer.write("<html lang=\"en\">");
        writer.write("<head>");
        writer.write("<meta charset=\"UTF-8\">");
        writer.write("<title>Title</title>");
        writer.write("</head>");
        writer.write("<body>");
        writer.write("<p>");
        writer.write("testtttt");
        writer.write("</p>");
        writer.write("<button type = button>按钮</button>");
        writer.write("</body>");
        writer.write("</html>");
        writer.println("中文中文");
    }
}

<?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>Res</servlet-name>
        <servlet-class>Res</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Res</servlet-name>
        <url-pattern>/res</url-pattern>
    </servlet-mapping>
</web-app>

响应后的效果为image-20210927210510762
由此可见整个页面的相关代码在设置时特别繁琐

新建一个jsp

右键-新建-JSP/JSPX

image-20210927211621264

jsp和HTML一样,都是存放在web目录底下,访问方式也和HTML一样

jsp本质上是一个servlet程序,第一次在浏览器打开jsp文件时,tomcat会自动将jsp文件翻译成相关的类,并将其编译为字节码文件,这个文件间接的继承了HttpServlet类,也就是说,是一个servlet程序

jsp

路径在image-20210927213454550

打开源文件,可以发现,生成的内容大致与自行写的writer.write("内容");差不多

jsp的配置指令

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

可以修改页面中的一些重要的属性或者行为

属性含义
language表示jsp所翻译的语言,暂时只支持Java,也就是打开相关页面后,会翻译成相关的编程语言
contentType表示jsp的返回的数据类型,可以设置页面的字符集编码,相当于翻译文件后的response.setContentType("text/html;charset=UTF-8");
pageEncoding表示当前页面的字符集编码
import与Java中的作用相同,用于导包、导入类,例如<%@ page import="java.util.Scanner" %>
autoFlush是给out输出流使用的,flush,中文为冲洗,读音为fləSH,设置当out缓冲区满了之后,是否自动刷新缓冲区,默认为true
buffer是给out输出流使用的,设置缓冲区的大小,默认为8kb
errorPage设置当前jsp页面出错后自动跳转到的路径(对于普通用户来说,可能会看不懂这是什么错误,可以指定到一个错误页面),即当前页面抛出异常时跳转去的页面,例如可能遇到下面jsp缓冲区溢出的错误后,直接跳转到设置的路径
isErrorPage设置当前页面是否是错误页面,默认为false,如果是true可以获取错误信息,会有相关的对象进行创建
session设置当前页面是否会创建HttpSession对象,默认为true,读音为ˈseʃn,中文为会议
extends设置翻译出来的Java类默认继承的类

对于给out输出流使用的属性:

<%@ page autoFlush="false" buffer="1kb" %>

这时候复制几千行的内容,此时大小必定超过1kb了,这时候也把自动刷新缓冲区给关闭了,此时打开浏览器,给提示错误,抛出的异常为java.io.IOException,具体信息是JSP缓冲区溢出

image-20210929212839604
HTTP状态 500 - 内部服务器错误
类型 异常报告

消息 在 [7] 行处理 [/2.jsp] 时发生异常

描述 服务器遇到一个意外的情况,阻止它完成请求。

例外情况

java.io.IOException: 在 [7] 行处理 [/2.jsp] 时发生异常

4:   Date: 2021/9/27
5:   Time: 21:25
6:   To change this template use File | Settings | File Templates.
7: --%>
8: <%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" language="java" %>
9: <%@ page autoFlush="false" %>
10: <html>


Stacktrace:
	org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:493)
	org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:383)
	org.apache.jasper.servlet.JspServlet.service(JspServlet.java:331)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
根本原因。

java.io.IOException: 错误:JSP缓冲区溢出
	org.apache.jasper.runtime.JspWriterImpl.bufferOverflow(JspWriterImpl.java:151)
	org.apache.jasper.runtime.JspWriterImpl.write(JspWriterImpl.java:330)
	java.base/java.io.Writer.write(Writer.java:249)
	org.apache.jsp._2_jsp._jspService(_2_jsp.java:626)
	org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
	org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:465)
	org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:383)
	org.apache.jasper.servlet.JspServlet.service(JspServlet.java:331)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
):注意 主要问题的全部 stack 信息可以在 server logs 里查看

Apache Tomcat/8.5.71

可以使用默认设置就可以

jsp脚本

格式:

<%! 声明脚本的格式,可以给jsp翻译出来的类定义属性和方法,甚至是静态代码块,内部类 %>
<%@ page import="java.util.HashSet" %>
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<p>测试页面</p>
<%--声明类的属性--%>
<%!
    public int id;
    public String name;
    public static HashSet<Integer> hashSet = new HashSet<>();
%>
<%--声明静态代码块--%>
<%!
    static {
        hashSet.add(2);
        hashSet.add(3);
        hashSet.add(32);
        hashSet.add(52);
    }
%>
<%--声明方法--%>
<%!
    public int function(int a, int b){
        return a + b;
    }
%>
<%--声明内部类--%>
<%!
    class innerClass{
        int text;

    }
%>
</body>
</html>

此时声明HashSet时,会自动导入java.util.HashSet

<%@ page import="java.util.HashSet" %>

此时打开翻译好的文件,会发现:

image-20210930110334718

翻译好的文件中有自定义的相关属性

如果自定义的jsp脚本中有System.out.printXXX();语句,则默认在ide的终端中输出

表达式脚本

可以在jsp页面中输出:

  • 整型
  • 浮点型
  • 对象
  • ....

格式:

<%=值/变量名%>
<%--整型--%>
<%=111%>
<br>
<%--浮点型--%>
<%=111.111%>
<br>
<%--字符串--%>
<%="字符串"%>
<br>
<%--HashSet对象--%>
<%=hashSet%>
<br>
<%--调用函数--%>
<%=function(1, 4)%>

根据以上编写的代码,浏览器输出结果为

测试页面

111
111.111
字符串
[32, 2, 3, 52]
5

相应的,在翻译的Java源码中,有以下的内容与之对应:

image-20210930111741396

      out.print(111);
      out.write("\r\n");
      out.write("<br>\r\n");
      out.write('\r');
      out.write('\n');
      out.print(111.111);
      out.write("\r\n");
      out.write("<br>\r\n");
      out.write('\r');
      out.write('\n');
      out.print("字符串");
      out.write("\r\n");
      out.write("<br>\r\n");
      out.write('\r');
      out.write('\n');
      out.print(hashSet);
      out.write("\r\n");
      out.write("<br>\r\n");
      out.write('\r');
      out.write('\n');
      out.print(function(1, 4));
  • 所有的表达式脚本会被翻译到_jspService()方法中,而这个方法所带有的两个参数为

    • _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response)
      
    • 这就意味着,requestresponse中的方法都可以正常使用

    • 例如,获取请求的参数值

    • <%=request.getParameter("username")%>
      
    • 浏览器地址栏为http://localhost:8080/ress/2.jsp?username=admin

    • 则此时的结果为admin

  • 会被翻译为out.print(XXX)

  • 表达式脚本的末尾不能以分号结束

    • 正确写法
      <%=request.getParameter("username")%>
      
    • 错误写法
      <%=request.getParameter("username");%>
      
    • image-20210930112950975

    • image-20210930113017836

    • 这是因为翻译后的代码为out.print(request.getParameter("username"););,很显然,这种格式是不对的

代码脚本

通常写一些语句,例如循环、判断、输出、加减乘除,可以写一个类,但不能直接写方法,需要在类中写方法

格式:

<%
	普通的Java语句;
%>
<%
    System.out.println("hello world");
%>
  • 和表达式脚本一样,翻译后的Java文件中,也是被翻译到_jspService()方法中,同时也是可以使用requestresponse

  • 同时,如果在代码脚本中new的对象,也可以在表达式脚本<%=对象%>中使用,也就说,能够被翻译到_jspService()方法中的代码都可以写

  • 也可以将代码脚本组合在一块形成一段完整的代码

    • <%
          for (int i = 0; i < 10; i++) {
      %>
      <%
              System.out.println("输出");
          }
      %>
      
    • 此时控制台内容为

    • 输出
      输出
      输出
      输出
      输出
      输出
      输出
      输出
      输出
      输出
      
  • 代码脚本也可以和表达式脚本配合在一起使用

    • <%
          for (int i = 0; i < 10; i++) {
      %>
      <%="测试"%>
      <%
          }
      %>
      
    • 此时的网页中的内容有10个测试

    • image-20210930120811434

可以和前端的代码缝合在一块,形成特殊的效果

image-20210930121034974

image-20210930121046990

jsp中的三种注释

  • HTML注释
    • 被翻译成out.write("<!--html注释-->\r\n");
  • Java注释
    • 直接翻译
    • image-20210930121834343
  • jsp注释,可以注释掉一切,包含HTML、Java注释和代码
    • 不被翻译

jsp中9大内置对象

image-20210930122110120

其中异常对象需要手动开启,开启方式:将isErrorPage属性设置true

四大域对象

域对象是可以像map一样存取内容的对象,四个域对象的功能一致,不同的是对数据存取的范围,使用的优先级顺序为数据存取范围的由小到大:

  • pageContextPageContextImpl类,仅在当前jsp页面有效
  • requestHttpServletRequest类,一次请求内有效
  • sessionHttpSession类,一个会话范围内有效(一次会话:打开浏览器访问服务器,直到关闭浏览器,只要浏览器不关闭,则会话一直都在)
  • applicationServletContext类,整个web工程内有效,只要web工程不停止,那么数据一直都在,中文为应用、申请、申请书,读音为ˌæplɪˈkeɪʃn

可能需要Java EE 6-Java EE 6

<%
//    给四个域设置键值
    pageContext.setAttribute("key", "pageContext");
    request.setAttribute("key", "request");
    session.setAttribute("key", "session");
    application.setAttribute("key", "application");
%>
<%=pageContext.getAttribute("key")%>
<br>
<%=request.getAttribute("key")%>
<br>
<%=session.getAttribute("key")%>
<br>
<%=application.getAttribute("key")%>

浏览器输出的内容为

pageContext
request
session
application

数据存取范围测试:

首先有两个文件,分别为3.jsp4.jsp,打开两个浏览器,分别访问这两个文件

<%--3.jsp--%>
-------------------------------------
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>3.jsp</h1>
<%
//    给四个域设置键值
    pageContext.setAttribute("key", "pageContext");
    request.setAttribute("key", "request");
    session.setAttribute("key", "session");
    application.setAttribute("key", "application");
%>
<%
//    设置请求的转发,转发到4.jsp,并且将request和response作为参数传递过去
    request.getRequestDispatcher("/4.jsp").forward(request, response);
%>
</body>
</html>
<%--4.jsp--%>
----------------------------------------
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1>4.jsp</h1>
pageContext: <%=pageContext.getAttribute("key")%>
<br>
request: <%=request.getAttribute("key")%>
<br>
session:<%=session.getAttribute("key")%>
<br>
application:<%=application.getAttribute("key")%>
</body>
</html>
  • 浏览器1访问3.jsp,此时内容转发到4.jsp,内容为:

    • 3.jsp跳转到4.jsppageContext只能在一个页面中有效,所以转发后为null;在请求转发的过程中传递的参数有request,算一次请求,所以request中的内容得以保存

    • 4.jsp
      pageContext: null
      request: request
      session:session
      application:application
      
  • 浏览器1访问4.jsp,此时内容为:

    • 4.jsp
      pageContext: null
      request: null
      session:session
      application:application
      
  • 浏览器2访问4.jsp,此时内容为:

    • 相当于关闭浏览器后再访问4.jsp,所以session此时为null

    • 4.jsp
      pageContext: null
      request: null
      session:null
      application:application
      
  • 重启服务器(重新部署)后,浏览器访问4.jsp,内容为:

    • 此时相当于web工程重启了,所以application也为null

    • 4.jsp
      pageContext: null
      request: null
      session:null
      application:null
      

JSP中的out输出和response.getWriter输出区别

在jsp中有这样一段代码

<%
    response.getWriter().write("response.getWriter()输出,<br>");
    out.write("out.writer()输出,<br>");
    response.getWriter().write("response.getWriter()输出,<br>");
%>

<%
    out.write("out.writer()输出,<br>");
    response.getWriter().write("response.getWriter()输出,<br>");
    response.getWriter().write("response.getWriter()输出,<br>");
%>

浏览器访问后,两次的结果都为

response.getWriter()输出,
response.getWriter()输出,
out.writer()输出,

可见,out的输出顺序一直都在response的下边

原因:

  • outresponse都分别有自己的缓冲区
  • 当jsp代码加载完毕后,会执行out.flush进行刷新缓冲区,此时缓冲区的内容会被加载到response的末尾
  • 然后会执行response的刷新操作,把全部数据写给客户端

为避免out的问题,一般都直接使用out.write的方式进行输出

out.write和out.printXX

  • out.write()针对字符串的输出不会出现问题,如果直接输出整型会出现问题
  • out.printXXX()对任何类型的值都可以输出,因为会将任意的类型转换为字符串类型

JSP静态标签

分为静态包含、动态包含、请求转发标签

jsp静态包含
  • image-20211002112633931
  • 一个页面中,或许都有顶部导航栏或者底部导航栏,如果在一个页面中都加入同样的导航栏,使用传统的方式进行插入,每次去维护成千上万的页面会变的特别麻烦

  • 为了提高可维护性,把所有页面中都包含的一些内容可以使用包含引用,将其引入到页面中

  • 引入方式

    • <%@ include file="路径" %>
      

有以下的目录结构

│   5.jsp
│
└───include
        header.jsp
        tail.jsp

5.jsp需要引入头部文件header.jsptail.jsp尾部文件

<%--header.jsp--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
头部信息 头部信息 头部信息 头部信息
<br>
<br>
</body>
</html>
<%--tail.jsp--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<br>
尾部信息 尾部信息 尾部信息 尾部信息
</body>
</html>
<%--5.jsp--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%--引入头部文件--%>
<%@ include file="include/header.jsp"%>
主体内容<br>
主体内容<br>
主体内容<br>
主体内容<br>
主体内容<br>
主体内容<br>
<%--引入尾部文件--%>
<%@ include file="include/tail.jsp"%>
</body>
</html>

页面内容为

头部信息 头部信息 头部信息 头部信息

主体内容
主体内容
主体内容
主体内容
主体内容
主体内容

尾部信息 尾部信息 尾部信息 尾部信息

对于以上文件的包含方式,翻译后的Java源码为


      out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("    <title>Title</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("    <title>Title</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("头部信息 头部信息 头部信息 头部信息\r\n");
      out.write("<br>\r\n");
      out.write("<br>\r\n");
      out.write("</body>\r\n");
      out.write("</html>\r\n");
      out.write("\r\n");
      out.write("主体内容<br>\r\n");
      out.write("主体内容<br>\r\n");
      out.write("主体内容<br>\r\n");
      out.write("主体内容<br>\r\n");
      out.write("主体内容<br>\r\n");
      out.write("主体内容<br>\r\n");
      out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("    <title>Title</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write("<br>\r\n");
      out.write("尾部信息 尾部信息 尾部信息 尾部信息\r\n");
      out.write("</body>\r\n");
      out.write("</html>\r\n");
      out.write("\r\n");
      out.write("</body>\r\n");
      out.write("</html>\r\n");
  • 也就是相当于把两个文件的所有的东西都导入到了页面中
  • 将多个文件中的内容直接使用out.write("字符串")进行写出
jsp动态包含

语法

<jsp:include page="路径"></jsp:include>

例如

<%--
  Created by IntelliJ IDEA.
  User: singx
  Date: 2021/10/2
  Time: 14:37
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%--动态包含--%>
<jsp:include page="include/header.jsp"></jsp:include>
中间内容
<br>
中间内容
<br>
中间内容
<br>
<%--动态包含--%>
<jsp:include page="include/tail.jsp"></jsp:include>

</body>
</html>

翻译后的jsp代码部分为

out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("    <title>Title</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");
      out.write('\r');
      out.write('\n');
      org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "include/header.jsp", out, false);
      out.write("\r\n");
      out.write("中间内容\r\n");
      out.write("<br>\r\n");
      out.write("中间内容\r\n");
      out.write("<br>\r\n");
      out.write("中间内容\r\n");
      out.write("<br>\r\n");
      out.write('\r');
      out.write('\n');
      org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "include/tail.jsp", out, false);
      out.write("\r\n");
      out.write("\r\n");
      out.write("</body>\r\n");
      out.write("</html>\r\n");
  • 会把相关语句翻译成Java代码

  • 相当于调用org.apache.jasper.runtime.JspRuntimeLibrary.include(request, response, "路径", out, false);

  • 此时将requestresponseout都传递给了被动态包含的页面中

  • 还可以传递一些参数

    • 需要写在静态包含的标签之间:

    • <jsp:include page="路径">
          <jsp:param key="key" value="value"/>
      </jsp:include>
      
    • 之后便可以在被包含进来的页面中,调用request.getParameter("key")获取键值对

  • 在实际的使用中,静态包含要比动态包含使用的多

jsp请求转发标签

之前的请求转发的实现方式为

request.getRequestDispatcher("路径").forward(request, response);

在jsp中也有专门的请求转发的标签

<jsp:forward page="路径"></jsp:forward>
  • 翻译后的Java代码为

  • if (true) {
         _jspx_page_context.forward("/");
         return;
    }
    
自定义类无法实例化异常

有概率出现此问题

image-20211002171724101

解决方法:文件-项目结构-工件-删掉当前有问题的web项目/模块,再重新新建一个:

  • 点击+,选择web应用程序:展开型-基于模块,重新添加一下删掉的模块

  • image-20211002172026426

  • 再进行编辑配置-部署-+号,选择工件,选择刚刚添加的工件

  • image-20211002172203722

傻逼CSDN耽误我时间!!!

无法使用out中的方法

文件-项目结构-模块-选择无法使用out中的方法的模块-依赖-+号-库(library)-应用程序服务器库-tomcat

image-20211002175416383

对于将查询到的结果放到一个表格中的解决方案

思路:先获取请求,在请求中取出数据,在服务器中进行对比查询,将查询到的结果使用response.setAttribute("key", List)放入到键值对中,在jsp页面,通过取出键值对中的信息进行在页面输出

监听器Listener

读音ˈlɪsənər,中文为听众,倾听者

是Java的三大组件之一,是Java EE的规范(接口)

作用:监听某种事物的变化,通过回调函数,反馈给客户端并做一些处理

相关知识

EL表达式

全称:Expression Language,中文为表达式语言,expression 中文为表达式、表达、表示,读音为ɪkˈspreʃn

作用:代替jsp页面中的表达式脚本,在jsp页面中输出,主要是输出域对象中的数据

<%--测试存在的值--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    request.setAttribute("key", "value");
%>
普通输出:<%=request.getAttribute("key")%> <br>
EL表达式输出:${key}<br>
</body>
</html>

结果为

普通输出:value
EL表达式输出:value

当普通的方式输出不存在的值时,会显示null,而EL表达式什么都不输出,如果输出null的话可能会有歧义,普通表达式也可以什么都不输出

此时可以写为

<%=request.getAttribute("不存在的值") == null ? "":request.getAttribute("不存在的值") %>

EL表达式对于4个域对象的搜索顺序

当一个jsp页面中,四个域对象都有相同的key

<%
    pageContext.setAttribute("key", "pageContext");
    request.setAttribute("key", "request");
    session.setAttribute("key", "session");
    application.setAttribute("key", "application");
%>
el表达式输出:${key}

搜索顺序是按照四个域对象的数据存取范围由小到大进行搜索,无论语句的位置,当多个域对象有相同的key时,默认都是使用数据存取范围最小的key

输出普通属性中的值

例如

package com.test;

import java.util.Arrays;
import java.util.HashMap;

public class Test {
    private String name;
    private HashMap<String, String> map;
    private int age;
    private int[] arr;

    @Override
    public String toString() {
        return "Test{" +
                "name='" + name + '\'' +
                ", map=" + map +
                ", age=" + age +
                ", arr=" + Arrays.toString(arr) +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public HashMap<String, String> getMap() {
        return map;
    }

    public void setMap(HashMap<String, String> map) {
        this.map = map;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int[] getArr() {
        return arr;
    }

    public void setArr(int[] arr) {
        this.arr = arr;
    }
}

jsp页面

<%@ page import="com.test.Test" %>
<%@ page import="java.util.HashMap" %><%--
  Created by IntelliJ IDEA.
  User: singx
  Date: 2021/10/5
  Time: 9:51
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    Test test = new Test();
    HashMap<String, String> map = new HashMap<>();
    map.put("key1", "value1");
    map.put("key2", "value2");
    map.put("key3", "value3");
    map.put("key4", "value4");
    test.setAge(99);
    test.setArr(new int[]{1, 2, 4, 5});
    test.setName("test");
    test.setMap(map);
//    将对象设置为一个参数,如果不设置为参数,则无法正常输出
    pageContext.setAttribute("test1", test);
%>
<%--作为参数进行输出--%>
el表达式输出:${test1}<br>
<%--从参数中,输出单个属性值--%>
el表达式输出单个属性:${test1.map}<br>
</body>
</html>

输出结果为

el表达式输出:Test{name='test', map={key1=value1, key2=value2, key3=value3, key4=value4}, age=99, arr=[1, 2, 4, 5]}
el表达式输出单个属性:{key1=value1, key2=value2, key3=value3, key4=value4}

对于所有的属性都是通过getXXX()方法进行获取到的,如果没有写相关的getXXX(),会抛出异常

对于List类型,可以通过取下标的方式进行获取数据,格式${key.List实例[下标]}

<%
    Test test = new Test();
    ArrayList<Double> list = new ArrayList<>();
    list.add(1111.88);
    list.add(222.88);
    list.add(3333.88);
    list.add(4444.88);
    test.setAge(99);
    test.setList(list);
//    将对象设置为一个参数,如果不设置为参数,则无法正常输出
    pageContext.setAttribute("test1", test);
%>
<%--作为参数进行输出--%>
el表达式输出ArrayList属性:${test1.list[1]}<br>

对于Map类型,也可以通过${key.Map实例.Map中的键}获取key

<%
    Test test = new Test();
    HashMap<String, String> map = new HashMap<>();
    map.put("key1", "value1");
    map.put("key2", "value2");
    map.put("key3", "value3");
    map.put("key4", "value4");
    test.setMap(map);
//    将对象设置为一个参数,如果不设置为参数,则无法正常输出
    pageContext.setAttribute("test1", test);
%>
<%--作为参数进行输出--%>
el表达式输出Map中的键属性:${test1.map.key1}<br>

运算

image-20211005110053356

image-20211005110120681

  1. 算术运算

    image-20211005110211121

empty运算

判断一个数据是否为空,如果为空输出true

为空的情况:

  1. 值为null
  2. 值为空串
  3. 值为Object类型数组,长度为0
  4. List集合,元素个数为0
  5. Map集合,元素个数为0

格式:${empty key}

<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.HashSet" %>
<%@ page import="java.util.HashMap" %><%--
  Created by IntelliJ IDEA.
  User: singx
  Date: 2021/10/5
  Time: 11:06
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
//    空值
    request.setAttribute("emptyNull", null);
//    长度为0的基本类型数组
    request.setAttribute("emptyArray", new int[0]);
//    长度为0的Object类型数组
    request.setAttribute("emptyObjectArray", new HashMap[0]);
//    空串
    request.setAttribute("emptyString", "");
//    长度为0的List集合
    request.setAttribute("emptyList", new ArrayList<>());
//    长度为0的Set集合
    request.setAttribute("emptySet", new HashSet<>());
//    长度为0的Map集合
    request.setAttribute("emptyMap", new HashMap<>());
%>
空值:${empty emptyNull}<br>
长度为0的基本类型数组:${empty emptyArray}<br>
长度为0的Object类型数组:${empty emptyObjectArray}<br>
空串:${empty emptyString}<br>
长度为0的List:${empty emptyList}<br>
长度为0的Set:${empty emptySet}<br>
长度为0的Map:${empty emptyMap}<br>
</body>
</html>
空值:true
长度为0的基本类型数组:false
长度为0的Object类型数组:true
空串:true
长度为0的List:true
长度为0的Set:true
长度为0的Map:true

.运算和[]运算

image-20211005112452975

map中也可以使用[],例如

<%
    HashMap<String, String> map = new HashMap<>();
    map.put("1", "value");
    request.setAttribute("map", map);
%>
${map["1"]}

EL中11个内置对象

scope,读音skəʊp,中文为范围

EL进行定义的,可以直接使用

image-20211005113839754

针对pageScoperequestScopesessionScopeapplicationScope的使用

<%--
  Created by IntelliJ IDEA.
  User: singx
  Date: 2021/10/5
  Time: 16:47
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    pageContext.setAttribute("key", "pageContext");
    request.setAttribute("key", "request");
    session.setAttribute("key", "session");
    application.setAttribute("key", "application");
%>
${pageScope.key}<br>
${requestScope.key}<br>
${sessionScope.key}<br>
${applicationScope.key}
</body>
</html>

解决了四个域中具有相同的键进行查询值的问题

pageContext的使用:

  • image-20211005194747265

  • <%--
      Created by IntelliJ IDEA.
      User: singx
      Date: 2021/10/5
      Time: 16:47
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <%
        pageContext.setAttribute("key", "pageContext");
        request.setAttribute("key", "request");
        session.setAttribute("key", "session");
        application.setAttribute("key", "application");
    %>
    <%--pageContext.request.scheme<=>request.getScheme()--%>
    协议:${pageContext.request.scheme}<br>
    IP地址:${pageContext.request.serverName}<br>
    服务器端口号:${pageContext.request.serverPort}<br>
    工程路径:${pageContext.request.contextPath}<br>
    请求方式:${pageContext.request.method}<br>
    客户端IP:${pageContext.request.remoteHost}<br>
    会话的ID:${pageContext.session.id}<br>
    
    </body>
    </html>
    
  • 具体的写法:pageContext.变量名.属性,属性相当于变量名.get属性,即写法中不用写get

param:获取请求的参数,该参数为Map类型:

所有单个参数:${param}<br>
<%--获取key为key1时的value--%>
参数key1:${param.key1}<br>

请求参数为?key1=value1&key2=value2

输出结果为

所有单个参数:{key1=value1, key2=value2}
参数key1:value1

paramValues:在请求的参数中,一个key可能有多个value,所以,可以通过该变量进行获取,输出的内容格式{key = [地址},地址是数组的地址,可以通过paramValues.key[下标]取出值

多个参数:${paramValues.key2[1]}<br>

请求参数为?key1=value1&key2=value2&key2=value3

结果为多个参数:value3

header:返回键值对,头部信息

${header}

Header:{sec-fetch-mode=navigate, sec-fetch-site=none, accept-language=zh-CN,zh;q=0.9, cookie=JSESSIONID=0C81B1BB28F1A6C56F9C0BE69A7FBDAE; Idea-35907095=2c39fe81-2ef8-43ca-9524-893dc2eec9de; _ga=GA1.1.935333769.1631151725, sec-fetch-user=?1, 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-ch-ua="Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99", sec-ch-ua-mobile=?0, sec-ch-ua-platform="Windows", host=localhost:8080, upgrade-insecure-requests=1, connection=keep-alive, cache-control=max-age=0, accept-encoding=gzip, deflate, br, user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36, sec-fetch-dest=document}
$(header.get("user-agent"))
<%--或者-->
header["user-agent"]

结果为Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36

headerValues:与paramValues用法一样

cookie:获取cookiecookie:${cookie}

initalParam:获取web.xml<context-param></context-param>中的键值对

JSTL标签库

全称:JSP Standard Tag Library即JSP标准标签库

主要用来替代代码的脚本

image-20211006180252217

使用:

  • 导入jstl的jar包,下载链接

  • 需要下载Impl:taglibs-standard-impl-1.2.5Spec:taglibs-standard-spec-1.2.5

  • image-20211006183204340

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

功能:

  • 在域中保存数据

    • <c:set scope="域" var="key" value="value"/>
      
    • 当域不填写时,默认相当于pageContext

    • image-20211006183457582

  • 单个判断标签

    • 没有不成立的语句,没有else

    • if标签:
      <c:if test="${el表达式的条件}">
          <p>成立执行这里,可以写标签</p>
      </c:if>
      
  • 多路判断标签,类似于if...else if...else

    • 其中有choosewhenotherwise

    • <c:choose>
          <c:when test="${el表达式条件}">
              <p>成立执行这里,可以写标签</p>
          </c:when>
          <c:when test="${el表达式条件}">
              <p>成立执行这里,可以写标签</p>
          </c:when>
          <c:otherwise>
              <p>以上都不成立执行这里,可以写标签</p>
          </c:otherwise>
      </c:choose>
      
    • 标签中不能写HTML注释,只能写JSP注释<%--注释--%>,否则会抛出异常

      <c:when test=${el表达式}></c:when>必须直接的写在<c:choose></c:choose>中,如果想要写在<otherwise></otherwise>中,则必须还要套一层<c:choose></c:choose>

    • <%--错误写法--%>
      <c:choose>
          <c:when test="${el表达式条件}">
              <p>成立执行这里,可以写标签</p>
          </c:when>
          <c:when test="${el表达式条件}">
              <p>成立执行这里,可以写标签</p>
          </c:when>
          <c:otherwise>
              <c:when test="${el表达式}">
                  <p>以上都不成立执行这里,可以写标签</p>
              </c:when>
          </c:otherwise>
      </c:choose>
      
      
      <%--正确写法--%>
      <c:choose>
          <c:when test="${el表达式条件}">
              <p>成立执行这里,可以写标签</p>
          </c:when>
          <c:when test="${el表达式条件}">
              <p>成立执行这里,可以写标签</p>
          </c:when>
          <c:otherwise>
              <c:choose>
                  <c:when test="${el表达式}">
                  	<p>以上都不成立执行这里,可以写标签</p>
              	</c:when>
              </c:choose>
          </c:otherwise>
      </c:choose>
      
  • <c:forEach/>标签

    • 作用:遍历输出

    • 对于普通的循环:输出1-10

      • <%--begin开始位置, end为结束的位置, var为变量名同时也为正在遍历的数据--%>
        <c:forEach begin="1" end="10" var="i">
            i =
            ${i} <br>
        </c:forEach>
        
      • i = 1
        i = 2
        i = 3
        i = 4
        i = 5
        i = 6
        i = 7
        i = 8
        i = 9
        i = 10
        
    • 遍历Object类型的数组,对于map类型,也可以使用这种方式进行遍历

      • <%
            request.setAttribute("Strings", new String[]{"abc", "ced", "fgh"});
        %>
        <%--相当于增强for循环,items为需要遍历的数组,i为一个取出其中值的变量,相当于forEach中:左侧的变量--%>
        <c:forEach var="i" items="${Strings}">
            i = ${i}<br>
        </c:forEach>
        
      • i = abc
        i = ced
        i = fgh
        
      • 也可以输出某个范围内的数据

      • beginend也可以省略,省略begin代表从开始位置0开始,省略end代表结束位置一直到末尾

      • <%
            request.setAttribute("Strings", new String[]{"abc", "ced", "fgh", "fgh", "fgh", "fgh", "fgh", "fgh", "fgh", "fgh"});
        %>
        <%--相当于增强for循环,items为需要遍历的数组,i为一个取出其中值的变量,相当于forEach中:左侧的变量,下端相当于从下标4的位置一致输出到下标7的位置的元素,也可以设置步长属性--%>
        <c:forEach var="i" items="${Strings}" begin="4" end="7">
            i = ${i}<br>
        </c:forEach>
        
      • 还有步长step属性

        • <c:forEach var="i" begin="1" end="100" step="8">
              i = ${i} <br>
          </c:forEach>
          
        • i = 1
          i = 9
          i = 17
          i = 25
          i = 33
          i = 41
          i = 49
          i = 57
          i = 65
          i = 73
          i = 81
          i = 89
          i = 97
          
    • varStatus属性

      • <c:forEach var="i" begin="1" end="100" step="8" varStatus="status">
            i = ${i} <br>
            status = ${status} <br>
        </c:forEach>
        
      • 输出的内容

      • i = 1
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 9
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 17
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 25
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 33
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 41
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 49
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 57
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 65
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 73
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 81
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 89
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        i = 97
        status = javax.servlet.jsp.jstl.core.LoopTagSupport$1Status@56dbf70c
        

        可见,varStatus是个对象,其中里边有以下的方法

        image-20211006211347981

文件的上传和下载

上传

需要符合:

  • 要有一个form标签,同时,method=post即必须是post请求
  • form标签中的encType属性的属性值必须为multipart/form-datamultipart中文为多部分,表示提交的数据以多段的形式拼接,然后以二进制流的形式发送给服务器
  • form标签中,需要使用<input type=file/>标签用来添加上传的文件
  • 编写服务器端的代码,处理上传的数据,使用servlet进行接收

因为get请求在地址栏中限制长度,所以要使用post请求

image-20211007212644168

在后端必须以流的形式接收,否则无法获取到里边的内容

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //获取一个流
    ServletInputStream inputStream = req.getInputStream();
    //new一个buffer数组
    byte[] buffer = new byte[1024000];
    //将读取的内容,放入到buffer数组中 
    int read = inputStream.read(buffer);
    //输出里边的内容
    System.out.println(new String(buffer, 0, read));
}

输出的内容格式f为

------WebKitFormBoundaryXKfrR0Kh6y0G3j21
Content-Disposition: form-data;键值

参数
------WebKitFormBoundaryXKfrR0Kh6y0G3j21
Content-Disposition: form-data;;键值

参数
------WebKitFormBoundaryXKfrR0Kh6y0G3j21--

此时需要做的是处理解析这些数据,可以使用第三方的jar包进行处理

需要两个:commons-fileupload.jar依赖于commons-io.jar,所以两个都需要引入

导入jar包后,可以使用ServletFileUpload类所提供的方法对上传的数据进行解析处理

方法含义
FileLoad.isMultipartContent(request)查看当前上传进来的数据是否是多段的格式,返回boolean类型
parseRequest(request)解析上传的数据,返回List<FileItem>类型

FileItem类:表示每一个表单项

步骤:

  • 首先先使用FileLoad.isMultipartContent(request)进行判断上传进来的数据是否是多段的格式,如果是,执行以下步骤

  • 实例化一个FileItemFactory文件工厂类对象,FileItemFactory是一个接口,可以使用它的一个实现类DiskFileItemFactory进行创建

  • 使用一个FileItemFactory文件工厂类对象,实例化一个ServletFileUpload对象,根据API文档描述,需要提供一个工程类来指定存储方式

  • 通过ServletFileUpload对象的parseRequest(request)方法,获取一个List<FileItem> fileItems,可以通过遍历fileItems,对每个元素进行处理,因此,可以使用每个元素的isFormField()方法判断当前的元素是普通的表单元素还是文件

    • 如果是普通的表单元素,可以输出其keyvalue

      • 通过getFieldName()获取key,即在原<input>标签中的name属性值
      • 通过getString("字符集编码")获取具体的value
    • 如果是文件,则可以将这个文件写入到本地

      • 通过getFieldName()获取key,即在原<input>标签中的name属性值

      • 通过getName()获取文件名

      • 通过write(new File("路径" + 文件名))把文件写入到本地自定义的路径中

      • //        上传后的文件名如果是中文可能会乱码,可以提前设置一下请求的字符集编码
            req.setCharacterEncoding("UTF-8");
        

全部代码

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //        上传后的文件名如果是中文可能会乱码,可以提前设置一下请求的字符集编码
        req.setCharacterEncoding("UTF-8");
//        判断当前上传的数据格式是否是多段的格式,返回布尔类型
        if (FileUpload.isMultipartContent(req)) {
//            创建FileItemFactory实现类
            FileItemFactory fileItemFactory = new DiskFileItemFactory();
//            创建解析上传数据的工具类
            ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
//            解析上传数据,得到每个表单项FileItem
            List<FileItem> fileItems;
            try {
                fileItems = servletFileUpload.parseRequest(req);
//                通过循环遍历,判断每个fileItems中的元素是一个普通的表单项还是一个上传的文件
                for (FileItem f : fileItems){
//                    true代表是普通表单项
                    if(f.isFormField()){
//                        对于普通表单项,可以尝试着输出一下键和值,为了解决输出值时可能出现的乱码问题,可以先将字符集编码设置为UTF-8
                        System.out.println("普通表单项,其中key = " + f.getFieldName() + ", value = " + f.getString("UTF-8"));
                        System.out.println("f = " + f);
                    }else{ //false代表上传的文件
                        System.out.println("上传的文件,其中key = " + f.getFieldName() + ",文件名为" + f.getName());
//                        写入文件
                        f.write(new File("D:\\Java IO\\upload\\" + f.getName()));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            resp.getWriter().println("不是多段的");
            return;
        }
    }

下载

步骤:

  1. 获取下载的文件名
  2. 读取文件的内容
  3. 把下载的文件回传到客户端
  4. 回传前,通过响应头告诉客户端返回的数据类型
  5. 通过响应头,告诉客户端收到的文件用于下载使用

请求下载的页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Download</title>
</head>
<body>
<h1>下载</h1>
<form action="/ress/download" method="post" target="_blank">
    文件名:<input type="text" name="filename">
    <br>
    <button type="submit">提交</button>
</form>
</body>
</html>

处理下载的代码

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=UTF-8");
//        1. 获取下载的文件名
        String filename = req.getParameter("filename");
        System.out.println(filename);
        if (filename == null){
            resp.getWriter().println("未找到文件");
            return;
        }
//        2. 通过响应头告诉客户端返回的数据类型
        ServletContext servletContext = getServletContext();
        //获取MIME 类型,MIME是一种标准,用来表示文档、文件或字节流的性质和格式
        String mimeType = servletContext.getMimeType("file/" + filename);
        System.out.println("文件类型为:" + mimeType);
        //设置响应头
        resp.setContentType(mimeType);
//        3. 通过响应头,告诉客户端收到的文件用于下载使用,同时也指定了文件名
        resp.setHeader("Content-Disposition", "attachment;fileName=" + filename);
//        4.获取文件流和输出流
        //通过文件的路径获取一个输入流
        InputStream resourceAsStream = servletContext.getResourceAsStream("file/" + filename);
        //输出流,获取响应的输出流
        ServletOutputStream outputStream = resp.getOutputStream();
//        5. 把下载的文件回传到客户端,使用第三方jar包进行操作
        IOUtils.copy(resourceAsStream, outputStream);
    }

对于第18行设置响应头的内容,可参考,Content-Disposition

当指定的文件名为中文时,可能会发生文件名乱码的情况,对于chrome浏览器,需要对url进行编码resp.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(filename, "UTF-8"));

对于base标签使用的一个问题

如果base标签中,直接填写的内容为http://localhost:port/XXXX那么,如果使用其他的IP地址进行访问时, 当前页面的地址也是localhost,这会导致使用相对路径引入的css等资源失效

可以尝试着动态获取

<%
//    解决指定地址后,css等资源丢失的问题
//    获取到协议://服务器IP地址:端口号/工程路径+自定义的其他路径
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/user/";
    request.setAttribute("basePath", basePath);
%>
<base href="<%=basePath%>">

对于相同的模块的功能用同一个servlet进行实现

可以设置一个隐藏的表单域

<input type="hidden" name="name" value="value">

在多个页面中,赋予隐藏的表单域不同的value属性进而进行判断要实现的功能

如果这么操作,会有大量的if...else if...else,这些都可以通过反射给优化掉

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        获取当前的Class
        Class clazz = this.getClass();
//        获取相应的方法,之后关于用户操作的方法名必须遵循xxxUser的格式
        try {
            Method method = clazz.getDeclaredMethod(req.getParameter("flag"),  HttpServletRequest.class, HttpServletResponse.class);
//            用当前的对象尝试调用方法
            try {
                method.invoke(this, req, resp);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }


        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }

BeanUtils工具类

可以导入此工具的第三方类,在一个项目中,可能有许多的getXXX()方法和new XXX()对象,可以使用这个工具类,注入到JavaBean中,BeanUtils工具类依赖于Commons-loggingCommons-collections(版本3.2.2)

User u = new User();
try {
    BeanUtils.populate(u, req.getParameterMap());
    System.out.println(u);
} catch (IllegalAccessException | InvocationTargetException e) {
    e.printStackTrace();
}

populate读音ˈpɑːpjuleɪt,中文:填充、居住

User{id=null, username='admin', password='jVbMCDZY4gxT8D', email='null'}

可以手动写一个工具类,用请求的参数中的值赋给一个对象中对应的属性

package utils;

import org.apache.commons.beanutils.BeanUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.InvocationTargetException;

public class WebUtils {
    /**
     * 使用BeanUtils填充值
     * @param req 请求,用来将其中的参数转换为map
     * @param o 需要填充的对象
     */
    public static void copyToBean(HttpServletRequest req, Object o){
        try {
            BeanUtils.populate(o, req.getParameterMap());
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

需要保证被填充的对象中,有相应的setXXX()方法与请求的key相同

或者,此时调用时,可以填写request.getPatameterMap()

public static void copyToBean(Map m, Object o){
    try {
        BeanUtils.populate(o, m);
    } catch (IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

还可以更简洁

public static Object copyToBean(Map m, Object o){
    try {
        BeanUtils.populate(o, m);
    } catch (IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    return o;
}

//调用
User user = (User) WebUtils.copyToBean(req.getParameterMap(), new User());

最后的方案-使用泛型

public static <T> T copyToBean(Map m, T o){
    try {
        BeanUtils.populate(o, m);
    } catch (IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    return o;
}
//调用
User user = WebUtils.copyToBean(req.getParameterMap(), new User());

MVC

全称:Model模型、View视图、Controller控制器

Model模型:将业务逻辑相关的数据封装到具体的JavaBean类中,其中不掺杂任何与数据处理相关的代码

View视图:只负责数据和界面的显示,不接受任何与数据显示无关的代码

Controller控制器:负责接收请求,调用业务层代码请求,派发页面,作为一个调度者,通常为servlet,负责转发到某个页面或者重定向到某个页面,读音kənˈtroʊlər

是一种思想,将代码拆分成组件,单独开发,组合使用,主要的目的还是为了解耦合

image-20211009122021946

分页

在实际的开发中,一个页面不可能直接显示全部的数据,需要使用分页,在一页中只显示部分数据

分页的模型:

  • 当前页码

  • 总页码

    • 总的记录数=总的数据记录数/每页的数量的上取整
  • 总的数据的记录条数

    • select count(1) from 表名;
      
  • 每页显示的数量

  • 当前页的数据

    • select 字段名 * from 表名 limit 开始位置, 数量;
      
    • 开始位置:当前的页码 - 1 * 每页的数量

服务器通知客户端保存数据(键值对)的一种方式,客户端有了Cookie之后,每次请求都要发给服务器,每个Cookie的大小不能超过4kb

创建

  1. 实例化一个Cookie对象
  2. response响应中添加Cookie对象
protected void createCookie(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        创建cookie对象
        Cookie cookie = new Cookie("key", "value");
//        将cookie添加到响应中
        resp.addCookie(cookie);
        resp.getWriter().println("cookie创建成功");
    }

image-20211014103102253

可以通过开发者工具查询到自动获取的cookie

在响应头中,可以看到

image-20211014103402285

创建过程:

image-20211014103624476

cookie一次可以创建多个,客户端可以同时收到多个

获取cookie

可以直接调用resquest.getCookies()方法,返回Cookie[]

遍历这个数组,对这个数组中的元素使用getName()获取keygetValue()获取value

protected void getCookie(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Cookie[] cookies = req.getCookies();
    for(Cookie c : cookies){
        resp.getWriter().println("[" + "key = " + c.getName() + ", value = " + c.getValue() + "]");
    }
}

如果想要获取指定keycookie,没有别的办法,只能挨个遍历,并且使用"key".equals(cookie.getName())进行判断

在实际的开发中,通常写一个utils类,单独写一个针对指定的key获取cookie

package utils;

import javax.servlet.http.Cookie;

/**
 * 获取cookie
 */
public class CookieUtils {
    public static Cookie findCookie(String key, Cookie[] c){
        if (key == null || c == null || c.length == 0){
            return null;
        }
        for(Cookie c1 : c){
            if(key.equals(c1.getName())){
                return c1;
            }
        }
        return null;
    }
}

cookie的值的修改

方式1:

  1. 实例化一个cookie
  2. 在构造器中,new cookie(需要修改的key, 新的value)
  3. 将新的cookie添加到response

方式2:

  1. 调用getCookie()获取一个Cookie[]
  2. 遍历Cookie[]中的每个元素,可以调用setValue(String value)进行修改值
  3. 将修改后的cookie添加到response
protected void updateCookie(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    if (req.getParameter("key") == null || "".equals(req.getParameter("key"))) {
        resp.getWriter().write("key不存在<br>");
    }else{
        //通过key获取key,需要key存在
        Cookie cookie = CookieUtils.findCookie(req.getParameter("key"), req.getCookies());
        if (cookie == null){
            resp.getWriter().write("key不存在<br>");
            return ;
        }
        cookie.setValue(req.getParameter("value"));
        //再次添加到response中
        resp.addCookie(cookie);
        resp.getWriter().write("修改成功<br>");
    }
}

cookie中不允许存储中文

cookie生命周期

控制cookie什么时候销毁/删除

可以通过每个cookiesetMaxAge(int value)方法设置

设置后,还需要将设置后的内容再次添加到response

value为正值,表示value秒后过期

value为负值,表示不会持久存储,即浏览器退出时清除,默认值为-1

value为0,表示立即清除

protected void setCookieLife(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Cookie[] cookies = req.getCookies();
    if (req.getParameter("key") == null || "".equals(req.getParameter("key")) || req.getParameter("time") == null || "".equals(req.getParameter("time"))) {
        resp.getWriter().write("key或者存活时间不存在<br>");
    } else {
        Cookie cookie = CookieUtils.findCookie(req.getParameter("key"), cookies);
        if (cookie == null) {
            resp.getWriter().write("key不存在<br>");
            return;
        }
        //设置存活时间,前提是时间存在并且key存在
        cookie.setMaxAge(Integer.parseInt(req.getParameter("time")));
        resp.addCookie(cookie);
        resp.getWriter().write("设置成功," + cookie.getName() + "的存活时间为" + cookie.getMaxAge() + "<br>");
    }
}

设置cookie的时间后,响应标头中的内容也发生了改变

image-20211014123423934

当cookie时间设置为0时,响应标头中的内容为Set-Cookie:key=value666; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT

有效路径path

可以过滤那些内容发给服务器,那些内容不发,是通过请求的地址确定的

image-20211014124120726

调用每个cookie元素的setPath(String path)设置路径

例如

Cookie cookie = new Cookie("key", "value666");
Cookie cookie2 = new Cookie("key2", "value2");
Cookie cookie3 = new Cookie("key3", "value3");
cookie3.setValue("value9999");
cookie.setPath("/ress/cookie/a");
cookie2.setPath("/ress/cookie/b");

设置3个cookie,为第一个和第二个指定路径,此时,浏览器使用默认的地址访问

所有的cookie都可以得到,但只能使用cookie3

  • 只能使用的cookie:
    • image-20211014180425012
  • 得到的响应头为:
    • 此时包含不能使用的cookie1和cookie2
    • image-20211014180538594

保存用户的cookie

功能:用户提交表单后,将用户提交的内容使用cookie保存起来,下次再打开这个页面时,表单中的内容依然存在

<form action="/ress/cookie">
    <input type="hidden" name="flag" value="saveCookie">
    用户名:<input type="text" name="username" value="${cookie.username.value}">
    <button type="submit">提交</button>
</form>

其中,第3行的EL表达式中的cookie对象是EL表达式的11个内置对象中的一个,是一个Map<String, Cookie>

protected void saveCookie(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String username = req.getParameter("username");
    Cookie cookie = new Cookie("username", username);
    cookie.setMaxAge(300);
    resp.addCookie(cookie);
    resp.getWriter().write("提交成功,key = " + cookie.getName() + ", value = " + cookie.getValue() + "<br>");
}

Session会话

Session是一个接口,具体是HttpSession

是用来维护客户端和服务器之间关联的技术,每个客户但都有自己的会话,通常用来保存用户登录后的信息

创建和获取Session

获取

req.getSession();

第一次调用这个方法是创建Session会话,之后的调用都是获取前边创建好的Session会话对象

判断一个Session是刚创建出来的还是直接获取的:调用session的isNew()方法

例如:req.getSession().isNew()方法

每个会话都已自己唯一的id值

设置/获取值

session设置值:

  • 获取一个sessionrequest.getSession()
  • 调用获取到的sessionsetAttribute(String key, Object value)

session获取值:

  • 获取一个sessionrequest.getSession()
  • 调用获取到的sessiongetAttribute(String key)
protected void sessionSetAttribute(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    req.getSession().setAttribute(req.getParameter("key"), req.getParameter("value"));
    resp.getWriter().write("已将数据插入到session中,数据key = " + req.getParameter("key") + "value = " + req.getParameter("value") + "<br>");
}

protected void sessionGetAttribute(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    Object value = req.getSession().getAttribute(req.getParameter("key"));
    resp.getWriter().write("key = " + req.getParameter("key") + ", value = " + value + "<br>");
}

Session生命周期控制

设置生命周期:

调用Sessionsessions.setMaxInactiveInterval(值);方法

Inactive中文为不活跃的,读音为inˈaktiv

Interval中文为间隔,读音为ˈɪntərvl

时长以秒为单位,默认的时长为1800秒,也就是30分钟 ,该项再Tomcat服务器中默认为30分钟

获取生命周期:

调用Sessionsessions.getMaxInactiveInterval(值);方法

超时的概念

假设设置的超时为3秒,如果获取了session后,在3秒内如果再次获取使用session,那么此时的超时又会变为3秒,也就是说,两次请求的最大间隔时长,一旦超过时长,session就会销毁

image-20211014221607861

值为正数,代表有超时时长,时长为设置的时长

值为负数,代表永不超时

设置为0无法让其立即失效,可以调用req.getSession().invalidate();让其马上无效,可以让其作为注销的功能

invalidate中文为作废,读音为inˈvaləˌdāt

原理

  1. 在没有Cookie时向服务器发送请求,服务器会调用getSession()方法创建会话对象
  2. 服务器内容中会有多个session,此时在响应头中会返回cookiecookie中只有key永远是JESSIONIDvaluesessionid
  3. 每次都是通过响应把新创建的session值返回到客户端
  4. 每次请求都会把sessionidcookie形式发给服务器,服务器在获取session时,每次都是通过request.getSession()获取session,获取时,通过id在内存中查找符合的session 并返回 ,因为内存中可能有许多session,所以只能通过id进行匹配查找
  5. 如果删除掉所有的cookie后,此时再发送请求时,会找不到之前的session,所以只能新创建一个新的session,也就是再添加一个key = JESSIONIDvalue = id,然后继续重复2-3-4步

和session有关的cookie,它的MaxAge此时为-1,所以说为-1MaxAge在Chrome中,MaxAge会显示为会话(session)

image-20211014225052565

image-20211014224507297

表单重复提交的问题

  • 提交完表单后,服务器使用请求进行页面跳转,此时如果用户按下刷新,就会再次发送一个请求,表单又会重复的提交到服务器,此时使用重定向跳转可以解决问题

    • resp.sendRedirect(req.getContextPath() + "工程路径下的其他页面");
      
  • 用户正常提交表单,但由于网络延迟等原因,用户多点了提交按钮,表单又会重复的提交到服务器

  • 用户正常提交表单,但按了浏览器的后退,在后退页面又重新提交,表单又会重复的提交到服务器

验证码

为解决表单重复提交的问题,特此引入了验证码

  • 用户在提交表单时,给表单随机生成一个字符串作为验证码
  • 验证码要保存到Session域中
  • 将验证码生成为图片显示在表单中

image-20211016114228275

可以使用Google的:Kaptcha

使用:

  • 配置web.xml,在jar包中,有servlet类,引入即可image-20211016163440157

  • 配置好之后,直接访问配置好的servlet就可以直接访问到图片,并且验证码也会放到session域中

  • 在这个文件中,有一些常量,KAPTCHA_SESSION_KEY代表存放在session中的key,可以通过getAttrbute(KAPTCHA_SESSION_KEY)获取valueimage-20211016170238857

  • 获取后,可以接着进行删除,防止验证码使用第二次.

  • 使用

    package server;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    import static com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY;
    
    public class TestServlet extends BaseServlet{
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.setContentType("text/html;charset=UTF-8");
    //        获取写入到session中的验证码的value
            String token = (String) req.getSession().getAttribute(KAPTCHA_SESSION_KEY);
    //        删除掉写入到session中的验证码的value,防止验证码使用第二次
            req.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
    //        获取表单中提交的验证码
            String code = req.getParameter("code");
    //        和session域中的验证码进行对比
            if (token != null && token.equals(code)){
                resp.getWriter().write("保存到数据库中");
            }else{
                resp.getWriter().write("验证码不正确");
            }
        }
    }
    
  • <%--
      Created by IntelliJ IDEA.
      User: singx
      Date: 2021/10/16
      Time: 16:40
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <form action="/ress/code">
        用户名:<input type="text" name="username"> <br>
        验证码: <input type="text" name="code"> <img width="100px" height="25px" src="/ress/img.jpg" alt="验证码">
        <br>
        <button type="submit">提交</button>
    </form>
    </body>
    </html>
    

刷新验证码

点击图片刷新

为图片绑定一个单击事件即可

code.click(function () {
    this.src = "/bookf/code.jpg";
})
<img width="100px" height="25px" src="/book/code.jpg" alt="验证码" id="code">

Filter过滤器

filter中文为过滤/筛选 读音为ˈfiltər,是Java Web的三大组件之一,是一个接口,也是Java EE的一个规范,作用是拦截请求,过滤响应

应用场景:

  • 权限检查
  • 事务操作
  • 等等...

权限管理

例如某个目录下,仅允许登录后的用户访问,此时可以用到Filter过滤器

大概的一个流程

image-20211017095114796

让一个类实现Filter接口,Filter接口可能有多个,需要实现的是import javax.servlet.Filter

主要是doFilter()方法,这个方法做权限检查

//AdminFilter.java
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;

public class AdminFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

//    做权限检查的方法
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//        需要类型转换,servletRequest中没有获取session的方法
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpSession session = req.getSession();
//        获取value
        String username = (String) session.getAttribute("username");
//        如果为空,代表没有这个参数,此时需要转发到另一个页面
        if(username == null){
            servletRequest.getRequestDispatcher("/login.jsp").forward(servletRequest, servletResponse);
        }else{
//            放行
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    @Override
    public void destroy() {

    }
}

以上第26行的代码一定要写,如果不写就不会放行!

还需要配置一个xmlxml的格式和之前的servlet类似,功能不同,功能是做拦截的

    <filter>
        <!--    给filter起一个名字-->
        <filter-name>AdminFilter</filter-name>
        <!--        指定一个类-->
        <filter-class>AdminFilter</filter-class>
    </filter>
	<!--    映射地址-->
    <filter-mapping>
        <filter-name>AdminFilter</filter-name>
	<!--        /代表工程路径,即IP:port/工程路径/,而*代表这个路径下的全部内容-->
        <url-pattern>/admin/*</url-pattern>
    </filter-mapping>

生命周期

Filter声明周期所包含的方法:

  • 构造器方法
  • init()初始化方法
  • doFilter()过滤方法
  • destroy()销毁方法

前两步在Web工程启动时执行,也就是Filter已经创建了

第三步在每次拦截到请求就会执行

第四步在停止Web工程的时候执行

FilterConfig类

见名知义,是Filter的配置文件类

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    System.out.println("filterConfig.getFilterName() = " + filterConfig.getFilterName());
    System.out.println("filterConfig.getInitParameterNames() = " + filterConfig.getInitParameterNames());
    System.out.println("filterConfig.getInitParameter(\"key\") = " + filterConfig.getInitParameter("key"));
}

配置文件如下

<filter>
        <!--    给filter起一个名字-->
        <filter-name>AdminFilter</filter-name>
        <!--        指定一个类-->
        <filter-class>AdminFilter</filter-class>
        <init-param>
            <param-name>key</param-name>
            <param-value>value</param-value>
        </init-param>
    </filter>
<!--    映射地址-->
    <filter-mapping>
        <filter-name>AdminFilter</filter-name>
<!--        /代表工程路径,即IP:port/工程路径/,而*代表这个路径下的全部内容-->
        <url-pattern>/admin/*</url-pattern>
    </filter-mapping>

FilterChain

chain中文为链条/通路,读音为tʃeɪn

该类出现在doFilter()方法的形式参数中

多个Filter执行的流程

doFilter()方法的作用

  • 如果有下一个Filter过滤器,那就向下执行
  • 如果没有,执行后边的资源

image-20211017105519339

如果多个Filter都指向了同一个需要过滤的地址,那么执行的顺序类似域上图

例子:

//Filter1.java
1public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    System.out.println("Filter1前置代码");
    filterChain.doFilter(servletRequest, servletResponse);
    System.out.println("Filter1后置代码");
}
//Filter2.java
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    System.out.println("Filter2前置代码");
    filterChain.doFilter(servletRequest, servletResponse);
    System.out.println("Filter2后置代码");
}

web.xml配置文件

<filter>
    <filter-name>Filter1</filter-name>
    <filter-class>Filter1</filter-class>
</filter>
<filter-mapping>
    <filter-name>Filter1</filter-name>
    <url-pattern>/test/*</url-pattern>
</filter-mapping>
<filter>
    <filter-name>Filter2</filter-name>
    <filter-class>Filter2</filter-class>
</filter>
<filter-mapping>
    <filter-name>Filter2</filter-name>
    <url-pattern>/test/*</url-pattern>
</filter-mapping>

访问需要过滤的地址后,输出的内容为:

Filter1前置代码
Filter2前置代码
Filter2后置代码
Filter1后置代码

如果将Filter2.javafilterChain.doFilter()方法调用去掉(第4行),那么输出内容为:

Filter1前置代码
Filter1后置代码

此时,Filter2.java中的doFilter()方法不会执行

  • 在有多个Filer过滤器时,它们的执行顺序由所在的Web.xml文件中的顺序决定的

  • 多个Filter默认都在同一个线程中

  • 多个Filer过滤器的请求request都是一样的

拦截路径的匹配

有3种匹配方法:

  • 精准匹配
    • <url-pattern>/路径/文件名</url-pattern>
    • 例如<url-pattern>/test/abc.html</url-pattern>
      • 代表路径匹配为http://IP:port/工程路径/test/abc.html
  • 目录匹配
    • <url-pattern>/路径/*</url-pattern>
      • 代表路径匹配为http://IP:port/工程路径/test/,即这个路径下的所有文件
  • 后缀名匹配
    • <url-pattern>*.html</url-pattern>
      • 代表匹配所有以.html结尾的文件
      • 不要加/!!!

配置错误页面

为特定的响应状态码进行配置跳转的页面,比如说抛出异常的500状态码

当抛出异常后,用户可能看不懂异常的内容,所以可以给专门的错误码配置一个页面

需要在web.xml中设置

<error-page>
    <error-code>错误码</error-code>
    <location>所跳转的文件</location>
</error-page>

例如tomcat默认的404是这样的

image-20211017113632282

可以自定义一个404页面,进行替换

web.xml

<error-page>
    <error-code>404</error-code>
    <location>/404.html</location>
</error-page>

404.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>
    404
</h1>
</body>
</html>

JSON

JSON全称为JavaScript Object Notation,是一种轻量级的数据交换格式

Notation中文为符号,读音为noʊˈteɪʃn

轻量级是与xml进行比较的,也是客户端和服务器之间的数据交换格式

以键值对组成,每个键由引号引起来,键和值由:进行分割,多组键和值之间用,进行分割

JSON本身是一个对象

在Javascript中使用JSON

<script type="text/javascript">
    var json = {
        "integer" : 12,
        "string" : "hello",
        "boolean" : true,
        "array" : [1, false, "world"],
        "json" : {
            "key" : "jsons"
        }
    }
    window.onload = function () {
        alert(json);
    }
</script>

获取值:json变量名.键

json中的值可以是整型/数组/字符串/浮点型/json

json的存在方式有两种,一种是字符串的形式,另一种是对象的形式

这两种方式可以互相转换:

  • json转字符串:
    • image-20211017153556011
    • JSON.stringify(对象)
  • 字符串转json:
    • image-20211017153634873
    • JSON.parse(字符串)

在操作JSON中的数据时,需要使用JSON对象

在客户端和服务器传递数据时,使用JSON字符串

在Java中使用JSON

需要第三方jar包,可以使用Google的GSON.jar

  • JavaBean和JSON的转换

    • public static void main(String[] args) {
          Cat cat = new Cat("cat", 99, true);
          System.out.println("cat = " + cat);
          Gson gson = new Gson();
          //JavaBean转json
          System.out.println("gson.toJson(cat) = " + gson.toJson(cat));
      
          //自定义一个json字符串
          String json = """
                  {"name":"dog","age":88,"sex":false,"map":{"key666":"value"}}
                  """;
          //json转JavaBean,第一个参数为json字符串,第二个参数为需要转换的类型
          Cat cat2 = gson.fromJson(json, Cat.class);
          System.out.println(cat2);
      }
      
    • 结果:
      
      cat = Cat{name='cat', age=99, sex=true, map={key2=value2, key3=value3, key4=value4, key=value}}
      gson.toJson(cat) = {"name":"cat","age":99,"sex":true,"map":{"key2":"value2","key3":"value3","key4":"value4","key":"value"}}
      Cat{name='dog', age=88, sex=false, map={key666=value}}
      
  • List和JSON转换

    • 对于list转json,使用上例的方法即可

    • 对于json转list,如果还使用上例的方法,那么只能转换为Object类型的

      • 在GSON中,官方有提供的一个reflect包,这个包中有TypeToken类,可以新建一个类继承这个类,泛型中填写ArrayList<类型>

        • image-20211017163214878
      • package com.test;
        
        import com.google.gson.Gson;
        import com.google.gson.reflect.TypeToken;
        
        import java.util.ArrayList;
        
        public class Test2 {
            public static void main(String[] args) {
                ArrayList<Cat> list = new ArrayList<>();
                list.add(new Cat("猫",88, false));
                list.add(new Cat("gou",288, true));
                list.add(new Cat("addf",828, false));
                list.add(new Cat("dff",828, true));
                list.add(new Cat("dfdf",838, false));
                System.out.println("list = " + list);
                Gson gson = new Gson();
                //list转json,结果是一个[每个元素]
                System.out.println("gson.toJson(list) = " + gson.toJson(list));
                String json = gson.toJson(list);
                //将json转换成List,此种方法只能转换成Object类型的
                ArrayList arrayList = gson.fromJson(json, ArrayList.class);
                System.out.println(arrayList);
        
        //        会抛出异常!!
        //        Object ccc = (Cat)arrayList.get(0);
        //        System.out.println(ccc);
        
        
                //这种方式创建出来的JavaBean最正常
                ArrayList<Cat> arrayList2 = gson.fromJson(json, new ArrayListType().getType());
                System.out.println(arrayList2);
                System.out.println("arrayList2.get(0).map = " + arrayList2.get(0).age);
            }
        }
        
        class ArrayListType extends TypeToken<ArrayList<Cat>>{
        
        }
        
  • Map和JSON转换,和List类似

    • package com.test;
      
      import com.google.gson.Gson;
      import com.google.gson.reflect.TypeToken;
      
      import java.util.ArrayList;
      import java.util.HashMap;
      
      public class Test2 {
          public static void main(String[] args) {
              HashMap<String, String> map = new HashMap<>();
              map.put("key", "value");
              map.put("key2", "value2");
              map.put("key3", "value3");
              map.put("key4", "value4");
              Gson json = new Gson();
      //        map转json
              String jsonString = json.toJson(map);
              System.out.println("jsonString = " + jsonString);
      //        json转map,此种方法和List类似,只能转换为Object类型的
              System.out.println(json.fromJson(jsonString, HashMap.class));
      //        和List一样的的解决方法,新建一个类,继承TypeToken
              HashMap<String, String> hashMap = json.fromJson(jsonString, new MapType().getType());
              System.out.println(hashMap.get("key2").length());
          }
      }
      
      class MapType extends TypeToken<HashMap<String, String>>{
      
      }
      

      也可以使用匿名实现类,上边第28行使用一个新类进行继承的,未免过于浪费,可以使用匿名实现类,直接将23行代码改写为

      HashMap<String, String> hashMap = json.fromJson(jsonString, new TypeToken<HashMap<String, String>>(){}.getType());
      

AJAX

Asynchronous JavaScript And XML(异步JavaScript 和 XML)

Asynchronous读音eɪˈsɪŋkrənəs中文为异步,创建交互式网页应用开发的网页开发技术,是浏览器异步发起请求,局部刷新的页面技术

局部刷新:浏览器页面没有刷新,而内容改变了,浏览器地址也没有变化,也不会舍弃页面中原有的内容

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script type="text/javascript">
        function ajax() {
            // 创建一个XMLHttpRequest
            var xmlhttprequest = new XMLHttpRequest();
            //设置请求的参数,主要有 请求的方法,请求的地址,是否是异步的 默认为true
            xmlhttprequest.open("GET", "http://localhost:8080/ress/ajax?flag=ajax", true);
            // 将请求发给服务器
            xmlhttprequest.send()
        }
    </script>
</head>
<body>
<button type="submit" id="ajax" onclick="ajax()">ajax</button>
</body>
</html>
public class AjaxServlet extends BaseServlet{
    protected void ajax(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Ajax请求过来了");
    }
}

点击按钮后,输出Ajax请求过来了


Ajax请求效果:

image-20211017174512042

<html>
<head>
    <title>Title</title>
    <script type="text/javascript">
        function ajax() {
            // 创建一个XMLHttpRequest
            var xmlhttprequest = new XMLHttpRequest();
            //设置请求的参数,主要有 请求的方法,请求的地址,是否是异步的 默认为true
            xmlhttprequest.open("GET", "http://localhost:8080/ress/ajax?flag=ajax", true);
            xmlhttprequest.onreadystatechange = function () {
                // 判断状态码,只有符合时才进行相关操作
                // readyState
                // 0: 请求未初始化
                // 1: 服务器连接已建立
                // 2: 请求已接收
                // 3: 正在处理请求
                // 4: 请求已完成且响应已就绪
                if (xmlhttprequest.readyState == 4 && xmlhttprequest.status == 200){
                    alert(xmlhttprequest.responseText);
                }
            }
            // 将请求发给服务器
            xmlhttprequest.send()
        }
    </script>
</head>
<body>
<button type="button" id="ajax" onclick="ajax()">ajax</button>
</body>
</html>

把内容写入到响应中,此时输出的内容是响应的内容

protected void ajax(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Ajax请求过来了");
        HashMap map = new HashMap<>();
        map.put("ke1", "value1");
        map.put("ke2", "value2");
        map.put("ke3", "value3");
        map.put("ke4", "value4");
//        将map转为json
        Gson json = new Gson();
        String jsonString = json.toJson(map);
//        将json写入到响应中
        resp.getWriter().write(jsonString);
    }

JQuery中的AJAX

使用JQuery实现

{
    url: "请求地址",
    data: "请求参数",
    type: "请求方式",
    success: function (date) {
        请求成功执行的函数
        alert("服务器返回的数据:" + date)
    },
    dataType: "返回的数据形式,通常为text xml json对象 "
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#button").click(function () {
                $.ajax({
                        url: "http://localhost:8080/ress/ajax",
                        data: "flag=ajax",
                        type: "GET",
                        success: function (date) {
                            alert("服务器返回的数据:" + date)
                        },
                        dataType: "text"
                    }
                )
            })
        })
    </script>
</head>
<body>
<button type="button" id="button">按钮</button>
</body>
</html>

JQuery中的get/post方法

用法与前边的大致一样,差别在于第10行和13行,无需再填写type属性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#button").click(function () {
                $.get或者post({
                        url: "http://localhost:8080/ress/ajax",
                        data: "flag=ajax",
                        success: function (date) {
                            alert("服务器返回的数据:" + date)
                        },
                        dataType: "text"
                    }
                )
            })
        })
    </script>
</head>
<body>
<button type="button" id="button">按钮</button>
</body>
</html>

getJSON

获取响应的内容为json的时候用的

    <script type="text/javascript">
        $(function () {
            $("#button").click(function () {
                $.getJSON({
                        url: "http://localhost:8080/ress/ajax",
                        data: "flag=ajax",
                        success: function (date) {
                            alert("key1" + date.key1)
                        }
                    }
                )
            })
        })
    </script>

将表单内容以AJAX提交

表单可以序列化的

$(form标签的选择器).serialize()方法可以把表达序列化,序列化效果就是将表单中的内容提取出来

image-20211017202048226

i8n国际化

指同一个网站支持不同的语言

实际用处不多

Q.E.D.


念念不忘,必有回响。