JavaWeb

JavaWeb

  • 01_Web开发基础
    • 一、常见软件系统体系结构
      • 1.1、C/S架构
      • 1.2、B/S架构
    • 二、Web资源
      • 2.1、静态资源
      • 2.2、动态资源
    • 三、Web服务器
      • 3.1、常见Web服务器
      • 3.2、Tomcat服务器
        • 3.2.1、Tomcat安装
          • 3.2.1.1、下载
          • 3.2.1.2、解压安装
          • 3.2.1.3、Tomcat目录结构
        • 3.2.2、启动和停止
          • 3.2.2.1、启动
          • 3.2.2.2、验证
          • 3.2.2.3、停止
        • 3.2.3、项目部署及访问
        • 3.2.4、常见问题
      • 3.3、IDEA集成Tomcat
    • 四、HTTP协议
      • 4.1、概述
      • 4.2、请求协议
      • 4.3、响应协议
    • 五、IDEA创建Web项目
      • 5.1、新建普通Java项目
      • 5.2、修改普通Java项目为JavaWeb项目
      • 5.3、项目部署
      • 5.4、项目运行
      • 5.5、其他操作
        • 5.5.1、关联第三方Jar包
        • 5.5.2、导出war包
  • 02_Servlet
    • 一、简介
    • 二、Servlet入门案例
      • 2.1、编写Servlet
      • 2.2、配置Servlet
      • 2.3、访问Servlet
      • 2.4、常见错误
    • 三、Servlet详解
      • 3.1、实现Servlet的三种方式
        • 3.1.1、实现Servlet接口
        • 3.1.2、继承GenericServlet类
        • 3.1.3、继承HttpServlet类
      • 3.2、配置Servlet的两种方式
        • 3.2.1、web.xml方式
        • 3.2.2、注解方式
      • 3.3、常见问题
    • 四、request和response
      • 4.1、请求响应流程
      • 4.2、response对象
        • 4.2.1、设置响应正文
        • 4.2.2、响应乱码问题处理
        • 4.2.3、重定向
      • 4.3、request对象
        • 4.3.1、获取请求头(了解)
        • 4.3.2、获取请求参数
        • 4.3.3、请求乱码问题处理
        • 4.3.4、域对象功能
        • 4.3.5、请求转发
      • 4.4、关于路径写法
        • 4.4.1、浏览器路径
        • 4.4.2、服务器路径
    • 五、登录案例
    • 六、ServletContext
      • 6.1、概述
      • 6.2、获取ServletContext
      • 6.3、作用
        • 6.3.1、获取项目在服务器上发布的真实路径
        • 6.3.2、获取项目的上下文路径
        • 6.3.3、域对象功能
  • 03_JSP入门_Cookie_Session
    • 一、JSP入门
      • 1.1、概述
        • 1.1.1、什么是JSP
        • 1.1.2、JSP组成
      • 1.2、JSP脚本
      • 1.3、JSP原理
      • 1.4、JSP注释
    • 二、Cookie
      • 2.1、什么是Cookie
      • 2.2、Cookie规范
      • 2.3、关于Cookie的操作
        • 2.3.1、创建Cookie
        • 2.3.2、获取Cookie
        • 2.3.3、修改Cookie
        • 2.3.4、Cookie的生命
    • 三、Session
      • 3.1、什么是Session
      • 3.2、获取Session
      • 3.3、HttpSession域对象功能
      • 3.4、登录案例
      • 3.5、Session原理
      • 3.6、Session其他API
      • 3.7、Session实战保存验证码
        • 3.7.1、测试生成验证码
        • 3.7.2、修改登录案例
        • 3.7.3、单击刷新验证码
  • 04_JSP进阶_EL_JSTL
    • 一、JSP指令
      • 1.1、page指令
      • 1.2、include指令
      • 1.3、taglib指令
    • 二、JSP九大内置对象
      • 2.1、简要说明
      • 2.2、pageContext
        • 2.2.1、域对象功能
        • 2.2.2、代理其他域对象
        • 2.2.3、获取其他内置对象
    • 三、JSP动作标签(了解)
      • 3.1、include标签
      • 3.2、forward
    • 四、EL表达式
      • 4.1、概述
      • 4.2、EL表达式使用
        • 4.2.1、EL表达式应用(获取基本类型、字符串)
        • 4.2.2、EL表达式应用(获取引用类型)
      • 4.3、EL表达式运算符
      • 4.4、EL的隐式对象
    • 五、JSTL
      • 5.1、目前存在的问题
      • 5.2、什么是JSTL
      • 5.3、JSTL的作用
      • 5.4、如何使用JSTL
      • 5.5、JSTL核心标签
          • 5.5.1、输入输出
        • 5.5.2、分支结构
          • 5.5.3、循环结构
      • 5.6、登录案例升级
      • 5.7、MVC设计模式
          • 5.7.1、经典的MVC
        • 5.7.2、JavaWeb经典三层框架
        • 5.7.3、基于JavaWeb三层架构升级登录案例
          • 5.7.3.1、建库建表
          • 5.7.3.2、Dao层
          • 5.7.3.3、Service层
          • 5.7.3.4、Servlet
          • 5.7.3.5、相关页面
  • 05_Filter
    • 一、什么是Filter
    • 二、Filter入门
      • 2.1、编写Filter
      • 2.2、配置Filter
    • 三、Filter的生命周期
    • 四、Filter的配置
      • 4.1、xml配置
      • 4.2、注解配置
      • 4.3、过滤器路径
      • 4.4、过滤器链和优先级
        • 4.4.1、过滤器链
        • 4.4.2、过滤器优先级
      • 4.5、Filter应用
        • 4.5.1、权限验证
        • 4.5.2、过滤器解决编码
  • 06_文件上传
    • 一、简介
    • 二、什么是文件上传
    • 三、上传的相关限制
      • 3.1、上传对表单的限制
      • 3.2、上传对Servlet的限制
      • 3.3、如何实现文件上传
        • 3.3.1、相关Jar包
        • 3.3.2、具体步骤
      • 3.4、需要注意的细节
        • 3.4.1、文件名或普通表单项乱码
        • 3.4.2、同名文件上传问题

01_Web开发基础

一、常见软件系统体系结构

C/S和B/S是软件发展过程中出现的两种软件架构方式。

1.1、C/S架构

C/S结构即客户端/服务器(Client/Server),例如QQ;

需要编写服务器端程序,以及客户端程序,例如我们安装的就是QQ的客户端程序;

缺点:软件更新时需要同时更新客户端和服务器端两端,比较麻烦;

优点:安全性比较好。

1.2、B/S架构

B/S结构即浏览器/服务器(Browser/Server);

优点:只需要编写服务器端程序,不需要安装专门的客户端软件,只需要浏览器就行;

缺点:安全性较差。

二、Web资源

Web(World Wide Web) 称为万维网,简单理解就是网站,它用来表示Internet主机上供外界访问的资源。

Internet上供外界访问的资源分为:

  • 静态资源
  • 动态资源

2.1、静态资源

供人们浏览的数据始终是不变;

浏览器能直接看懂

例如:HTML、CSS、JavaScript、各种图片

2.2、动态资源

供人们浏览的数据是由程序产生的,不同时间点访问WEB页面看到的内容各不相同。

客户端请求的页面如果是静态网页,那么服务器会直接把静态网页的内容响应给客户端。如果客户端请求的是动态网页,服务器需要先把动态网页转换成静态网页,然后再把转换后的静态网页响应给客户端。

注:从广义上讲,用户看到的都是静态网页。

例如:Servlet、JSP、ASP、PHP,但是在Java里面只涉及JSP/Servlet

在Java中,动态web资源开发技术统称为JavaWeb

三、Web服务器

Web服务器是运行及发布Web应用的容器,只有将开发的Web项目放置到该容器中,才能使网络中的所有用户通过浏览器进行访问。

3.1、常见Web服务器

开源:

  • Tomcat:主流Web服务器之一,适合初学者
  • Jetty:运行效率比Tomcat高
  • Resin:所有开源服务器软件中,运行效率最高的

三者的用法从代码角度完全相同,只有在开启、关闭服务器软件时对应的命
令稍有区别。掌握一个即掌握所有

收费:

  • WebLogic:Oracle
  • WebSphere:IBM

提供相应的服务与支持,软件大,耗资源

3.2、Tomcat服务器

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,免费开源、并支持Servlet 和JSP 规范。目前Tomcat最新版本为9.0。

Tomcat 技术先进、性能稳定,深受Java 爱好者喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

3.2.1、Tomcat安装
3.2.1.1、下载

官网:Apache Tomcat® - Welcome!

Tomcat8下载地址:Apache Tomcat® - Apache Tomcat 8 Software Downloads

JavaWeb_第1张图片

根据自己电脑情况,选择32位版本或64位版本下载。为了统一,一定要下载压缩版本。

3.2.1.2、解压安装

将Tomcat解压到一个没有特殊符号的目录中,纯英文路径就可以。

3.2.1.3、Tomcat目录结构
文件夹 说明 备注
bin 该目录下存放的是二进制可执行文件 startup.bat启动Tomcat、shutdown.bat停止Tomcat
conf 这是一个非常重要的目录,这个目录下有两个最为重要的文件server.xml和web.xml server.xml:配置整个服务器信息。例如修改端口号,编码格式等。web.xml:项目部署描述符文件,这个文件中注册了很多MIME类型,即文档类型。
lib Tomcat的类库,里面存放Tomcat运行所需要的jar文件。
logs 存放日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,异常也会记录在日志文件中。
temp Tomcat的临时文件,这个目录下的东西在停止Tomcat后删除。
webapps 存放web项目的目录,其中每个文件夹都是一个项目;其中ROOT是一个特殊的项目,在地址栏中没有给出项目目录时,对应的就是ROOT项目。
work 运行时生成的文件,最终运行的文件都在这里。 当客户端用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下。
3.2.2、启动和停止
3.2.2.1、启动

进入Tomcat安装目录bin下,双击startup.bat启动程序,直到控制台出现xxxms,表示启动完成。

3.2.2.2、验证

打开浏览器,在地址栏输入http://localhost:8080,出现如下界面表示启动成功。

image-20210905234021264

3.2.2.3、停止

双击shutdown.bat即可关闭Tomcat启动窗口。

注意:一定不要点击控制台的关闭按钮,可能导致停止失败。

3.2.3、项目部署及访问

静态网站:

在webapps目录下创建一个目录hello(命名必须不包含中文和空格),这个目录称之为项目目录;
在项目目录下创建一个html文件,例如index.html;
启动tomcat;
打开浏览器访问:http://localhost:8080/hello/index.html
动态网站:

在webapps目录下创建一个项目目录hello1;
在项目目录下创建如下内容:
WEB-INF目录;
在WEB-INF目录下创建web.xml文件(到ROOT项目下的WEB-INF复制即可);
创建classes,用于存放.class文件;
创建lib,用于存放jar文件。
创建动态页面index.jsp和WEB-INF同级目录;
打开浏览器访问:http://localhost:8080/hello1/index.jsp
index.jsp内容如下

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>Indextitle>
  head>
  <body>
      <%
        String name = "张三";
        out.print("<p>" + name + "p>");
      %>
      <p>Hello JavaWebp>
  body>
html>

关于访问路径:协议://主机名:端口号/项目名/资源

  • 协议:一般是http或https
  • 主机名:localhost或者一个其他的域名
  • 端口号:Tomcat默认的是8080
  • 项目名:和webapps下项目所在目录的目录名一致
  • 资源:静态资源或动态资源位置,如果资源在某个目录中,此处也要包含目录的名字。

注意:WEB-INF目录中的资源无法直接访问。

3.2.4、常见问题

启动Tomcat控制台闪退,一般是由于没有配置JAVA_HOME

之前在配置JDK的环境变量时,配置JAVA_HOME就是为了此处准备的。

JAVA_HOME的值是JDK中bin的上一级目录。

3.3、IDEA集成Tomcat

JavaWeb_第2张图片

JavaWeb_第3张图片

JavaWeb_第4张图片

四、HTTP协议

4.1、概述

HTTP(hypertext transport protocol),即超文本传输协议。这个协议详细规定了浏览器和万维网服务器之间互相通信的规则。

HTTP就是一个通信规则,通信规则规定了客户端发送给服务器的内容格式,也规定了服务器发送给客户端的内容格式。我们要学习的就是这个两个格式。客户端发送给服务器的格式叫“请求协议”;服务器发送给客户端的格式叫“响应协议”。

HTTP协议的特点:

  • 支持客户端(浏览器)/服务器模式。

  • 简单快速:客户端只向服务器发送请求方法和路径,服务器即可响应数据,因而通信速度很快。请求方法常用的有GET、POST等。

  • 灵活:HTTP允许传输任意类型的数据,传输的数据类型由Content-Type标识。

  • 无连接:无连接指的是每次TCP连接只处理一个或多个请求,服务器处理完客户的请求后,即断开连接。采用这种方式可以节省传输时间。

    • HTTP1.0版本是一个请求响应之后,直接就断开了。称为短连接。
    • HTTP1.1版本不是响应后直接就断开了,而是等几秒钟,这几秒钟之内有新的请求,那么还是通过之前的连接通道来收发消息,如果过了这几秒钟用户没有发送新的请求,就会断开连接。称为长连接。
  • 无状态:HTTP协议是无状态协议。

    • 无状态是指协议对于事务处理没有记忆能力。

4.2、请求协议

客户端发送给服务器的格式

格式:

请求首行
请求头信息
空行
请求体

分类:

  • GET
    • 可以在请求的URL地址后以?的形式带上交给服务器的数据,多个数据之间以&进行分隔;
    • 在URL地址后附带的参数是有限制的,其数据容量通常不能超过1K;
    • GET请求没有请求体。
  • POST
    • 传送的数据量无限制;
    • 传输的数据在请求体内。

JavaWeb_第5张图片

4.3、响应协议

服务器发送给客户端的格式

格式:

响应首行
响应头信息
空行
响应体

常见响应码

  • 200:请求成功,浏览器会把响应体内容(通常是html)显示在浏览器中
  • 404:请求的资源没有找到,说明客户端错误的请求了不存在的资源
  • 500:请求资源找到了,但服务器内部出现了错误
  • 302:重定向
  • 304:如果再次访问的页面没有经过修改,返回304

JavaWeb_第6张图片

五、IDEA创建Web项目

此处以Idea 2020.3举例

5.1、新建普通Java项目

JavaWeb_第7张图片

注意:Idea2020无法直接新建JavaWeb项目,只能通过新建普通Java项目的方式间接新建JavaWeb项目。

选择项目位置和普通Java项目相同,此处略过。

5.2、修改普通Java项目为JavaWeb项目

项目根目录->右键->Add Framework Support

JavaWeb_第8张图片

选择JavaEE版本

JavaWeb_第9张图片

勾选左侧的Web Application

JavaWeb_第10张图片

完成之后,可以看到项目下新建了web目录,并包含如下内容。

JavaWeb_第11张图片

添加相关依赖File->Project Structure

JavaWeb_第12张图片

JavaWeb_第13张图片

JavaWeb_第14张图片

执行上述操作之后,Tomcat相关Jar包就添加到了项目中,不添加后续很多开发无法进行。

5.3、项目部署

此处指的是将Idea中开发的Web项目在Tomcat中部署。

JavaWeb_第15张图片

JavaWeb_第16张图片

JavaWeb_第17张图片

JavaWeb_第18张图片

修改index.jsp的代码


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>Indextitle>
  head>
  <body>
      <%
        String name = "张三";
        out.print("<p>" + name + "p>");
      %>
      <p>Hello JavaWebp>
  body>
html>

5.4、项目运行

单击运行按钮,运行项目,默认会在浏览器中打开index.jsp

JavaWeb_第19张图片

在以后的开发中,多数时候都是重复上述步骤进行JavaWeb项目的开发。

5.5、其他操作

5.5.1、关联第三方Jar包
  1. 在项目WEB-INF目录下新建lib目录;
  2. 将第三方Jar包(例如:MySQL驱动Jar包,druid连接池Jar包)拷贝到lib目录下;
  3. 在lib上右键Add as Library
  4. 选择Project Library,完成
    -. Global Library表示所有工程都可以使用
    -. Project Library表示当前工程中所有模块都可以使用
    -. Module Library表示当前模块可以使用
5.5.2、导出war包

项目完成后,有时候需要打成war方便部署。war包可以直接放入Tomcat的webapps目录中,启动Tomcat后自动解压,即可访问。

JavaWeb_第20张图片

JavaWeb_第21张图片

JavaWeb_第22张图片

执行上述操作,后在项目根目录下生成out目录,内部包含的war包就是我们需要的war包。

JavaWeb_第23张图片

将该war包拷贝到tomcat的webapps目录下,双击运行startup.bat,tomcat会自动解压该war包并发布项目,发布之后我们就可以访问。

02_Servlet

一、简介

Server Applet(服务器小程序),是由服务器端调用和执行的、按照Servlet自身规范编写的Java类。

JavaWeb的三大组件(Servlet、Filter、Lisener)之一,是动态资源的一种。

作用:

  • 接收请求
  • 处理数据
  • 完成响应

后续我们学习Servlet也是集中在这三点。

二、Servlet入门案例

2.1、编写Servlet

  • 实现javax.servlet.Servlet
  • 重写5个主要方法
  • 在核心的service()方法中编写输出语句,打印访问结果
package com.qfedu.servlet;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;

public class TestServlet implements Servlet {
    public void init(ServletConfig config) throws ServletException{
    }
    
    public void service(ServletRequest request,ServletResponse response) throws ServletException,IOException{
        System.out.println("My First Servlet!");
    }
    
    public void destroy(){
    
    }
    
    public ServletConfig getServletConfig(){
        return null;    
    }    
    
    public String getServletInfo(){
        return null;
    }
}

2.2、配置Servlet

在web.xml中配置Servlet


<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_3_1.xsd"
         version="3.1">
    
    <servlet>
        
        <servlet-name>aServletservlet-name>
        
        <servlet-class>com.qfedu.servlet.AServletservlet-class>
    servlet>
    
    <servlet-mapping>
        
        <servlet-name>aServletservlet-name>
        
           <url-pattern>/aServleturl-pattern>
    servlet-mapping>
web-app>

url-pattern配置的内容就是浏览器地址栏输入的URL中项目名称后资源的内容

2.3、访问Servlet

在Tomcat中部署该项目,访问http://localhost:8080/项目名/testServlet,控制台及浏览器有输出证明Servlet部署及访问成功。

2.4、常见错误

500错误:服务器内部出现了错误

修改TestServlet代码如下:

package com.qfedu.servlet;

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

public class AServlet implements Servlet {

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

    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("My First Servlet!");
        String str = null;
        System.out.println(str.length());
    }

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

    @Override
    public void destroy() {

    }
}

运行JavaWeb项目,访问TestServlet,页面显示如下:
JavaWeb_第24张图片

三、Servlet详解

Servlet是单例的,一个类型的Servlet只有一个实例对象,那么就有可能会出现一个Servlet同时处理多个请求,Servlet不是线程安全的,不应该在Servlet中创建成员变量,因为可能会存在一个线程对这个成员变量进行写操作,另一个线程对这个成员变量进行读操作。

  • 不要在Servlet中创建成员,创建局部变量即可;
  • 可以创建无状态成员;
  • 可以创建有状态的成员,但状态必须为只读的。

3.1、实现Servlet的三种方式

3.1.1、实现Servlet接口

javax.servlet.Servlet代码

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;
    ServletConfig getServletConfig();
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
    String getServletInfo();
    void destroy();
}

Servlet中的大多数方法不由我们来调用,而是由Tomcat来调用,并且Servlet的对象也不由我们来创建,由Tomcat来创建!

生命周期方法

  • init()
    • 服务器会在Servlet第一次被访问时创建Servlet,在Servlet被创建后,服务器会马上调用Servlet的void init(ServletConfig)方法;
    • 在整个servlet的生命周期中,该方法只被调用一次。
  • service()
    • 当服务器每次接收到请求时,都会去调用Servlet的service()方法来处理请求;
    • 每次处理请求都会被调用
  • destroy()
    • 在服务器被关闭时,服务器会去销毁Servlet,在销毁Servlet之前服务器会先去调用Servlet的destroy()方法;
    • 在整个servlet的生命周期中,该方法只被调用一次。

生命周期方法方法演示

package com.qfedu.servlet;

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

public class BServlet implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init...");
    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service...");
    }

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

    @Override
    public void destroy() {
        System.out.println("destroy...");
    }
}

在web.xml中配置Servlet

<servlet>
    <servlet-name>bServletservlet-name>
    <servlet-class>com.qfedu.servlet.BServletservlet-class>
servlet>
<servlet-mapping>
    <servlet-name>bServletservlet-name>
    <url-pattern>/bServleturl-pattern>
servlet-mapping>

启动tomcat->访问BServlet->关闭tomcat

观察控制台的打印。

关于ServletConfig

  • 一个ServletConfig对象对应一段web.xml中的配置信息
  • ServletConfig是一个接口,该接口实现类的对象由Tomcat提供
  • 方法
    • getInitParameter(java.lang.String name) 获取servlet的初始化参数
    • getInitParameterNames()
    • getServletContext()
    • getServletName()

修改BServlet代码

package com.qfedu.servlet;

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

public class BServlet implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init...");
        System.out.println(servletConfig.getInitParameter("name"));
    }

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

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service...");
    }

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

    @Override
    public void destroy() {
        System.out.println("destroy...");
    }
}

修改web.xml关于BServlet的配置

<servlet>
    <servlet-name>bServletservlet-name>
    <servlet-class>com.qfedu.servlet.BServletservlet-class>
    
    <init-param>
        <param-name>nameparam-name>
        <param-value>zhangsanparam-value>
    init-param>
servlet>
<servlet-mapping>
    <servlet-name>bServletservlet-name>
    <url-pattern>/bServleturl-pattern>
servlet-mapping>
3.1.2、继承GenericServlet类

GenericServlet使编写Servlet变得更容易。它提供生命周期方法init和destroy的简单实现,要编写一般的Servlet,只需重写抽象service方法即可。

代码如下:

package com.qfedu.servlet;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;

public class CServlet extends GenericServlet {

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("CServlet....");
    }
}

web.xml配置CServlet

<servlet>
    <servlet-name>cServletservlet-name>
    <servlet-class>com.qfedu.servlet.CServletservlet-class>
servlet>
<servlet-mapping>
    <servlet-name>cServletservlet-name>
    <url-pattern>/cServleturl-pattern>
servlet-mapping>
3.1.3、继承HttpServlet类

HttpServlet类是GenericServlet的子类,它提供了对HTTP请求的特殊支持,所以通常我们都会通过继承HttpServlet来完成自定义的Servlet。

在HttpServlet的service(HttpServletRequest,HttpServletResponse)方法会去判断当前请求是GET还是POST,如果是GET请求,那么会去调用本类的doGet()方法,如果是POST请求会去调用doPost()方法,这说明我们在子类中去覆盖doGet()或doPost()方法即可。

package com.qfedu.servlet;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;

public class DServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("doGet.......");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("doPost........");
    }
}

web.xml配置DServlet

<servlet>
    <servlet-name>dServletservlet-name>
    <servlet-class>com.qfedu.servlet.DServletservlet-class>
servlet>
<servlet-mapping>
    <servlet-name>dServletservlet-name>
    <url-pattern>/dServleturl-pattern>
servlet-mapping>

3.2、配置Servlet的两种方式

3.2.1、web.xml方式

Servlet2.5及之前使用该方式


<servlet>
    
    <servlet-name>aServletservlet-name>
    
    <servlet-class>com.qfedu.servlet.AServletservlet-class>
    
    <load-on-startup>1load-on-startup>
servlet>

<servlet-mapping>
    
    <servlet-name>aServletservlet-name>
    
    <url-pattern>/aServleturl-pattern>
servlet-mapping>

url-pattern定义匹配规则,取值说明:

  • 精确匹配/具体的名称只有url路径是具体的名称的时候才会触发Servlet
  • 后缀匹配*.xxx只要是以xxx结尾的就匹配触发Servlet
  • 通配符匹配/*匹配所有请求,包含服务器的所有资源
  • 通配符匹配/匹配所有请求,包含服务器的所有资源,不包括jsp

load-on-startup

  • 元素标记容器是否应该在web应用程序启动的时候就加载这个servlet;
  • 它的值必须是一个整数,表示servlet被加载的先后顺序;
  • 如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载;
  • 如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
3.2.2、注解方式

Servlet3.0新增特性,推荐使用

@WebServlet常用属性

  • name:Serlvet名字,可选

  • value::配置url路径,可以配置多个

  • urlPatterns:配置url路径 ,和value作用一样,不能同时和value使用

l- oadOnStartup:配置Servlet的创建的时机, 如果是0或者正数,启动程序时创建,如果是负数,则访问时创建。 数子越小优先级越高。

package com.qfedu.servlet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "EServlet", value = "/eServlet", loadOnStartup = 1)
public class EServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("doGet...");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("doPost...");
    }
}

3.3、常见问题

  1. HTTP Status 404资源找不到
    -. 第一种情况:地址书写错误;
    -. 第二种情况:地址没有问题,把IDEA项目中out目录删除,然后重新运行。

  2. Serlvet地址配置重复,多个Servlet的url-pattern使用相同的值

  3. Serlvet地址配置错误,比如没有写/。

四、request和response

4.1、请求响应流程

JavaWeb_第25张图片

4.2、response对象

reponse类型为javax.servlet.http.HttpServletResponse,客户端发出每个请求时,服务器都会创建一个response对象,并传入给Servlet.service()方法。response对象是用来对客户端进行响应的,这说明在service()方法中使用response对象可以完成对客户端的响应工作。

主要功能

  • 设置响应正文
  • 设置响应头信息
  • 发送状态码
  • 重定向
4.2.1、设置响应正文

关于设置响应正文

  • response是响应对象,向客户端输出响应正文(响应体)可以使用response的响应流,常用方法:

    • PrintWriter out = response.getWriter():获取字符流—16
    • ServletOutputStream out = response.getOutputStream():获取字节流—8
  • 如果响应正文内容为字符,那么使用response.getWriter(),如果响应内容是字节,例如下载时,那么可以使用response.getOutputStream();

注意:在一个请求中,不能同时使用这两个流,也就是说,要么你使用repsonse.getWriter(),要么使用response.getOutputStream(),但不能同时使用这两个流。不然会抛出IllegalStateException异常。

例:在页面打印文字,字体颜色为红色。

package com.qfedu.servlet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "FServlet", value = "/FServlet")
public class FServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        out.println("

Hello World

"
); out.println("

你好世界

"
); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
4.2.2、响应乱码问题处理

问题分析:

  1. 在使用response.getWriter()时默认字符编码为ISO-8859-1,不支持中文;
  2. 如果希望设置字符流的字符编码为utf-8,可以使用response.setCharacterEncoding(“utf-8”)来设置,这样可以保证输出给客户端的字符都是使用的UTF-8编码;
  3. 客户端浏览器并不知道响应数据是什么编码的,浏览器解析之后依然出现乱码。

解决方案:

  • 使用response.setContentType("text/html;charset=utf-8")
  • 一定要在获取输出流前进行设置;
  • 优势
    • 设置content-type响应头,客户端浏览器会使用content-type头来解读响应数据;
    • 这个方法还会调用response.setCharacterEncoding(“utf-8”)保证输出给客户端的字符都是使用UTF-8编码的。

上述案例修改

package com.qfedu.servlet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "FServlet", value = "/FServlet")
public class FServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置响应头
        response.setContentType("text/html;charset=utf-8");

        PrintWriter out = response.getWriter();
        out.println("

Hello World

"
); out.println("

你好世界

"
); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
4.2.3、重定向

现象:当访问http://baidu.com时,发现地址栏变成https://www.baidu.com,这就是重定向了

概念:重定向是服务器通知浏览器去访问另一个地址,即再发出另一个请求。

JavaWeb_第26张图片

实现方式1:

  1. 设置响应码response.setStatus(302);
  2. 设置重定向的位置response.setHeader("Location", "/项目名/bServlet");

实现方式2:

response.sendRedirect("/项目名/bServlet");

package com.qfedu.servlet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "GServlet", value = "/GServlet")
public class GServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("GServlet............................");
        //实现方式1
        //response.setStatus(302);
        //response.setHeader("Location", "/JavaWebTest_war_exploded/bServlet");
        //实现方式2
        response.sendRedirect("/JavaWebTest_war_exploded/bServlet");
    }

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

    }
}

关于重定向的总结

  • 重定向是两次请求
  • 重定向的URL可以是其他应用,不局限于当前应用,例如重定向到百度
  • 重定向的响应头为302,并且必须要有Location响应头
  • 重定向就不要再使用response.getWriter()或response.getOutputStream()输出数据

4.3、request对象

request是Servlet.service()方法的一个参数,类型为javax.servlet.http.HttpServletRequest。在客户端发出每个请求时,服务器都会创建一个request对象,并把请求数据封装到request中,然后在调用Servlet.service()方法时传递给service()方法,这说明在service()方法中可以通过request对象来获取请求数据。

主要功能

  • 获取请求头
  • 获取请求参数
  • 域对象功能
  • 请求转发和请求包含
4.3.1、获取请求头(了解)

String getMethod():返回请求方法,例如:GET String getRemoteAddr():返回当前客户端的IP地址 String getRemoteHost():返回当前客户端的主机名,但这个方法的实现还是获取IP地址 String getServerName():返回主机名,例如:localhost int getServerPort():返回服务器端口号,例如:8080

package com.qfedu.servlet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "HServlet", value = "/HServlet")
public class HServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("请求方法:" + request.getMethod());
        System.out.println("客户端IP:" + request.getRemoteAddr());
        System.out.println("客户端主机名:" + request.getRemoteHost());
        System.out.println("主机名:" + request.getServerName());
        System.out.println("服务器端口号:" + request.getServerPort());
        System.out.println("客户端端口号:" + request.getRemotePort());
    }

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

    }
}
4.3.2、获取请求参数

客户端传递参数方式有两种(GET和POST)

  • 浏览器地址栏直接输入:一定是GET请求
  • 超链接:一定是GET请求
  • 表单:可以是GET,也可以是POST,这取决与form的method属性值

GET请求和POST请求的区别

  • GET
    • 可以在请求的URL地址后以?的形式带上交给服务器的数据,多个数据之间以&进行分隔,不安全
    • 在URL地址后附带的参数是有限制的,其数据容量通常不能超过1K
    • GET请求没有请求体
  • POST
    • 传送的数据量无限制
    • 传输的数据在请求体内
    • 请求参数不会显示浏览器的地址栏,相对安全

request获取请求参数的API

  • String getParameter(String name):通过指定名称获取参数值(必须掌握)
  • String[] getParameterValues(String name):当多个参数名称相同时,可以使用方法来获取
  • Enumeration getParameterNames():获取所有参数的名字
  • Map getParameterMap():获取所有参数封装到Map中,其中key为参数名,value为参数值,因为一个参数名称可能有多个值,所以参数值是String[],而不是String

Servlet代码如下:

package com.qfedu.servlet;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.util.Arrays;

@WebServlet(name = "IServlet", value = "/IServlet")
public class IServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取请求参数
        String username = request.getParameter("username");
        System.out.println(username);
        
        //当多个参数名称相同时,可以使用方法来获取
        String[] hobbies = request.getParameterValues("hobby");
        System.out.println(Arrays.toString(hobbies));
    }

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

Get方式提交:

http://localhost:8080/JavaWebTest_war_exploded/IServlet?username=zs&hobby=睡觉&hobby=game

Post方式提交:

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    <form action="/JavaWebTest_war_exploded/IServlet" method="post">
        用户名<input type="text" name="username" /><br/>
        爱好<input type="checkbox" name="hobby" value="睡觉1">睡觉1
        <input type="checkbox" name="hobby" value="睡觉2">睡觉2
        <input type="checkbox" name="hobby" value="睡觉3">睡觉3
        <button type="submit">提交button>
    form>
body>
html>
4.3.3、请求乱码问题处理

Get请求乱码

  • Tomcat8的版本中get方式不会出现乱码了,因为服务器对url的编码格式可以进行自动转换。

POST请求乱码

  • 由于客户端是以UTF-8字符编码将表单数据传输到服务器端的,因此服务器也需要设置以UTF-8字符编码进行接收。
  • 解决方案:使用从ServletRequest接口继承而来的setCharacterEncoding(charset)方法进行统一的编码设置。

修改IServlet代码如下:

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.util.Arrays;

@WebServlet(name = "IServlet", value = "/IServlet")
public class IServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置请求编码
        request.setCharacterEncoding("utf-8");

        //获取请求参数
        String username = request.getParameter("username");
        System.out.println(username);
        //当多个参数名称相同时,可以使用方法来获取
        String[] hobbies = request.getParameterValues("hobby");
        System.out.println(Arrays.toString(hobbies));
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
4.3.4、域对象功能
  • 域对象作用就是在多个Servlet之间传递数据
  • request可以在一个请求中共享数据,一个请求会创建一个request对象,如果在一个请求中经历了多个Servlet,那么多个Servlet就可以使用request来共享数据
  • 域方法
    • 存void setAttribute(String name, Object value)
    • 取Object getAttribute(String name)
    • 删除void removeAttribute(String name)
  • 一个请求可以通过转发、包含跨越多个servlet,在这个请求链之上的servlet可以通过request共享数据
4.3.5、请求转发

转发的作用在服务器端,将请求发送给服务器上的其他资源,以共同完成一次请求的处理,多个Servlet或JSP共同来处理一个请求。

JavaWeb_第27张图片

实现步骤:

  1. 创建调度器RequestDispatcher rd = request.getRequestDispatcher("/BServlet");
  2. 转发rd.forward(request, response);
    以下代码实现转发,并通过request域传递数据
@WebServlet(name = "JServlet", value = "/JServlet")
public class JServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //在request域中存放数据
        request.setAttribute("name", "zs");

        //转发
        //创建调度器
        RequestDispatcher dispatcher = request.getRequestDispatcher("/KServlet");
        //转发
        dispatcher.forward(request, response);
    }

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

@WebServlet(name = "KServlet", value = "/KServlet")
public class KServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //从request域中获取数据
        String name = (String)request.getAttribute("name");
        System.out.println(name);

        //从request域中删除数据
        request.removeAttribute("name");

        //转发
        request.getRequestDispatcher("/LServlet").forward(request, response);
    }

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

@WebServlet(name = "LServlet", value = "/LServlet")
public class LServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //从request域中获取数据
        String name = (String)request.getAttribute("name");
        //由于KServlet删除了域对象中的内容,导致name输出为null
        System.out.println("LServlet:" + name);
    }

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

    }
}

请求转发和重定向的区别

  • 请求转发是一个请求,而重定向是两个请求
  • 请求转发后浏览器地址栏不会有变化,而重定向会有变化,因为重定向是两个请求
  • 请求转发的目标只能是本应用中的资源,重定向的目标可以是其他应用
  • 请求转发对AServlet和BServlet的请求方法是相同的,即要么都是GET,要么都是POST,因为请求转发是一个请求
  • 重定向的第二个请求一定是GET

4.4、关于路径写法

4.4.1、浏览器路径

超链接、表单、重定向都是客户端路径(假设当前主机是http://localhost:8080,应用名称Test)

  • 绝对路径http://www.baidu.com
  • 相对路径
    • /开头的相对路径
      • 表示相对于当前主机地址(http://localhost:8080)
      • 在超链接、表单、重定向,如果以/开头,一定是/Test/...
      • 参考重定向中路径的写法
  • 不以/开头的相对路径
    • 表示相对当前路径
      注意:强烈建议使用/开头的相对路径,后面是当前应用的名称,再是访问路径。
4.4.2、服务器路径

这里说的服务器路径,就是以/开头的相对路径

  • 表示相对于当前应用(http://localhost:8080/Test)
  • 参考请求转发中路径的写法

五、登录案例

使用Servlet完成登录功能,不需要连接数据库,需求如下:

  • 输入用户名:admin,密码:admin,页面显示“登录成功”;
  • 输入其他用户名密码,页面显示“登录失败”。

登录页代码如下:

DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>登录title>
    head>
    <body>
        <form action="http://localhost:8080/JavaWebTest/LoginServlet" method="post">
            <fieldset style="width: 300px;">
                    <legend>用户登录legend>
                    <p>
                        <label>账号label>
                        <input type="text" name="username" placeholder="请输入用户名" />
                    p>
                    <p>
                        <label>密码label>
                        <input type="password" name="password" placeholder="请输入密码" />
                    p>
                    <p>
                        <button type="submit">登录button>
                        <button type="reset">重置button>
                    p>
            fieldset>
        form>
    body>
html>

登录成功页

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>successtitle>
head>
<body>
    <p>登录成功p>
body>
html>

登录失败页

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>errortitle>
head>
<body>
    <p>登录失败p>
body>
html>

Servlet代码

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

@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        if(username.equals("admin") && password.equals("admin")) {
            request.getRequestDispatcher("/success.html").forward(request, response);
        } else {
            request.getRequestDispatcher("/error.html").forward(request, response);
        }
    }

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

六、ServletContext

6.1、概述

一个项目只有一个ServletContext对象,使用它可以给多个Servlet传递数据,与天地同寿,这个对象在Tomcat启动时就创建,在Tomcat关闭时才会消失。

特点:

  • 唯一性: 一个应用对应一个ServletContext。

  • 生命周期: 只要容器不关闭或者应用不卸载,ServletContext就一直存在。

6.2、获取ServletContext

  • GenericServlet提供了getServletContext()方法;

  • HttpServletRequest提供了getServletContext()方法;

  • HttpSession提供了getServletContext()方法。

6.3、作用

6.3.1、获取项目在服务器上发布的真实路径
//获取项目在服务器上的真实路径
String realpath=servletContext.getRealPath("/");
6.3.2、获取项目的上下文路径

上下文路径指的是应用名称。

//获取项目的上下文路径
//上下文路径(应用程序名称)
System.out.println(servletContext.getContextPath());
System.out.println(request.getContextPath());

案例

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "MServlet", value = "/MServlet")
public class MServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取servletContext
        ServletContext servletContext = this.getServletContext();
        //获取项目真实路径
        System.out.println(servletContext.getRealPath("/"));
        //获取项目上下文路径
        System.out.println(servletContext.getContextPath());
        System.out.println(request.getContextPath());
    }

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

    }
}
6.3.3、域对象功能

ServletContext拥有作用域,可以存储数据到全局容器中

  • 存储数据:servletContext.setAttribute("name",value);
  • 获取数据:servletContext.getAttribute("name");
  • 移除数据:servletContext.removeAttribute("name");

案例,统计网站访问人数

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

@WebServlet(name = "NServlet", value = "/NServlet")
public class NServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext sctx = this.getServletContext();

        Integer count = (Integer) sctx.getAttribute("count");

        if(count == null) {
            count = 1; //自动装箱
        } else {
            count++;
        }
        sctx.setAttribute("count", count);
        PrintWriter out = response.getWriter();
        out.println("

" + count + "

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

03_JSP入门_Cookie_Session

一、JSP入门

1.1、概述

1.1.1、什么是JSP

JSP(Java Server Pages)是JavaWeb服务器端的动态资源。它与HTML页面的作用是相同的,显示数据和获取数据。

1.1.2、JSP组成

JSP=HTML+Java脚本+JSP动作标签(包含EL表达式)

1.2、JSP脚本

本质上就是Java代码片段

分类:

  • <%...%>:Java语句
  • <%=…%>:Java表达式out.print(…);
  • <%!...%>:Java定义类成员

内置对象(无需创建就可以使用的对象):

  • out对象在JSP页面中无需创建就可以使用,它的作用是用来向客户端输出;
  • <%=…%>与out.print()功能是相同的,它们都是向客户端输出
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>jsptesttitle>
head>
<body>
<h1>JSP演示h1>
    <%
        // Java语句
        String s1 = "hello jsp";
        // 不会输出到客户端,而是在服务器端的控制台打印
        System.out.println(s1);
    %>
    
    输出变量:<%=s1 %><br/>
    输出int类型常量:<%=100 %><br/>
    输出String类型常量:<%="你好" %><br/>
    使用HTML直接输出常量<span>100span>
body>
html>

在一个JSP中多个<%...%>可以一起使用

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>jsp表格title>
head>
<body>
    <h1>表格h1>
    <table border="1" width="50%">
        <tr>
            <th>序号th>
            <th>用户名th>
            <th>密码th>
        tr>
        <%
            for(int i = 0; i < 5; i++) {
        %>
            <tr>
                <td><%=i+1 %>td>
                <td>user<%=i %>td>
                <td><%=100+1 %>td>
            tr>
        <%
            }
        %>
    table>
body>
html>

1.3、JSP原理

JSP是特殊的Servlet(查看编译后的JSP源码)类,当JSP页面首次被访问时,容器(Tomcat)会先把JSP编译成Servlet,然后再去执行Servlet。所以JSP其实就是一个Servlet。
JavaWeb_第28张图片

JSP生成的Servlet存放在tomcat的work目录下,它是JSP的“真身”。我们打开看看其中的内容,了解一下JSP的“真身”。

你会发现,在JSP中的静态信息(例如等)在“真身”中都是使用out.write()完成打印!这些静态信息都是作为字符串输出给了客户端。

1.4、JSP注释

<%-- ... --%>, 在JSP编译成.java时会被忽略的,即JSP注释。 可以在JSP页面中使用html注释:,但这个注释在JSP编译成的.java中是存在的,它不会被忽略,而且会被发送到客户端浏览器。

二、Cookie

2.1、什么是Cookie

Cookie翻译成中文是小甜点,小饼干的意思。在HTTP中它表示服务器送给客户端浏览器的小甜点。

Cookie是在浏览器访问Web服务器的某个资源时,由Web服务器在HTTP响应消息头中附带传送给浏览器的一小段数据。一旦Web浏览器保存了某个Cookie,那么它在以后每次访问该Web服务器时,都应在HTTP请求头中将这个Cookie回传给Web服务器。一个Cookie主要由标识该信息的名称(name)和值(value)组成。

JavaWeb_第29张图片

2.2、Cookie规范

Cookie大小上限为4KB;

一个服务器最多在客户端浏览器上保存20个Cookie;

一个浏览器最多保存300个Cookie;

上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能!

注意,不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。

2.3、关于Cookie的操作

2.3.1、创建Cookie
//创建Cookie
Cookie ck=new Cookie("name", "zs");
ck.setMaxAge(-1);//内存存储,取值有三种:>0有效期,单位秒;=0浏览器关闭;<0内存存储,默认-1
response.addCookie(ck);//添加到response对象中,响应时发送给客户端
2.3.2、获取Cookie
//获取所有的Cookie
Cookie[] cks=request.getCookies();
//遍历Cookie
for(Cookie ck:cks){
    //检索出自己的Cookie
    if(ck.getName().equals("name")) {
        //记录Cookie的值
        code=ck.getValue();
        break;
    }
}
2.3.3、修改Cookie

只需要保证Cookie的名和路径一致即可修改

//修改Cookie
Cookie ck=new Cookie("name", "ls");
ck.setMaxAge(-1);//内存存储,取值有三种:>0有效期,单位秒;=0失效;<0内存存储
response.addCookie(ck);//让浏览器添加Cookie
2.3.4、Cookie的生命

Cookie不只有name和value,Cookie还是生命。所谓生命就是Cookie在客户端的有效时间,可以通过setMaxAge(int)来设置Cookie的有效时间。

  • cookie.setMaxAge(-1):cookie的maxAge属性的默认值就是-1,表示只在浏览器内存中存活。一旦关闭浏览器窗口,那么cookie就会消失。
  • cookie.setMaxAge(60*60):表示cookie对象可存活1小时。当生命大于0时,浏览器会把Cookie保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie也会存活1小时;
  • cookie.setMaxAge(0):cookie生命等于0是一个特殊的值,它表示cookie被作废!也就是说,如果原来浏览器已经保存了这个Cookie,那么可以通过Cookie的setMaxAge(0)来删除这个Cookie。无论是在浏览器内存中,还是在客户端硬盘上都会删除这个Cookie。

案例

//设置Cookie
@WebServlet(name = "AServlet", value = "/AServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置Cookie
        Cookie cookie = new Cookie("name", "zs");
        //设置Cookie的超时时间 -1表示一旦关闭浏览器窗口,那么cookie就会消失
        cookie.setMaxAge(-1);
        response.addCookie(cookie);
    }

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

    }
}

//获取Cookie
@WebServlet(name = "BServlet", value = "/BServlet")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie[] cks = request.getCookies();
        for (Cookie ck : cks) {
            if(ck.getName().equals("name")) {
                //获取Cookie的值
                System.out.println(ck.getValue());
                break;
            }
        }
    }

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

    }
}

三、Session

3.1、什么是Session

关于会话:

  • 会话范围是某个用户从首次访问服务器开始,到该用户关闭浏览器结束
  • 一个用户对服务器的多次连贯性请求!所谓连贯性请求,就是该用户多次请求中间没有关闭浏览器
  • 类似生活中的对话

javax.servlet.http.HttpSession接口表示一个会话,是Java Web提供的。

Session是服务器端对象,保存在服务器端。

3.2、获取Session

HttpSession request.getSesssion():如果当前会话已经有了session对象那么直接返回,如果当前会话还不存在会话,那么创建session并返回; JSP中得到session对象:session是JSP内置对象之一,不用创建就可以直接使用。

3.3、HttpSession域对象功能

一个会话创建一个HttpSession对象,同一会话中的多个请求中可以共享session中的数据

目前为止已经学习了三个域对象,分别是request、session、servletContext,他们都有共同的方法:

  • void setAttribute(String name, Object value)
  • Object getAttribute(String name)
  • void removeAttribute(String name)

如果用户需要在会话范围之内共享数据,应该将数据保存在session中。

案例,演示session中会话的多次请求中共享数据

@WebServlet(name = "CServlet", value = "/CServlet")
public class CServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取Session
        HttpSession session = request.getSession();
        //在Session域中存放数据
        session.setAttribute("name", "zs");
    }

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

    }
}

@WebServlet(name = "DServlet", value = "/DServlet")
public class DServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取Session
        HttpSession session = request.getSession();
        //从Session域中获取数据
        String name = (String)session.getAttribute("name");
        System.out.println(name);
    }

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

    }
}

3.4、登录案例

login.jsp:提供登录表单,提交表单请求到LoginServlet

LoginServlet:获取请求参数,校验用户是否登录成功

  • 失败:跳转到登录页面,显示错误信息
  • 成功:跳转到成功页,显示“欢迎xxx”的提示信息

success.jsp:登录成功页面,显示欢迎信息,关闭浏览器后,直接访问登录页会提示登录

LoginServlet代码如下

@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        if(username.equals("admin") && password.equals("admin")) {
            //登录成功
            //保存数据到Session
            HttpSession session = request.getSession();
            session.setAttribute("username", username);
            request.getRequestDispatcher("/success.jsp").forward(request, response);
        } else {
            //登录失败信息
            String msg = "用户名或密码错误";
            request.setAttribute("msg", msg);
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }

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

login.jsp代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Logintitle>
head>
<body>
<%
    String msg = (String)request.getAttribute("msg");
    if(msg != null) {
%>
    <p style="color: red;"><%=msg %>p>
<%
    }
%>
<form action="<%=request.getContextPath() %>/LoginServlet" method="post">
    <fieldset style="width: 300px;">
        <legend>用户登录legend>
        <p>
            <label>账号label>
            <input type="text" name="username" placeholder="请输入用户名" />
        p>
        <p>
            <label>密码label>
            <input type="password" name="password" placeholder="请输入密码" />
        p>
        <p>
            <button type="submit">登录button>
            <button type="reset">重置button>
        p>
    fieldset>
form>
body>
html>

success.jsp代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>successtitle>
head>
<body>
<%
    String username = (String) session.getAttribute("username");
    if(username != null) {
%>
        <p>欢迎<%=username %>p>
<%
    } else {
%>
        <p>请先登录p>
<%
    }
%>
body>
html>

3.5、Session原理

Session底层依赖Cookie

  1. 当用户第一次使用session时(表示第一次请求服务器),服务器会创建session,并创建一个Cookie,在Cookie中保存了session的id,发送给客户端。这样客户端就有了自己session的id了。但这个Cookie只在浏览器内存中存在,也就是说,在关闭浏览器窗口后,Cookie就会丢失,也就丢失了sessionId;
  2. 当用户第二次访问服务器时,会在请求中把保存了sessionId的Cookie发送给服务器,服务器通过sessionId查找session对象,然后给使用。也就是说,只要浏览器容器不关闭,无论访问服务器多少次,使用的都是同一个session对象。这样也就可以让多个请求共享同一个session了;
  3. 当用户关闭了浏览器窗口后,再打开浏览器访问服务器,这时请求中没有了sessionId,那么服务器会创建一个session,再把sessionId通过Cookie保存到浏览器中,也是一个新的会话开始了。原来的session会因为长时间无法访问而失效;
  4. 当用户打开某个服务器页面长时间没动作时,这样session会超时失效,当用户再有活动时,服务器通过用户提供的sessionId已经找不到session对象了,那么服务器还是会创建一个新的session对象,再把新的sessionId保存到客户端。这也是一个新的会话开始了。

3.6、Session其他API

String getId():获取sessionId;

int getMaxInactiveInterval():获取session可以的最大不活动时间(秒),默认为30分钟。当session在30分钟内没有使用,那么Tomcat会在session池中移除这个session;

void setMaxInactiveInterval(int interval):设置session允许的最大不活动时间(秒),如果设置为1秒,那么只要session在1秒内不被使用,那么session就会被移除;

long getCreationTime():返回session的创建时间,返回值为当前时间的毫秒值;

long getLastAccessedTime():返回session的最后活动时间,返回值为当前时间的毫秒值;

void invalidate():让session失效!调用这个方法会被session失效,当session失效后,客l 户端再次请求,服务器会给客户端创建一个新的session,并在响应中给客户端新session的sessionId;

boolean isNew():查看session是否为新。当客户端第一次请求时,服务器为客户端创建session,但这时服务器还没有响应客户端,也就是还没有把sessionId响应给客户端时,这时session的状态为新。

修改LoginServlet,体会Session超时时间及Session失效

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

@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        if(username.equals("admin") && password.equals("admin")) {
            //登录成功
            //保存数据到Session
            HttpSession session = request.getSession();
            session.setAttribute("username", username);
            //设置Session超时时间为1s
            session.setMaxInactiveInterval(1);
            //延时2000ms,模拟Session超时
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //设置Session失效
            //session.invalidate();
            request.getRequestDispatcher("/success.jsp").forward(request, response);
        } else {
            //登录失败信息
            String msg = "用户名或密码错误";
            request.setAttribute("msg", msg);
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }

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

修改登录案例,增加退出功能

用于退出登录的Servlet

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "LogoutServlet", value = "/LogoutServlet")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置Session失效
        request.getSession().invalidate();
        //跳转到登录页
        request.getRequestDispatcher("/login.jsp").forward(request, response);
    }

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

    }
}

修改success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>successtitle>
head>
<body>
<%
    String username = (String) session.getAttribute("username");
    if(username != null) {
%>
        <p>欢迎<%=username %>p>
        <p><a href="<%=request.getContextPath()%>/LogoutServlet">退出a>p>
<%
    } else {
%>
        <p>请先登录p>
<%
    }
%>
body>
html>

3.7、Session实战保存验证码

在我们注册时,如果没有验证码的话,我们可以使用URLConnection来写一段代码发出注册请求。甚至可以使用while(true)来注册!那么服务器就废了!

验证码可以去识别发出请求的是人还是程序!当然,如果聪明的程序可以去分析验证码图片!但分析图片也不是一件容易的事,因为一般验证码图片都会带有干扰线,人都看不清,那么程序一定分析不出来。

3.7.1、测试生成验证码

在项目中导入ValidateCode.jar

import cn.dsna.util.images.ValidateCode;

import java.io.FileOutputStream;
import java.io.IOException;

public class MyTest {
    public static void main(String[] args) throws IOException {
        //生成验证码
        ValidateCode vc=new ValidateCode(200, 30, 4, 10);
        //获取验证码对应的文字
        String code=vc.getCode();
        System.out.println(code);
        // 保存图片
        FileOutputStream out = new FileOutputStream("D:/code_img.jpg");
        vc.write(out);
    }
}

运行完成后可以在D盘下看到验证码图片。

3.7.2、修改登录案例

增加生成验证码的Servlet

import cn.dsna.util.images.ValidateCode;

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

@WebServlet(name = "CodeServlet", value = "/CodeServlet")
public class CodeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //生成验证码
        ValidateCode vc = new ValidateCode(150, 30, 4, 5);
        String sysCode = vc.getCode();
        System.out.println(sysCode);

        //在Session中存储验证码
        request.getSession().setAttribute("sysCode", sysCode);
        //将验证码响应给用户
        vc.write(response.getOutputStream());
    }

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

修改登录页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Logintitle>
head>
<body>
<%
    String msg = (String)request.getAttribute("msg");
    if(msg != null) {
%>
    <p style="color: red;"><%=msg %>p>
<%
    }
%>
<form action="<%=request.getContextPath() %>/LoginServlet" method="post">
    <fieldset style="width: 300px;">
        <legend>用户登录legend>
        <p>
            <label>账号label>
            <input type="text" name="username" placeholder="请输入用户名" />
        p>
        <p>
            <label>密码label>
            <input type="password" name="password" placeholder="请输入密码" />
        p>
        <p>
            <label>验证码label>
            <input type="text" name="code" placeholder="请输入验证码" />
            <img src="<%=request.getContextPath()%>/CodeServlet" alt="验证码">
        p>
        <p>
            <button type="submit">登录button>
            <button type="reset">重置button>
        p>
    fieldset>
form>
body>
html>

修改LoginServlet

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

@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String code = request.getParameter("code");

        //获取Session中的Code
        HttpSession session = request.getSession();
        String sysCode = (String)session.getAttribute("sysCode");
        if(sysCode.equalsIgnoreCase(code)) {
            if(username.equals("admin") && password.equals("admin")) {
                session.setAttribute("username", username);
                request.getRequestDispatcher("/success.jsp").forward(request, response);
            } else {
                //登录失败信息
                String msg = "用户名或密码错误";
                request.setAttribute("msg", msg);
                request.getRequestDispatcher("/login.jsp").forward(request, response);
            }
        } else {
            String msg = "验证码输入错误";
            request.setAttribute("msg", msg);
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
3.7.3、单击刷新验证码

在项目中引入jquery,修改登录页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录title>
    <script src="<%=request.getContextPath() %>/js/jquery-3.4.1.min.js">script>
    <script>
        $(function () {
            $("#codeImg").click(function () {
                var date = new Date();
                $("#codeImg").prop("src", "<%=request.getContextPath() %>/CodeServlet?timeStamp=" + date.getTime());
            });
        });
    script>
head>
<body>
<%
    String errorMsg = (String)request.getAttribute("errorMsg");
    if(errorMsg != null) {
%>
    <p style="color: red;"><%=errorMsg %>p>
<%
    }
%>
<form action="<%=request.getContextPath() %>/LoginServlet" method="post">
    <fieldset style="width: 300px;">
        <legend>用户登录legend>
        <p>
            <label>账号label>
            <input type="text" name="username" placeholder="请输入用户名" />
        p>
        <p>
            <label>密码label>
            <input type="password" name="password" placeholder="请输入密码" />
        p>
        <p>
            <label>验证码label>
            <input type="text" name="code" placeholder="请输入验证码" />
            <img id="codeImg" src="<%=request.getContextPath() %>/CodeServlet" alt="验证码" />
        p>
        <p>
            <button type="submit">登录button>
            <button type="reset">重置button>
        p>
    fieldset>
form>
body>
html>

04_JSP进阶_EL_JSTL

一、JSP指令

JSP指令用来设置与整个JSP页面相关的属性。

语法格式:指令格式:<%@指令名 attr1="" attr2="" %>

一般都会把JSP指令放到JSP文件的最上方,但这不是必须的。

常用指令:

  • page:定义页面的依赖属性,比如脚本语言、error页面、缓存需求等等;

  • include:包含其他文件;

  • taglib:引入标签库的定义,可以是自定义标签。

1.1、page指令

page指令是最为常用的指定,也是属性最多的属性,page指令没有必须属性,都是可选属性,例如<%@page %>,没有给出任何属性也是可以的。

关于pageEncoding和contentType:

  • pageEncoding
    • 指定当前JSP页面的编码
    • 这个编码是给服务器看的,服务器需要知道当前JSP使用的编码,不然服务器无法正确把JSP编译成java文件
    • 这个编码只需要与真实的页面编码一致即可
  • contentType
    • 设置响应字符流的编码
    • 设置content-type响应头
  • 无论是page指令的pageEncoding还是contentType,它们的默认值都是ISO-8859-1,ISO-8859-1是无法显示中文的,所以JSP页面中存在中文的话,一定要设置这两个属性
  • 两者关系
    • 当pageEncoding和contentType只出现一个时,那么另一个的值与出现的值相同。
    • 如果两个都不出现,那么两个属性的值都是ISO-8859-1。

import属性:对应java代码中的import语句,用来导入包。

1.2、include指令

  • include指令表示静态包含,即目的是把多个JSP合并成一个JSP文件。
  • include指令只有一个属性:file,指定要包含的页面
  • a.jsp页面中使用include指令包含了b.jsp,那么在编译a.jsp时,会把两个文件合并成一个文件再编译成.java。

1.3、taglib指令

学习jstl标签使用,后面再讲

二、JSP九大内置对象

2.1、简要说明

JSP内置对象:在JSP中无需创建就可以使用的9个对象。

九大内置对象如下

  • out(JspWriter):等同与response.getWriter(),用来向客户端发送文本数据
  • config(ServletConfig):对应”真身”中的ServletConfig
  • page(当前JSP的真身类型):当前JSP页面的“this”,即当前对象
  • pageContext(PageContext):页面上下文对象,它是最后一个没讲的域对象
  • exception(Throwable):只有在错误页面中可以使用这个对象
  • request(HttpServletRequest):即HttpServletRequest类的对象(注意)
  • response(HttpServletResponse):即HttpServletResponse类的对象(注意)
  • application(ServletContext):即ServletContext类的对象(注意)
  • session(HttpSession):即HttpSession类的对象,不是每个JSP页面中都可以使用,如果在某个JSP页面中设置<%@page session=”false”%>,说明这个页面不能使用session

使用情况

  • 极少使用:config、page、exception
  • 不是每个JSP页面都可以使用:exception、session

2.2、pageContext

主要功能:

  • 域对象功能
  • 代理其它域对象功能
  • 获取其他内置对象
2.2.1、域对象功能

表示当前页面 和其他域对象一样,他们都有共同的方法:

  • void setAttribute(String name, Object value)
  • Object getAttribute(String name)
  • void removeAttribute(String name)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>测试Page域对象title>
head>
<body>
    <%
        //在page域中存放数据
        pageContext.setAttribute("name", "zhangsan");
    %>

    <%
        //从page域中获取数据
        System.out.println(pageContext.getAttribute("name"));
    %>
body>
html>
2.2.2、代理其他域对象

可以使用pageContext向request、session、application对象中存取数据,“一个顶四个”

void setAttribute(String name, Object value, int scope):在指定范围中添加数据

Object getAttribute(String name, int scope):获取指定范围的数据

void removeAttribute(String name, int scope):移除指定范围的数据

pageContext.setAttribute("x", "X");
pageContext.setAttribute("x", "XX", PageContext.REQUEST_SCOPE);
pageContext.setAttribute("x", "XXX", PageContext.SESSION_SCOPE);
pageContext.setAttribute("x", "XXXX", PageContext.APPLICATION_SCOPE);

创建Servlet

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "MServlet", value = "/MServlet")
public class MServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取Session
        HttpSession session = request.getSession();
        //在Session域中存放数据
        session.setAttribute("name", "lisi");
    }

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

创建测试的JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Titletitle>
head>
<body>
    <%
        //通过page域从session中获取数据
        Object name = pageContext.getAttribute("name", PageContext.SESSION_SCOPE);
        System.out.println(name);
    %>
body>
html>

Object findAttribute(String name):依次在page、request、session、application范围查找名称为name的数据,如果找到就停止查找。这说明在这个范围内有相同名称的数据,那么page范围的优先级最高

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head>
<title>Insert title heretitle>
head>
<body>
    <%
    
        pageContext.setAttribute("key", "page_value");
        request.setAttribute("key", "request_value");
        session.setAttribute("key", "session_value");
        application.setAttribute("key", "app_value");
    %>
    
    <%
        //全域查找
        String value = (String)pageContext.findAttribute("key");
        out.print(value);
    %>
body>
html>
2.2.3、获取其他内置对象

一个pageContext对象等于所有内置对象,即1个当9个。这是因为可以使用pageContext对象获取其它8个内置对象。

JspWriter getOut():获取out内置对象

ServletConfig getServletConfig():获取config内置对象

Object getPage():获取page内置对象

ServletRequest getRequest():获取request内置对象

ServletResponse getResponse():获取response内置对象

HttpSession getSession():获取session内置对象

ServletContext getServletContext():获取application内置对象

Exception getException():获取exception内置对象

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Titletitle>
head>
<body>
    <%
        //获取application对象
        System.out.println(pageContext.getServletContext().getContextPath());
    %>
body>
html>

三、JSP动作标签(了解)

动作标签的作用是用来简化Java脚本的,JSP动作标签是JavaWeb内置的动作标签,它们是已经定义好的动作标签,我们可以拿来直接使用。

3.1、include标签

语法:

作用:包含其它JSP页面

与include指令区别:

  • include指令是在编译级别完成的包含,即把当前JSP和被包含的JSP合并成一个JSP,然后再编译成一个Servlet;
  • include动作标签是在运行级别完成的包含,即当前JSP和被包含的JSP都会各自生成Servlet,然后在执行当前JSP的Servlet时完成包含另一个JSP的Servlet。它与RequestDispatcher的include()方法是相同的。

被包含的JSP:a.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Titletitle>
head>
<body>
    <p>11111111111p>
body>
html>

b.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Titletitle>
head>
<body>
    <jsp:include page="a.jsp" />
    <p>222222222222p>
body>
html>

3.2、forward

forward标签的作用是请求转发!forward标签的作用与RequestDispatcher.forward()方法相同

四、EL表达式

4.1、概述

EL:Expression Language,表达式语言。在JSP页面中可以直接使用,从JSP2.0开始,代替JSP脚本,非Java开发人员也可以使用。

4.2、EL表达式使用

  • 用于替换作用域对象.getAttribute("name"), 并将从域中获取的数据进行显示;
  • EL用来代替<%=...%>,<%=...%>代表输出。
4.2.1、EL表达式应用(获取基本类型、字符串)

${scope.name}获取具体某个作用域中的数据;

${name}获取作用域中的数据,逐级查找(pageContext、request、session、application)

EL和JSP脚本的区别

  • <%=request.getAttribute() %>没有找到返回null
  • ${requestScope.name}没找到返回""
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>el初步title>
head>
<body>
    <%
        //在request域总存放数据 String
        pageContext.setAttribute("name", "Bob");
        request.setAttribute("name", "Zhangsan");
        request.setAttribute("age", 10);
        session.setAttribute("name", "Jim");
        application.setAttribute("name", "Lucy");
    %>

    <%-- 使用EL表达式获取某个域中的数据并在网页上显示
        作用域 xxxScope
     --%>
    <p>${requestScope.name}p>
    <p>${requestScope.age}p>
    <hr/>
    <%--
        全域查找
            如果没有限定xxxScope,会按照pageContext,request,session,application的顺序进行查找
     --%>
    <p>${name}p>
    <hr/>
    <%--
        JSP脚本和EL表达式的区别
     --%>
    <p><%=request.getAttribute("abc")%>p>
    <p>${requestScope.abc}p>
    <hr/>
body>
html>
4.2.2、EL表达式应用(获取引用类型)

使用EL获取作用域中的对象调用属性时,只能访问对象的get方法,必须遵守命名规范定义

创建实体类

/**
 * 表示Person的实体类
 */
public class Person {
    private Integer id;
    private String name;
    private Integer age;

    //set,get
    //toString
}

EL表达式演示

<%@ page import="com.qf.entity.Person" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.HashMap" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>el表达式处理复杂类型title>
head>
<body>
    <%
        Person p = new Person();
        p.setId(100);
        p.setName("Tom");
        p.setAge(20);

        Person p1 = new Person();
        p1.setId(200);
        p1.setName("zs");
        p1.setAge(21);
        //将Person对象存放在域当中
        request.setAttribute("person", p);

        int[] arr = {1, 2, 100, 50};
        request.setAttribute("arr", arr);

        List<String> names = new ArrayList<>();
        names.add("zs");
        names.add("ls");
        names.add("ww");
        request.setAttribute("names", names);

        List<Person> persons = new ArrayList<>();
        persons.add(p);
        persons.add(p1);
        request.setAttribute("persons", persons);

        Map<String, Object> map = new HashMap<>();
        map.put("name", "zs");
        map.put("addr", "qd");
        request.setAttribute("map", map);
    %>
    <%--
        通过EL表达式在页面上显示对象中的属性
        前提:属性要有对应的set和get方法
     --%>
    <p>${requestScope.person.id}p>
    <p>${requestScope.person.name}p>
    <p>${requestScope.person.age}p>
    <hr/>
    <%--
        int[]
            1 2 100 1000
        List<String>
            "111" "222" "333"
        List<Person>
        Map<String, Object>
    --%>
    <%--
        通过EL表达式在页面上显示数组中的元素
    --%>
    <p>${requestScope.arr[3]}p>
    <hr/>
    <%--
        通过EL表达式在页面上显示集合中的元素,集合中存放的是简单类型(基本数据类型 + String)
    --%>
    <p>${names[2]}p>
    <hr/>

    <%--
        通过EL表达式在页面上显示集合(List, Map)中的元素,集合中存放的是复杂类型(除了String类型之外的引用数据类型)
    --%>
    <p>${persons[0].id}p>
    <p>${persons[0].name}p>
    <p>${persons[0].age}p>
    <hr/>
    <p>${map.name}p>
    <p>${map.addr}p>
    <p>${map["addr"]}p>
    <hr/>
body>
html>

4.3、EL表达式运算符

操作符 描述
. 访问一个Bean属性或者一个映射条目
[] 访问一个数组或者集合的元素
+
- 减或负
*
/ or div
% or mod 取模
== or eq 测试是否相等
!= or ne 测试是否不等
< or lt 测试是否小于
> or gt 测试是否大于
<= or le 测试是否小于等于
>= or ge 测试是否大于等于
&& or and 测试逻辑与
or or
! or not 测试取反
empty 测试是否空值
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>el_运算符title>
head>
<body>
    <%
        request.setAttribute("num", 15);
        request.setAttribute("name", "");
    %>

    <%-- el运算符 --%>
    <p>${num + 1}p>
    <p>${num - 1}p>
    <p>${num * 10}p>
    <p>${num / 10}p>
    <p>${num div 10}p>
    <p>${num % 3}p>
    <p>${num mod 3}p>
    <hr/>
    <p>${num == 15}p>
    <p>${num eq 15}p> <%-- eq equals --%>
    <p>${num != 15}p>
    <p>${num ne 15}p><%-- ne not equals --%>
    <p>${num lt 20}p><%-- lt less than --%>
    <p>${num gt 20}p><%-- gt great than --%>
    <hr/>
    <p>${true or false}p>
    <hr/>
    <p>${empty name}p>
body>
html>

关于empty关键字

<% 
    String s1="";
    pageContext.setAttribute("s1", s1);
    String s2=null;
    pageContext.setAttribute("s2", s2);
    String s3="abcdefg";
    pageContext.setAttribute("s3", s3);
    List list1 = new ArrayList();
    pageContext.setAttribute("list1", list1);
%>

${empty s1}<br>
${empty s2}<br>
${empty s3}<br>
${empty list1}<br>

4.4、EL的隐式对象

EL 表达式语言定义了11个隐式对象

隐含对象 描述
pageScope page作用域
requestScope request作用域
sessionScope session作用域
applicationScope application作用域
param request对象的参数,字符串
paramValues request对象的参数,字符串集合
header HTTP信息头,字符串
headerValues HTTP信息头,字符串集合
initParam 上下文初始化参数
cookie Cookie值
pageContext 当前页面的pageContext域对象
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Titletitle>
head>
<body>
    <%--
        访问服务器某个位置的时候
        协议://主机名:端口  http://localhost:8080
        项目名:在实际开发中不能写死 - “动”
        资源的位置
    --%>
    <%--<a href="/el_jstl/loginServlet?username=bob">登录a>--%>
    <a href="${pageContext.request.contextPath}/loginServlet?username=bob">登录a>
body>
html>

使用EL表达式修改登录案例

login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录title>
head>
<body>
<%
    String errorMsg = (String)request.getAttribute("errorMsg");
    if(errorMsg != null) {
%>
    <p style="color: red;">${errorMsg}p>
<%
    }
%>
<form action="${pageContext.request.contextPath}/LoginServlet" method="post">
    <fieldset style="width: 300px;">
        <legend>用户登录legend>
        <p>
            <label>账号label>
            <input type="text" name="username" placeholder="请输入用户名" />
        p>
        <p>
            <label>密码label>
            <input type="password" name="password" placeholder="请输入密码" />
        p>
        <p>
            <label>验证码label>
            <input type="text" name="code" placeholder="请输入验证码" />
            <img src="${pageContext.request.contextPath}/CodeServlet" alt="验证码">
        p>
        <p>
            <button type="submit">登录button>
            <button type="reset">重置button>
        p>
    fieldset>
form>
body>
html>

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>successtitle>
head>
<body>
    <%
        String username = (String) session.getAttribute("username");
        if(username != null) {
    %>
    <p>欢迎${username}p>
    <p><a href="${pageContext.request.contextPath}/LogoutServlet">注销a>p>
    <%
        } else {
    %>
    <p>您还没有登录,请先<a href="${pageContext.request.contextPath}/login.jsp">登录a>p>
    <%
        }
    %>
body>
html>

五、JSTL

5.1、目前存在的问题

EL主要是用于作用域获取数据,虽然可以做运算判断,但是得到的都是一个结果,做展示;

EL不存在流程控制。比如判断;

EL对于集合只能做单点访问,不能实现遍历操作。比如循环。

5.2、什么是JSTL

JSTL是apache对EL表达式的扩展(也就是说JSTL依赖EL),JSTL是标签语言;

不是JSP的内置标签,使用时需要导包

5.3、JSTL的作用

可对EL获取到的数据进行逻辑操作;

与EL合作完成数据的展示。

5.4、如何使用JSTL

导入Jar包,standard.jar 和 jstl.jar;
在JSP页面引入标签库<% @taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c">

5.5、JSTL核心标签

5.5.1、输入输出

out标签

  • value:可以是字符串常量,也可以是EL表达式
  • default:当要输出的内容为null时,会输出default指定的值

<c:out value="aaa"/> 

<c:out value="${aaa}"/> 

<c:out value="${aaa}" default="xxx"/>

set标签


<c:set var="a" value="hello"/> 

<c:set var="a" value="hello" scope="session"/> 

remove标签


<c:remove var="a"/> 

<c:remove var="a" scope="page"/> 

案例

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>jstl输入输出title>
head>
<body>
    <%--
        JSTL
        增强EL表达式的功能,实现复杂的逻辑操作
     --%>
    <%-- 输出 --%>
    <p><c:out value="hello world"/>p>
    <p>hello worldp>
    <hr/>
    <%-- 定义变量
        int age = 10;
        在域对象中存放数据,默认在page域中存放
        scope:指定数据存放在哪个域中
     --%>
    <c:set var="name" value="Zhangsan" />
    <p>${pageScope.name}p>

    <c:set var="age" value="10" scope="application" />
    <p>${age}p>
body>
html>
5.5.2、分支结构

if标签


<c:if test="${条件}"> 
    hello
c:if>

choose标签


<c:choose>
    <c:when test="${条件1}">ac:when>
    <c:when test="${条件2}">bc:when>
    <c:when test="${条件3}">cc:when>
    <c:otherwise>dc:otherwise>
c:choose>

案例

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>jstl分支结构title>
head>
<body>
    
    <c:set var="num" value="10" />
    <c:if test="${num gt 2}">
        ${num}大于2是成立的
    c:if>
    <hr/>

    <%-- 判断是否及格 --%>
    <c:set var="score" value="80" />
    <c:choose>
        <c:when test="${score >= 60}">
            <p>及格p>
        c:when>
        <c:otherwise>
            <p>不及格p>
        c:otherwise>
    c:choose>
    <hr/>
    <c:choose>
        <c:when test="${score >= 80}">
            <p>优秀p>
        c:when>
        <c:when test="${score >= 70}">
            <p>良好p>
        c:when>
        <c:when test="${score >= 60}">
            <p>及格p>
        c:when>
        <c:otherwise>
            <p>不及格p>
        c:otherwise>
    c:choose>
body>
html>
5.5.3、循环结构

forEach标签


<c:forEach var="i" begin="1" end="10" step="1">
    ${i}
c:forEach>



<c:forEach items="${strs }" var="str">
    ${str }<br/>
c:forEach>

案例

<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page import="com.qf.entity.Person" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>JSTL循环结构title>
head>
<body>
    <%--
        循环 - 反复做某件事情
        1.输出1 2 3 ... 5
        for(int i = 1; i <= 5; i++) {
            System.out.println(i);
        }
    --%>
    <c:forEach var="i" begin="1" end="5" step="1">
        <p>${i}p>
    c:forEach>
    <hr/>

    <%--
        1+2+3+...+100

        int sum = 0;
        for(int i = 1; i <= 100; i++) {
            sum = sum + i;
        }
        System.out.println(sum);

        2+4+6+8+...+100
    --%>
    <c:set var="sum" value="0" />
    <c:forEach var="i" begin="2" end="100" step="2">
        <c:set var="sum" value="${sum + i}" />
    c:forEach>
    <p>${sum}p>
    <hr/>

    <%-- 通过jstl遍历一个集合,在h1标题中显示
        假设集合从servlet中保存到request域中,然后转发到该页面
     --%>
    <%
        List<String> list = new ArrayList<>();
        list.add("1111");
        list.add("2222");
        list.add("333");
        list.add("******");

        request.setAttribute("list", list);
    %>
    <%--
        items:表示被遍历的集合
        var:每一次被遍历到的元素

        int[] arr = {1, 2, 3};
        for(int i : arr) {

        }
    --%>
    <c:forEach items="${list}" var="item">
        <h1>${item}h1>
    c:forEach>

    <hr/>
    <%
        //模拟
        List<Person> persons = new ArrayList<>();
        Person p = new Person();
        p.setId(100);
        p.setName("Tom");
        p.setAge(20);

        Person p1 = new Person();
        p1.setId(200);
        p1.setName("zs");
        p1.setAge(21);

        persons.add(p);
        persons.add(p1);

        request.setAttribute("persons", persons);
        //emp
    %>

    <table border="1">
        <tr>
            <th>idth>
            <th>nameth>
            <th>ageth>
            <th colspan="2">optth>
        tr>
        <c:forEach items="${persons}" var="p">
            <tr>
                <td>${p.id}td>
                <td>${p.name}td>
                <td>${p.age}td>
                <td><a href="${pageContext.request.contextPath}/xxxServlet?id=${p.id}">修改a>td>
                <td><a href="${pageContext.request.contextPath}/aaaServlet?id=${p.id}">删除a>td>
            tr>
        c:forEach>
    table>
body>
html>

5.6、登录案例升级

修改login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<head>
    <title>登录title>
head>
<body>
<p style="color: red;">${errorMsg}p>
<form action="${pageContext.request.contextPath}/LoginServlet" method="post">
    <fieldset style="width: 300px;">
        <legend>用户登录legend>
        <p>
            <label>账号label>
            <input type="text" name="username" placeholder="请输入用户名" />
        p>
        <p>
            <label>密码label>
            <input type="password" name="password" placeholder="请输入密码" />
        p>
        <p>
            <label>验证码label>
            <input type="text" name="code" placeholder="请输入验证码" />
            <img src="${pageContext.request.contextPath}/CodeServlet" alt="验证码">
        p>
        <p>
            <button type="submit">登录button>
            <button type="reset">重置button>
        p>
    fieldset>
form>
body>
html>

修改success.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
  Created by IntelliJ IDEA.
  User: Maxwell
  Date: 2021/09/08
  Time: 17:31
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>successtitle>
head>
<body>
    <c:choose>
        <c:when test="${not empty username}">
            <p>欢迎${username}p>
            <p><a href="${pageContext.request.contextPath}/LogoutServlet">注销a>p>
        c:when>
        <c:otherwise>
            <p>您还没有登录,请先<a href="${pageContext.request.contextPath}/login.jsp">登录a>p>
        c:otherwise>
    c:choose>
body>
html>

5.7、MVC设计模式

5.7.1、经典的MVC

MVC是软件工程中的一种架构模式,是一种软件设计思想,将数据操作、页面展示、业务逻辑分为三个层级(模块),独立完成,相互调用 ,MVC并不是Java独有的,现在几乎所有的B/S的架构都采用了MVC模式,三个层级如下:

  • 视图View:视图即是用户看到并与之交互的界面,比如HTML(静态资源),JSP(动态资源)等等;
  • 控制器Controller:控制器即是控制请求的处理逻辑,对请求进行处理,负责流程跳转(转发和重定向);
  • 模型Model:对客观世界的一种代表和模拟(业务模拟、对象模拟)。

优点:

  • 低耦合性:模块与模块之间的关联性不强,不与某一种具体实现产生密不可分的关联性;
  • 高维护性:基于低耦合性,可做到不同层级的功能模块灵活更换、插拔;
  • 高重用性:相同的数据库操作,可以服务于不同的业务处理。将数据作为独立模块,提高重用性。
5.7.2、JavaWeb经典三层框架

WEB层:包含JSP和Servlet等与WEB相关的内容

业务层:业务层中不包含JavaWeb API,它只关心业务逻辑

数据层:封装了对数据库的访问细节,进行最细粒度的增删改查的操作

调用关系:web层调用—业务层(Service)—数据层(Dao)—DB

  • 业务处理从前到后
  • 开发时要从后向前进行

关于业务:

  • 转账
  • 对DAO层方法的组合

注意:

  • 业务层(Service)不要出现Java Web API,业务层代码是可重用的,甚至可以应用到非Web环境中;
  • 业务层不要出现JDBC相关的API;
  • JavaBean作为实体类贯穿web层、业务层、数据层,各层之间通过JavaBean或者JavaBean的组合进行数据交互。
5.7.3、基于JavaWeb三层架构升级登录案例

准备工作:

  • 建库建表
  • 新建项目
  • 拷贝Jar包到项目的WEB-INF发lib目录下
  • 准备jdbc配置文件,放置在src下
  • JdbcUtils工具类放在utils包下。
5.7.3.1、建库建表

SQL语句如下

CREATE DATABASE webtest;
USE webtest;

DROP TABLE IF EXISTS user;
CREATE TABLE user  (
  id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(20),
  password varchar(20),
  PRIMARY KEY (id)
);

INSERT INTO user VALUES (1, 'zhangsan', '123456');
INSERT INTO user VALUES (2, 'lisi', '123456');

对应的实体类User

public class User {
    private Integer id;
    private String username;
    private String password;
    
    //set和get
    //toString
}
5.7.3.2、Dao层

Dao层接口

import com.qfedu.entity.User;
import java.sql.SQLException;

public interface UserDao {
    User findByUsernameAndPassword(String username, String password) throws SQLException;
}

Dao层接口实现类

import com.qfedu.dao.UserDao;
import com.qfedu.entity.User;
import com.qfedu.utils.JdbcUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;

import java.sql.SQLException;

public class UserDaoImpl implements UserDao {
    @Override
    public User findByUsernameAndPassword(String username, String password) throws SQLException {
        String sql = "select * from user where username=? and password=?";
        Object[] params = {username, password};

        QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());
        User user = qr.query(sql, new BeanHandler<User>(User.class), params);
        return user;
    }
}
5.7.3.3、Service层

Service层接口

import com.qfedu.entity.User;

public interface UserService {
    User login(String username, String password);
}

Service层实现类对象

import com.qfedu.dao.UserDao;
import com.qfedu.dao.impl.UserDaoImpl;
import com.qfedu.entity.User;
import com.qfedu.service.UserService;

import java.sql.SQLException;

public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl();

    @Override
    public User login(String username, String password) {
        User user = null;
        try {
            user = userDao.findByUsernameAndPassword(username, password);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }

        return user;
    }
}
5.7.3.4、Servlet

用于登录验证的Servlet

import com.qfedu.entity.User;
import com.qfedu.service.UserService;
import com.qfedu.service.impl.UserServiceImpl;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "LoginServlet", value = "/LoginServlet")
public class LoginServlet extends HttpServlet {
    private UserService userService = new UserServiceImpl();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取登录信息
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String code = request.getParameter("code");

        HttpSession session = request.getSession();
        //使用用户输入的验证码和生成的验证码进行比较
        String generateCode = (String)session.getAttribute("generateCode");
        //忽略大小写比较强
        if(generateCode.equalsIgnoreCase(code)) {
            //登录验证
            User user = userService.login(username, password);

            if(user != null) {
                //将成功登录的用户信息放在Session中
                session.setAttribute("username", "admin");
                request.getRequestDispatcher("/success.jsp").forward(request, response);
            } else {
                request.setAttribute("errorMsg", "用户名或密码错误,请重新登录");
                request.getRequestDispatcher("/login.jsp").forward(request, response);
            }
        } else {
            request.setAttribute("errorMsg", "验证码错误,请重新登录...");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }

    }

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

用于生成验证码的Servlet

import cn.dsna.util.images.ValidateCode;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "CodeServlet", value = "/CodeServlet")
public class CodeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //生成验证码
        ValidateCode codeImg = new ValidateCode(200, 30, 4, 5);
        String code = codeImg.getCode();
        System.out.println(code);

        //将生成的验证码存放在session中
        request.getSession().setAttribute("generateCode", code);

        //发送给浏览器
        codeImg.write(response.getOutputStream());
    }

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

用于退出登录的Servlet

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "LogoutServlet", value = "/LogoutServlet")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //让session失效
        request.getSession().invalidate();
        //重定向到login.jsp
        response.sendRedirect(request.getContextPath() + "/login.jsp");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
5.7.3.5、相关页面

登录页面login.jsp

注意这个页面中引入jquery用于实现单击刷新验证码,如果加载jquery不能运行成功,删除out目录重试。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<head>
    <title>登录title>
    <script src="${pageContext.request.contextPath}/js/jquery-3.4.1.min.js">script>
    <script>
        $(function () {
            //单击验证码图片,刷新验证码
            $("#codeImg").click(function () {
                var date = new Date();
                $("#codeImg").prop("src", "${pageContext.request.contextPath}/CodeServlet?timeStamp=" + date.getTime());
            });
        });
    script>
head>
<body>
<p style="color: red;">${errorMsg}p>
<form action="${pageContext.request.contextPath}/LoginServlet" method="post">
    <fieldset style="width: 300px;">
        <legend>用户登录legend>
        <p>
            <label>账号label>
            <input type="text" name="username" placeholder="请输入用户名" />
        p>
        <p>
            <label>密码label>
            <input type="password" name="password" placeholder="请输入密码" />
        p>
        <p>
            <label>验证码label>
            <input type="text" name="code" placeholder="请输入验证码" />
            <img id="codeImg" src="${pageContext.request.contextPath}/CodeServlet" alt="验证码" />
        p>
        <p>
            <button type="submit">登录button>
            <button type="reset">重置button>
        p>
    fieldset>
form>
body>
html>

成功页面success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>successtitle>
head>
<body>
    <c:choose>
        <c:when test="${not empty username}">
            <p>欢迎${username}p>
            <p><a href="${pageContext.request.contextPath}/LogoutServlet">注销a>p>
        c:when>
        <c:otherwise>
            <p>您还没有登录,请先<a href="${pageContext.request.contextPath}/login.jsp">登录a>p>
        c:otherwise>
    c:choose>
body>
html>

05_Filter

一、什么是Filter

JavaWeb三大组件(Servlet,Filter,Listener)之一;

与Servlet类似,用来拦截请求,不是用来处理请求的;

过滤器就是一个Java类,实现Filter接口。
JavaWeb_第30张图片

执行地位在Servlet之前,客户端发送请求时,会先经过Filter,再到达目标Servlet中;响应时,会根据执行流程再次反向执行Filter。可以解决多个Servlet共性代码的冗余问题(例如:乱码处理、登录验证)

二、Filter入门

2.1、编写Filter

Servlet API中提供了一个Filter接口,开发人员编写一个Java类实现了这个接口即可,这个Java类称之为过滤器。

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

public class TestFilter implements Filter {
    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("filter....");
        chain.doFilter(request, response);
    }
}

编写Servlet用来验证Filter

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "TestServlet", value = "/TestServlet")
public class TestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("servlet...");
    }

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

2.2、配置Filter

在web.xml中配置Filter,类似在web.xml中配置Servlet


<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_3_1.xsd"
         version="3.1">
    <filter>
        
        <filter-name>TestFilterfilter-name>
        
        <filter-class>com.qfedu.filter.TestFilterfilter-class>
    filter>
    <filter-mapping>
        
        <filter-name>TestFilterfilter-name>
        
        <url-pattern>/TestServleturl-pattern>
    filter-mapping>
web-app>

启动项目,访问TestServlet,发现先运行TestFilter再运行TestServlet。

三、Filter的生命周期

init在服务器启动时会创建Filter实例,并且每个类型的Filter只创建一个实例,从此不再创建,在创建完Filter实例后,会马上调用init()方法完成初始化工作,这个方法只会被执行一次;

doFilter:这个方法会在用户每次访问目标资源(web.xml文件配置Filter的url-pattern中的路径)时执行,如果需要“放行”,那么需要调用FilterChain的doFilter(ServletRequest,ServletResponse)方法,如果不调用FilterChain的doFilter()方法,那么目标资源将无法执行;

destroy:服务器会在创建Filter对象之后,把Filter放到缓存中一直使用,通常不会销毁它。一般会在服务器关闭时销毁Filter对象,在销毁Filter对象之前,服务器会调用Filter对象的destory()方法。

修改TestFilter的代码,进行测试

public class TestFilter implements Filter {
    public void init(FilterConfig config) throws ServletException {
        System.out.println("init...");
    }

    public void destroy() {
        System.out.println("destroy...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("filter....");
        chain.doFilter(request, response);
    }
}

四、Filter的配置

Filter的配置和Servlet的配置类似,分为xml和注解两种配置方式。

4.1、xml配置


<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_3_1.xsd"
         version="3.1">
    <filter>
        
        <filter-name>TestFilterfilter-name>
        
        <filter-class>com.qfedu.filter.TestFilterfilter-class>
    filter>
    <filter-mapping>
        
        <filter-name>TestFilterfilter-name>
        
        <url-pattern>/TestServleturl-pattern>
    filter-mapping>
web-app>

4.2、注解配置

在自定义的Filter类上使用注解@WebFilter,注解常用属性:

  • filterName:filter的名字;
  • value:过滤的目标资源的地址。
    如果@WebFilter只写了字符串,这个字符串Filter过滤目标资源的地址。
import javax.servlet.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebFilter(filterName = "AFilter", value = "/AServlet")
public class AFilter implements Filter {
    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("AFilter...");
        chain.doFilter(request, response);
    }
}

对应的Servlet

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "AServlet", value = "/AServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("AServlet...");
    }

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

4.3、过滤器路径

过滤器的过滤路径通常有三种形式:

  • 精确过滤匹配 ,比如/index.jsp、/myservlet1
  • 后缀过滤匹配,比如*.jsp、*.html、*.jpg
  • 通配符过滤匹配/*,表示拦截所有。注意过滤器不能使用/匹配,/aaa/bbb/*允许。

4.4、过滤器链和优先级

4.4.1、过滤器链

客户端对服务器请求之后,服务器调用Servlet之前会执行一组过滤器(多个过滤器),那么这组过滤器就称为一条过滤器链。

每个过滤器实现某个特定的功能,当第一个Filter的doFilter方法被调用时,Web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则Web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

JavaWeb_第31张图片

创建BServlet


import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet(name = "BServlet", value = "/BServlet")
public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("BServlet...");
    }

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

创建BFilter1和BFilter2

//BFilter1
public class BFilter1 implements Filter {
    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("BFilter1...");
        //放行,如果有后续Filter,继续调用后续Filter执行过滤
        chain.doFilter(request, response);
    }
}

//BFilter2
public class BFilter2 implements Filter {
    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("BFilter2...");
        //放行,如果有后续Filter,继续调用后续Filter执行过滤
        chain.doFilter(request, response);
    }
}

在web.xml中分别配置两个Filter


<filter>
    <filter-name>BFilter1filter-name>
    <filter-class>com.qfedu.filter.BFilter1filter-class>
filter>
<filter-mapping>
    <filter-name>BFilter1filter-name>
    <url-pattern>/BServleturl-pattern>
filter-mapping>

<filter>
    <filter-name>BFilter2filter-name>
    <filter-class>com.qfedu.filter.BFilter2filter-class>
filter>
<filter-mapping>
    <filter-name>BFilter2filter-name>
    <url-pattern>/BServleturl-pattern>
filter-mapping>

访问BServlet测试

4.4.2、过滤器优先级

在一个Web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。 优先级:

  • 如果为注解的话,是按照类全名称的字符串顺序决定作用顺序;
  • 如果web.xml,按照 filter-mapping注册顺序,从上往下
  • web.xml配置高于注解方式;
  • 如果注解和web.xml同时配置,会创建多个过滤器对象,造成过滤多次。

关于“注解和web.xml同时配置,会创建多个过滤器对象,造成过滤多次”,见下面的代码(注意注释中的内容)

/**
 * 修改BFilter2代码,开发其注解配置,注意:注解的filterName和web.xml中filter-name值要是不同的
 */
@WebFilter(filterName = "BFilter2x", value = "/BServlet")
public class BFilter2 implements Filter {
    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        //过滤时,打印BFilter2的hash值
        System.out.println("BFilter2...." + this.hashCode());
        chain.doFilter(request, response);
    }
}

测试,访问BServlet,打印如下,两次的hash值不同,说明“注解和web.xml同时配置,会创建多个过滤器对象,造成过滤多次”。

BFilter1...
BFilter2....768904998
BFilter2....703274564
BServlet...

4.5、Filter应用

4.5.1、权限验证

修改登录案例,使用Filter进行权限验证:

  • 登录成功,直接进入success.jsp;
  • 登录失败,跳转到login.jsp,并显示提示信息;
  • 强行访问success.jsp,跳转到login.jsp,并显示提示信息,提示未登录;

用于登录验证的Filter代码如下:

/**
 * 用于登录验证的Filter
 */
@WebFilter(filterName = "LoginFilter", value = {"/success.jsp"})
public class LoginFilter implements Filter {
    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest req = (HttpServletRequest)request;
        HttpServletResponse resp = (HttpServletResponse)response;

        //获取Session
        HttpSession session = req.getSession();
        User user = (User) session.getAttribute("user");
        if(user == null) {
            request.setAttribute("errorMsg", "您还未登录,请先登录");
            request.getRequestDispatcher("/login.jsp").forward(request, response);
        }

        chain.doFilter(request, response);
    }
}

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>successtitle>
head>
<body>
    <p>欢迎${user.username}p>
    <p><a href="${pageContext.request.contextPath}/LogoutServlet">注销a>p>
body>
html>
4.5.2、过滤器解决编码
@WebFilter(filterName = "EncodingFilter", value = "/*")
public class EncodingFilter implements Filter {
    public void init(FilterConfig config) throws ServletException {
    }

    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        //统一处理请求和响应的乱码
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=utf-8");
        chain.doFilter(request, response);
    }
}

06_文件上传

一、简介

在项目中,文件的上传是常见的功能。很多程序或者软件中都经常使用到文件的上传。

  • 邮箱中有附件的上传;
  • 图片上传。

二、什么是文件上传

当用户在前端页面点击文件上传后,用户上传的文件数据提交给服务器端,实现保存。

三、上传的相关限制

3.1、上传对表单的限制

  • method="post"
  • enctype="multipart/form-data"
  • 表单中需要添加文件表单项:
<form action="xxx" method="post" enctype="multipart/form-data">
  用户名;<input type="text" name="username"/><br/>
  照 片:<input type="file" name="photo"/><br/>
  <input type="submit" value="上传"/>
form>

3.2、上传对Servlet的限制

request.getParameter("xxx")这个方法在表单为enctype="multipart/form-data"时,它作废了,它永远都返回null

3.3、如何实现文件上传

3.3.1、相关Jar包
  • commons-fileupload-1.4.jar
  • commons-io-2.2.jar
    它会帮我们解析request中的上传数据,解析后的结果是一个表单项数据封装到一个FileItem对象中。我们只需要调用FileItem的方法即可。
3.3.2、具体步骤
  1. 创建工厂:DiskFileItemFactory factory = new DiskFileItemFactory();

  2. 创建解析器:ServletFileUpload sfu = new ServletFileUpload(factory);

  3. 使用解析器来解析request,得到FileItem集合:

List fileItemList = sfu.parseRequest(request),

FileItem中常用方法:

  • boolean isFormField():是否为普通表单项!返回true为普通表单项,如果为false即文件表单项
  • String getFieldName():返回当前表单项的名称;
  • String getString(String charset):返回表单项的值;
  • String getName():返回上传的文件名称;
  • long getSize():返回上传文件的字节数;
  • InputStream getInputStream():返回上传文件对应的输入流;
  • void write(File destFile):把上传的文件内容保存到指定的文件中。
//创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//创建解析器
ServletFileUpload sfu = new ServletFileUpload(factory);
//使用解析器来解析request,得到FileItem集合
try {
    List<FileItem> list = sfu.parseRequest(request);
    for (FileItem item : list) {
        if(item.isFormField()) {
            String fieldName = item.getFieldName();
            String value = item.getString("utf-8");
            System.out.println(fieldName + ":" + value);
        } else {
            String fileName = item.getName();
            long size = item.getSize();
            System.out.println("文件名:" + fileName);
            System.out.println("文件大小" + size);
            
            ServletContext servletContext = this.getServletConfig().getServletContext();
            String realPath = servletContext.getRealPath("/img");
            
            item.write(new File(realPath + "/" + fileName));
        }
    }
} catch (FileUploadException e) {
    e.printStackTrace();
} catch (Exception e) {
    e.printStackTrace();
}

3.4、需要注意的细节

3.4.1、文件名或普通表单项乱码

如何解决:

  • request.setCharacterEncoding("utf-8");
  • servletFileUpload.setHeaderEncoding("utf-8");

以上两个方案二选一

//创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//创建解析器
ServletFileUpload sfu = new ServletFileUpload(factory);
request.setCharacterEncoding("utf-8");
//sfu.setHeaderEncoding("utf-8");

try {
    //使用解析器来解析request,得到FileItem集合
    List<FileItem> list = sfu.parseRequest(request);
    for (FileItem item : list) {
        if(item.isFormField()) {
            String fieldName = item.getFieldName();
            String value = item.getString("utf-8");
            System.out.println(fieldName + ":" + value);
        } else {
            String fileName = item.getName();
            long size = item.getSize();
            System.out.println("文件名:" + fileName);
            System.out.println("文件大小" + size);
            
            ServletContext servletContext = this.getServletConfig().getServletContext();
            String realPath = servletContext.getRealPath("/img");
            
            item.write(new File(realPath + "/" + fileName));
        }
    }
} catch (FileUploadException e) {
    e.printStackTrace();
} catch (Exception e) {
    e.printStackTrace();
}
3.4.2、同名文件上传问题

使用UUID:fileName = UUID.randomUUID().toString().replace("-", "") + "_" + fileName;

//创建工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
//创建解析器
ServletFileUpload sfu = new ServletFileUpload(factory);
request.setCharacterEncoding("utf-8");
//sfu.setHeaderEncoding("utf-8");

try {
    //使用解析器来解析request,得到FileItem集合
    List<FileItem> list = sfu.parseRequest(request);
    for (FileItem item : list) {
        if(item.isFormField()) {
            String fieldName = item.getFieldName();
            String value = item.getString("utf-8");
            System.out.println(fieldName + ":" + value);
        } else {
            String fileName = item.getName();
            long size = item.getSize();
            System.out.println("文件名:" + fileName);
            System.out.println("文件大小" + size);
            
            ServletContext servletContext = this.getServletConfig().getServletContext();
            String realPath = servletContext.getRealPath("/img");
            
            item.write(new File(realPath + 
                                "/" + 
                                UUID.randomUUID().toString().replace("-", "") + 
                                "_" + 
                                fileName));
        }
    }
} catch (FileUploadException e) {
    e.printStackTrace();
} catch (Exception e) {
    e.printStackTrace();
}

你可能感兴趣的:(JavaWeb,java,tomcat)