Servlet

Servlet

webapp:[服务器端的java小程序]


1.关于系统架构

(1)系统架构包括什么形式

  • C/S形式
  • B/S形式

(2)C/S架构

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

  • CS架构的软件或者系统有哪些呢?

    • QQ (先去腾讯官网下载一个QQ软件,然后把客户端软件安装上去,

      然后输入QQ号以及密码,登录之后就可以使用

    • 很多数据集成到客户端里边了

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

  • C/S架构的系统:

    • 优点:
      • 速度快 (软件中的数据大部分是集成在客户端软件中)
      • 体验好 (速度快,界面炫酷,体验就好了)
      • 服务器压力小 (例如游戏客户端: 大部分数据在本地,3D效果等,当然服务器压力小)
      • 安全 (应为大量数据集成在客户端软件当中,即使服务器端出现致命问题,对数据影响也不大,因为大量数据在本地有缓存)
      • 界面炫酷 (专门的语言去实现界面的,更加灵活)
    • 缺点:
      • 升级维护比较差劲:(升级维护比较麻烦,每一个客户端软件都需要升级,有些软件不是那么容易安装的(比如说银行系统,安全系数高,只能运维人员去升级,成本高))

(3)B/S架构

  • B / S ( Browser / Server ,浏览器/服务器)

  • B / S 结构的系统是不是一个特殊的C/S系统?

    • 实际上BS结构的系统还是一个C/S,只不过这个C比较特殊,这个Client是一个固定不变的浏览器软件
  • B / S 结构的系统优点和缺点是

    • 优点:
      • 升级维护方便,成本比较低 (只需要升级服务器端就可以了)
      • 不需要安装特定的客户端软件,用户操作及其简单,用户只用输入网站就可以使用
    • 缺点:
      • 速度慢: (不是因为带宽低的问题,是因为所以的数据都是在服务器,用户发送的每一个请求都是需要服务器响应数据 ,所以B/S结构的系统在网络中传递的数据量比较大)
      • 体验差: (界面不是那么炫酷 , 因为浏览器只支持三种语言 HTML, CSS ,JavaScript,再加上速度慢)
      • 不安全: (所有数据都在服务器上,只要服务器出现问题所有数据都会丢失)

(4)C/S和B/S结构使用场景:

  • 在不同的场景下有不同的优缺点
    • 娱乐性软件:
      • 建议使用C/S
    • 公司内部使用的系统,
      • 公司内部的系统,需要维护成本低
      • 公司内部不需要华丽的界面
      • 公司内部的系统主要是进行数据的维护即可
      • B/S结构

(5)开发技术

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

  • 开发一个WEB系统要会的知识
    • WEB前端 (运行在浏览器的程序)
      • HTML
      • CSS
      • JavaScript
    • WEB后端 (WEB服务器端的程序)
      • Java可以 (java做WEB开发我们称为JavaWEB开发,JavaWEB开发最核心的规范:Servlet [服务器端的java小程序])
      • C语言也可以
      • C++
      • Python
      • PHP

2.javaEE是什么?

①javaSE(基础)

  • java标准版(一套类库:别人写好的一套类库,只不过这个类库是标准类库,是基础)

②javaEE (WEB方向)

  • java企业版(一套类库,只不过这套类库可以帮助我们进行企业级项目的开发,专门为企业内部提供解决方案的多套类库)
  • 用它可以开发企业级项目
  • 可以开发WEB系统
  • java比较火的就是JavaEE方向

③javaME:(微型设备)

  • Java微型版(还是一套类库,只不过这套类库帮助我们进行电子微型设备内核程序的开发)

  • 机顶盒内核程序,吸尘器,电冰箱…等

  • javaEE实际上包括很多种规范:13种规范,其中Servlet就是java规范之一,学Servlet还是学java语言


3.B/S结构的系统通信原理

(1)关于域名:

  • https://www.baidu.com/ (网址)

  • www.baidu.com 是一个域名

  • 在浏览器地址栏输入域名后,回车之后

    域名解析器会解析出来一个具体的IP地址和端口号

  • 解析结果也许是:http://39.156.66.14/index.html

  • IP地址:

    • 计算机在网络中的身份证号,在同一个网络中,IP地址是唯一的
    • A计算机想和B计算机通信,首先需要知道B计算机的IP地址,有了IP地址才能建立连接
  • 端口号:

    • 一个端口代表一个软件(一个端口代表一个应用,一个端口仅代表一个服务)
    • 一个计算机有很多软件,每个启动之后都有一个端口号
    • 在同一台计算机上,端口号具有唯一性

1641742815276

(2)一个WEB系统的通信原理:

  • 步骤:
  • 第一步:用户输入网址( URL )
  • 第二步:域名解析器进行解析:http://39.156.66.14/index.html
  • 第三步:浏览器软件在网络中搜索IP地址为39.156.66.14这台主机,直接找到这台主机
  • 第四步:定位39.156.66.14这台主机上的服务器软件,根据端口号,可以轻松定位到对应的服务器软件
  • 第五步:端口号对应的服务器软件得知浏览器想要的资源名是:index.html
  • 第六步:服务器软件找到index.html文件,并且将index.html文件中的内容直接输出响应到浏览器上
  • 第七步:浏览器接收到来自服务器的代码(HTML , CSS ,JS)
  • 第八步:浏览器渲染,执行(HTML , CSS ,JS)代码,展示效果
①URL:统一资源定位符
②请求 ( request )
③响应 ( response )

4.关于web服务器软件(Tomcat)

(1)web服务器软件都有哪些呢?

  • (这些软件都是以前开发好的)
  • Tomcat (Apache基金会的)
  • jetty
  • JBOSS (应用服务器)
  • WebLogic (应用服务器)
  • WebSphere (应用服务器)

(2)应用服务器和WEB服务器的关系?

  • 应用服务器实现了JavaEE的所有规范 ( 十三个不同的规范 )
  • WEB服务器只实现了JavaEE的Servlet和JSP两个核心规范
  • 应用服务器是包含了WEB服务器的

(3)Tomcat 下载

  • apache官网:https://www.apache.org/
  • tomcat官网:https://tomcat.apache.org/
  • tomcat开源免费的轻量级WEB服务器 (又被称为catalina)
  • tomcat服务器是轻量级的,体积小,运行速度快,只实现了Servlet+JSP规范
  • tomcat是java语言写的,tomcat服务器要是想要运行需要先有jre,
  • 所以要先配置java环境
    • JAVA_HOME=D:\JAVA\jdk-17.0.1
    • PATH=%JAVA_HOME%\bin

(4)Tomcat 服务器安装和运行:

  • 直接zip包解压即可

  • 启动Tomcat服务器:startup.bat (重命名为OK.bat)

  • 关闭服务器:shutdown.bat

    ​ ///重命名为NO.bat (因为shutdown和windows关机命令重复,害怕输入错误)

    • bin目录下有个startup.bat文件,通过他可以启动Tomcat服务器

      • xxx.bat文件时windows操作系统专用的,bat文件是批处理文件,这种文件中可以编写大量的windows的dos命令,然后执行bat文件就相当于批量的执行dos命令

      • startup.sh :这个文件在windows当中无法执行,在linux环境中能够执行的是shell命令,大量的shell命令编写在shell文件当中.然后执行这个shell文件就可以批量执行shell命令

      • Tomcat服务器提供了两套文件,说明可以在windows和linux中使用

      • 分析startup.bat文件,执行这个命令,实际上最后执行的是catalina.bat文件

      • catalina.bat文件中有这样一段代码:

        • MAINCLASS=org.apache.catalina.startup.Bootstrap

          这个类就是main方法所在的类

      • Tomcat服务器是java语言写的,那么启动服务器就是执行main方法

    • 我们打开dos命令窗口,输入startup.bat来启动Tomcat服务器

    • 启动Tomcat服务器只配置path对应的bin目录是不行的,有两个环境变量需要配置:

      • (1)JAVA_HOME=jdk的根
      • (2)CATALINA_HOME=Tomcat的根
      • (3)PATH=%JAVA_HOME%\bin;%CATALINA_HOME%\bin;

(5)测试Tomcat服务器是否启动成功

  • 打开浏览器,在浏览器上输入URL即可:

    • http://IP地址:端口号

      • 端口号是8080

      • IP是本机IP

        本机IP是192.168.0.103

(6)查看Tomcat服务器版本:

  • cmd输入 : catalina version

(7)关于Tomcat服务器的目录

  • bin:这个目录是Tomcat服务器的命令文件存放目录:比如:启动Tomcat ,关闭Tomcat等
  • conf : 这个目录是Tomcat服务器的配置文件存放目录
    • conf中有一个web.xml 是所有项目的父配置文件
  • lib : 目录是Tomcat服务器的核心程序目录,因为Tomcat服务器是用java语言写的,这里的jar包里面都是class文件
  • logs : Tomcat服务器日志目录,Tomcat服务器启动等信息都会在这个目录下生成日志文件
  • temp : Tomcat服务器临时文件目录存储临时文件
  • webapps : 这个目录就是用来存放大量的webapp(web application :web应用)
  • work : 这个是用来存放JSP文件翻译之后的java文件以及编译之后的class文件

(8)解决Tomcat服务器在DOS命令窗口中乱码的问题(控制台乱码)

(1)打开Tomcat服务器根下的conf文件夹下的logging.properties日志文件

​ 将内容修改如下: 将UTF-8改为GBK (因为Windows的DOS命令窗口是GBK的编码方式)


5.实现一个简单的web应用,(没有java小程序):

  • 第一步:找到CATALINA_HOME下的webapps目录

    • 因为所有的webapp要放到webapps目录下
  • 第二步:在CATALINA_HOME下的webapps目录下新建一个子目录 oa

    • 这个目录名oa就是你这个webapp的名字
  • 第三步:在oa目录下新建资源文件,例如index.html

  • 第四步:启动服务器

  • 第五步:浏览器搜索资源

    • ​ http://192.168.0.103:8080/oa/index.html
      • 思考:
        • 我们在地址栏输入一个网址和点击超链接是一样的
        • 用户点击超链接相当于在浏览器输入地址

*在超链接中IP地址和端口号是可以省略的

  • ​ /oa/index.html 绝对路径

  • <!--注意以下的路径,以../开始,是一个绝对路径,不需要加http://192.168.0.103:8080-->
    <a href="../../Tomcat/apache-tomcat-10.0.14/webapps/oa/index.html"></a>
    <!--多个层级没有关系,直接访问即可-->
    

6.静态资源/动态资源

(1)静态资源:

  • 展示的页面数据都是写死在html上的,这种资源我们称为静态资源,
  • 怎么能变成动态资源?显然需要java小程序

(2)动态资源:

  • 连接数据库需要JDBC程序,也就是说要编写java程序来连接数据库,数据库中有多少条记录,页面上就展示多少条记录,这种技术称为动态网页技术(动态网页是说网页中的数据是动态的,随着数据库中数据的变化而变化)

需要知道的是:

后端到底要执行那个java小程序,取决于你前端浏览器发送的请求路径,一个路径对应一个Servlet小程序

(3)角色和角色之间需要遵守那些规范,那些协议?

  • 有哪些角色(在整个BS结构的系统中,有哪些角色参与)

    • 浏览器软件的开发商 (谷歌浏览器,火狐浏览器…)
    • WEB Server的开发团队 (WEB Server这个软件也是太多了 Tomcat , jetty , WebLogin , JBOSS , WebSphere…)
    • DB Server的开发团队 (Oracle , MySQL…)
    • webapp的开发团队 (WEB应用是我们作为javaWEB程序员开发的)

  • webapp的开发团队WEB Server的开发团队之间有一套规范:javaEE规范之一:Servlet规范

  • Servlet规范的作用是什么?

    • WEB Server 和 webapp之间解耦合
  • 浏览器(Browser)和WebServer之间有一套传输协议: HTTP协议: (超文本传输协议)

  • webapp开发团队DB Server的开发团队之间有一套协议:JDBC


7.Servlet规范:

严格意义上来说Servlet其实并不是简单的一个接口:

  • Tomcat服务器要遵守Servlet规范,javaWeb程序员也要遵循这个Servlet规范,这样Tomcat服务器和webapp才能解耦合
  • 遵循了Servlet规范的webapp,这个webapp就可以放在不同的WEB服务器中运行 (因为这个webapp是遵循Servlet规范的)
    • Servlet规范中规定了:
      • 规范了那些接口
      • 规范了哪些类
      • 规范了一个web应用中应该有哪些配置文件
      • 规范了配置文件的名字
      • 一个合格的webapp应该是一个怎样的目录结构
      • 一个合格的webapp配置文件路径放在哪里
      • 一个合格的webapp中java程序放在哪

8.开发一个带Servlet的webapp

(1)开发步骤是什么(重点)

  • **第一步:**在webapps目录下新建一个目录,起名sichen(这个sichen就是项目的名字) ,也可以设置其他名字

    • 注意:sichen就是这个webapp的根
  • **第二步:**在项目目录下,新建一个目录: WEB-INF

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

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

    • 注意:这个目录不是必须的,但是如果一个webapp需要第三方的jar包,这个jar包就要放在这个lib目录下,例如java语言连接数据库需要的数据库jar包 ,那么这个jar包就要放在这个lib目录下,这是Servlet规定的
  • **第五步:**在WEB-INF目录下新建一个文件叫 web.xml

    • 注意:这个文件是必须的 , 这个文件必须叫做 web.xml ,这个文件必须放在这里 ,一个合法的webapp, web.xml文件是必须的,这个文件就是配置文件 ,这个文件描述了请求路径和Servlet类之间的对照关系

    • 这个文件最好从其他的webapp文件中复制,不要自己写复制粘贴即可

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

    • 重点:编写java小程序的时候,java源代码放在哪里都可以,但是java文件的class文件必须放在项目下的classes目录下

    • 注意:这个Servlet接口不在jdk当中(因为Servlet不是javaSE了,Servlet属于JavaEE属于另外一套类库)

      • Servlet接口 (Servlet.class文件) 是Oracle提供的

      • Servlet接口是JavaEE规范中的一员

      • Tomcat服务器实现了Servlet规范,所以Tomcat服务器也需要使用Servlet接口,(所以Tomcat服务器中应该有这个接口的jar包)

      • Tomcat服务器的根目录下 --> lib -->

      • 解压这个servlet-api.jar 在这个路径下会看到一个Servlet.class文件

        • 1641823472506
    • ​ 从jakartaEE9开始,Servlet接口的包全名变了,叫

      ​ jakarta.servlet.Servlet;

  • **第七步:**编译我们编写的HelloServlet.java文件

    • 重点:你怎么能让你的HelloServlet编译通过呢?配置环境变量CLASSPATH==

      .;D:\Tomcat\apache-tomcat-10.0.14\lib\servlet-api.jar

    • 思考问题:让配置的CLASSPATH和Tomcat服务器运行有没有关系?

      • 没有任何关系,以上配置这个环境变量只是为了让你的HelloServlet能够正常编译生成class文件
    • HelloServlet.java

    • package com.sichen.servlet;
      
      import jakarta.servlet.Servlet;
      import jakarta.servlet.ServletException;
      import jakarta.servlet.ServletRequest;
      import jakarta.servlet.ServletResponse;
      import jakarta.servlet.ServletConfig;
      import java.io.IOException;
      
      public class HelloServlet implements Servlet{
          //五个方法:
          public void init(ServletConfig config) throws ServletException{
          }
          public void service(ServletRequest request , ServletResponse response) 
              throws ServletException , IOException{
              System.out.println("Hello,World");
          }
          public void destroy(){   
          }
          public String getServletInfo(){
              return "";
          }
          public ServletConfig getServletConfig(){
              return null;
          }
      }
      
  • **第八步:**将以上编译后的HelloServlet的class文件,连带包名拷贝在webapp文件夹下的classes文件夹下

  • **第九步:**在web.xml文件中编写配置文件,让"请求路径"和"Servlet类名" 关联在一起

    • 这一步用专业的术语描述叫做: 在web.xml文件中注册Servlet类
    • web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                          https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
      version="5.0"
      metadata-complete="true">
    		<!-- Servlet描述信息 -->
    		<!-- 任何一个servlet都对应一个servlet-mapping -->
    		<servlet>
    			<servlet-name>sichen</servlet-name>
    			<!-- 这个位置必须是带有包名的全限定类名java小程序的包名+类名
    				就是package后边的加上类名-->
    			<servlet-class>com.sichen.servlet.HelloServlet</servlet-class>
    		</servlet>
    
    		<!-- servlet映射信息 -->
    			<servlet-mapping>
    			<!-- 这个地方也可以随便写,不过这里写的内容要和上面写的一样 -->
    				<servlet-name>sichen</servlet-name>
    			<!-- 这里需要一个路径 -->
    			<!-- 这个路径唯一的要求就是必须是斜杠/开头 -->
    			<!-- 当前这个路径可以随便写,这个路径就是最后浏览器输入的时候,webapp后边的资源路径,参照第十一步 -->
    				<url-pattern>/ai/sichen</url-pattern>
    			</servlet-mapping>
    		<!-- 执行流程是:	服务器通过 <url-pattern> 找到 对应的<servlet-name> 在通过<servlet-name>找到对应的<servlet-class>-->
      <display-name>Welcome to Tomcat</display-name>
      <description>
         Welcome to Tomcat
      </description>
    </web-app>
    
  • **第十步:**启动Tomcat服务器

  • **第十一步:**打开浏览器,在地址栏上输入一个URL,这个URL必须是

    • http://192.168.0.103:8080/sichen/ai/sichen

    • 非常重要一点:浏览器上的请求路径不能随便写,这个请求路径必须是和web.xml文件中的url-pattern一致

    • 注意:浏览器上的请求路径和web.xml文件中的url-pattern中的请求路径唯一的区别是

      • 浏览器上的请求路径前边要加(项目名)
  • 浏览器上编写的路径太长,可以使用超链接(非常重要:html项目只能放在WEB_INF文件夹之外)

    • index.html

    • <!doctype html>
      <html>
      	<head>
      		<title>sichen</title>
      	</head>
      	<body>
              <!-- 这里不用加http://IP:端口号  但是要加项目名 -->
      		<a href="/sichen/ai/sichen">思尘</a>
      	</body>
      </html>
      
  • 以后我们不需要编写main方法了,Tomcat服务器负责调用main方法,

    Tomcat服务器在启动时,调用的就是main方法

    java程序员只需要编写Servlet接口的实现类,然后将其注册到web.xml文件中即可

  • 总结:一个合法的webapp的目录结构是:

    • webapp
      	|------WEB-INF
      			|------classes  (存字节码,java编译后的文件)
      			|------lib		(第三方jar包)
      			|------web.xml	(注册Servlet)
           |------html
           |------CSS
           |------javaScript
           |------image
           |---.........
      
  • 浏览器发送请求,到最终服务器调用Servlet中的方法,是怎样一个过程(以下这个过程很粗糙)

    • 用户输入URL,或者直接点击超链接http://192.168.0.103:8080/sichen/ai/sichen
    • 然后Tomcat服务器接收到请求后,截取路径:/sichen/ai/sichen
    • Tomcat 服务器找到sichen项目
    • Tomcat服务器在web.xml文件中查找/ai/sichen 对应的Servlet是 com.sichen.servlet.HelloServlet
    • Tomcat服务器通过反射机制,创建com.sichen.servlet.HelloServlet的对象
    • Tomcat服务器调用com.sichen.servlet.HelloServlet这个类

*关于JavaEE的版本:

  • 关于javaEE的版本 目前最高版本是JavaEE8
  • javaEE被Oracle捐献了,Oracle将JavaEE规范捐献给了Apache
  • Apache将javaEE改名了,以后就不叫JavaEE了,
    • 叫做 jakartaEE
  • javaEE版本升级之后的"JavaEE9",不叫这个名字了,叫 jakartaEE9
  • javaEE的时候对应的Servlet类名是: javax.servlet.Servlet;
  • jakartaEE9 的时候对应的Servlet类名是: jakarta.servlet.Servlet (包名都换了)
    • 注意:如果你之前的项目还是在使用javax.servlet.Servlet,那么你的项目无法直接部署到Tomcat10+版本上,你能部署到Tomcat9,以及9之前的版本中

9.Servlet中的五种方法:

package com.sichen.servlet;

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

public class HelloServlet implements Servlet{
    //五个方法:
    //init方法被翻译成初始化
    //init方法只执行一次
    //在AServlet对象第一次被创建之后执行
    //init方法通常是完成初始化操作的
    //init方法在执行的时候Aservlet对象已经被创建了
    public void init(ServletConfig config) throws ServletException{
    }
    //service方法:是处理用户请求的核心方法
    //只要用户发送一次请求,service方法必然会执行一次
    //发送几次,就执行几次
    public void service(ServletRequest request , ServletResponse response) 
        throws ServletException , IOException{
        //向控制台打印
        System.out.println("Hello,World");
    }
    //destroy方法只执行一次
    //Tomcat服务器在销毁AServlet对象之前会调用一次destroy方法
    //destroy在执行的时候.AServlet对象的内存还没有被销毁,即将被销毁
    //destroy方法内可以编写销毁前的准备
    //比如说:服务器关闭的时候,AServlet对象开启了一些设备,这些资源可能是流,可能是数据库连接
    //要关闭这些流和数据库,可以把代码写在这个方法当中
    public void destroy(){
    }
    public String getServletInfo(){
        return "";
    }
    public ServletConfig getServletConfig(){
        return null;
    }
}

(1)向浏览器响应一段html代码(*)

import java.io.PrintWriter;
public void service(ServletRequest request , ServletResponse response) 
        throws ServletException , IOException{
        //向控制台打印
        System.out.println("Hello,World");

    //设置响应的内容类型是普通文本或HTML
    //设置响应的内容类型时,要在获取流对象之前设置,有效
    response.setContentType("text/html");
        
    //怎么将一个数据直接输出在浏览器上:
    //需要使用ServletResponse接口:response
    //response表示响应,从服务器端发送数据到浏览器端叫响应
        //获取浏览器响应流
        PrintWriter out = response.getWriter();
        out.print("HelloServlet , You Are my first Servlet");
    //这是一个输出流,负责输出数据到浏览器,这个输出流不需要我们刷新,也不需要我们关闭,这些都有Tomcat来维护
    	//可以识别html内容
        out.print("<h1>Hello servlet</h1>");
    }

(2)在Servlet中连接数据库:(重点)

  • 在代码中直接编写JDBC即可

  • 不要忘了在web.xml中绑定路径

  • lib中添加mysql的jar包

  • StudentList.java

  • package com.sichen.servlet;
    //注意包名尽量一致,这样到时候往classes中放的时候更容易
    import jakarta.servlet.Servlet;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.ServletRequest;
    import jakarta.servlet.ServletResponse;
    import jakarta.servlet.ServletConfig;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.sql.*;
    
    public class StudentList implements Servlet{
        //五个方法:
        public void init(ServletConfig config) throws ServletException{
        }
        public void service(ServletRequest request , ServletResponse response) 
            throws ServletException , IOException{
          
            //这条语句直接写在最前边  
            response.setContentType("text/hmtl");
            //流:在浏览器页面上响应数据
            PrintWriter pw = response.getWriter();
            
            //编写JDBC代码,连接数据库,查询所有学生信息
    		Connection conn = null;
    		PreparedStatement ps = null;
    		ResultSet rs = null;
    		try{
    			//注册驱动
    			Class.forName("com.mysql.cj.jdbc.Driver");
    			//获取连接
    			String url = "jdbc:mysql://localhost:3306/aisichen";
    			String user = "root";
    			String password = "acpl159357";
    			conn = DriverManager.getConnection(url,user,password);
    			//获取预编译对象
    			String sql = "select id,name,dizhi from t_student";
    			ps = conn.prepareStatement(sql);
    			//执行sql
    			rs = ps.executeQuery();
    			//处理查询结果集
    			while(rs.next()){
    				String id = rs.getString("id");
    				String name = rs.getString("name");
    				String dizhi = rs.getString("dizhi");
    				
                    //将信息打印在浏览器中
    		
                    //可以java和html语言联合使用
    				pw.print("id:"+id+"姓名:"+name+"地址:"+dizhi+"<br>");
    			}
    		}catch(Exception e){
    			e.printStackTrace();
    		}finally{
    			if(rs!=null){
    				try{
    					rs.close();
    				}catch(Exception e){
    					e.printStackTrace();
    				}
    			}
    			if(ps!=null){
    				try{
    					ps.close();
    				}catch(Exception e){
    					e.printStackTrace();
    				}
    			}
    			if(conn!=null){
    				try{
    					conn.close();
    				}catch(Exception e){
    					e.printStackTrace();
    				}
    			}
    		}
        }
        public void destroy(){
        }
        public String getServletInfo(){
            return "";
        }
        public ServletConfig getServletConfig(){
            return null;
        }
    }
    

10.在集成开发工具IDEA开发Servlet程序(重点)

(1)使用IDEA集成开发工具:

  • 第一步:创建一个空的工程; Servlet

  • 第二步:新建模块

    • 这里创建一个普通的javaSE模块(先不要创建javaEnterprise模块)
    • 这个Module自动会被放在Servlet的project下面
    • 这个module名字叫sichen
  • 第三步:让这个Module变成javaEE的模块 (让模块变成webapp的模块,符合Servlet规范的Module)

    • 在Module上点击右键 Add Framework Support…(添加框架支持)
    • 在弹出的窗口中,选择Web Application (选择的是webapp的支持)
    • 选择了这个webapp的支持之后,IDEA会自动生成一个符合Servlet规范的webapp目录结构
    • 重点: 需要注意的是:在IDEA工具中根据Web Application模板生成的目录中**有一个web目录,这个目录就代表webappd的根**
  • 第四步:(非必须): 根据Web Application生成的资源中有一个index.jsp的文件 这里可以删除它

  • 第五步: 编写Servlet (StudentServlet)

    public class StudentServlet implements Servlet
    
    • 这时发现没有Servlet.class文件没有,怎么办? 将servlet-api.jar和jsp-api.jar包添加到IDEA的classpath中
      • File–>Project Structrue --> Modules --> +加号 -->Add JARS
  • 第六步:在Servlet当中的servlet方法当中编写业务代码

  • 第七步:在WEB-INF目录下新建一个lib目录,将连接mysql数据库的jar包放进去

  • 第八步:在web.xml文件中完成StudentServlet类的注册: (请求路径)

  • <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
        <servlet>
            <servlet-name>sichen</servlet-name>
            <servlet-class>com.sichen.servlet.StudentServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>sichen</servlet-name>
            <url-pattern>/servlet/student</url-pattern>
        </servlet-mapping>
    </web-app>
    
  • 第九步:给一个html页面,在html页面中编写一个超连接,用户点击这个超连接,发送请求,Tomcat执行后台的StudentServlet

    • index.html

    • <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <title>思尘</title>
      </head>
      <body>
              <!-- 这里路径是写死的,不能动态更换 -->
        <a href="/sichen/servlet/student">student</a>
      </body>
      </html>
      
  • 第十步:让IDEA工具关联Tomcat服务器,关联的过程当中将webapp部署到Tomcat服务器当中

    • 左上角加号 —>点击Tomcat Server —> local

    • 在弹出的界面上设置服务器的参数 ,(基本上不用动)

    • 在当前窗口中有一个Deployment(点击这个用来部署webapp) —> 加号 —> 工件

    • 1641898934667
    • 这里的上下文设置为index.html文件中写的项目名

    • 1641899059621
    • 修改Application context 为: /sichen

  • 第十一步:启动Tomcat服务器

    • 以DeBug模式启动Tomcat服务器
  • 第十二步:打开浏览器,在地址栏输入: http://localhost:8080/sichen/index.html


11.Servlet对象的生命周期

(1)什么是Servlet对象的生命周期:

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

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

  • Servlet对象的创建,对象上方法的调用,对象最终的销毁,javaweb程序员是无权干预的
  • Servelt对象的生命周期是由Tomcat服务器(WEB Server)全权负责的
  • Tomcat服务器通常我们又称为: WEB容器 (这个叫法你要知道[WEB Container])
  • WEB容器来管理Servlet对象的生命周期

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

  • 我们自己new的Servlet对象是不受WEB容器的管理,

  • WEB容器创建的Servlet对象,都会被放到一个集合当中 (HashMap),只有放到这个HashMap集合当中的Servlet才能被WEB容器管理,自己new的Servlet不会被WEB容器管理 (相当于不在容器当中)

  • WEB容器底层有一个HashMap这样的集合,在这个集合当中存储了Servlet对象和请求路径之间的关系


(4)研究:服务器启动时,Servlet对象有没有被创建出来(默认情况下)?

  • 在Servlet中提供一个无参构造方法,启动服务器的时候,看构造方法是否执行
  • 经过测试得出结论: 默认情况下,服务器在启动的时候Servlet对象并不会被实例化
  • 这个设计是合理的,用户没有发送请求之前,如果提前创建出来所有的Servlet对象,必然是耗费内存的.并且创建出来的Servlet如果一直没有用户访问,显然这个Servlet是一个废物,没必要先创建

(5)怎么让服务器启动的时候创建Servlet对象:

  • 在web.xml文件对应的地方添加:

  • (正整数),

  • 在此标签中填写整数,整数越小优先级越高

  • <servlet>
            <servlet-name>aservlet</servlet-name>
            <servlet-class>com.sichen.javaweb.servlet.AServlet</servlet-class>
        	<!-- 在这里加一个这个标签,中间跟一个正整数,这个整数决定了创建时的顺序,数字越小优先级越高,最小是0 -->	
        	<load-on-startup>(正整数)</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>aservlet</servlet-name>
            <url-pattern>/lujing/a</url-pattern>
        </servlet-mapping>
    
  • 注意:通常情况下,在服务器启动时,是不创建Servlet对象的


(6)Servlet的生命周期: (用户发送请求之后)

  • 默认情况下服务器启动的时候,Aservlet对象并没有被实例化,
  • 用户发送请求后Servlet对象被实例化 (一次)
  • 执行init方法 (一次)
  • 执行service方法 (多次,用户发送一次请求执行一次)
  • 关闭服务器 , 执行destroy方法 (一次)
  • 服务器销毁Servlet对象的内存

①用户发送第一个请求后,控制台输出了以下内容:

  • AServlet无参构造方法执行了
    Aservlet's init method execute!
    Aservlet's service method execute!
    
  • 根据以上输出的内容得出结论:

    • 用户在发送第一次请求的时候,Servlet对象被实例化,(AServlet的构造方法被执行了并且执行的是无参构造方法)

    • AServlet对象被创建出来之后,Tomcat服务器马上调用了AServlet对象的init方法

      (init方法在执行的时候,AServlet的对象已经被创造出来了)

    • init方法执行后,Tomcat服务器立刻执行service方法

②用户发送第二次请求:控制台输出以下内容:

  • Aservlet's service method execute!
    
  • 根据以上输出结果可知,用户在发送第二次,或者多次的时候:Servlet的对象并没有新建,还是使用之前创建好的Servlet对象,直接调用Servlet对象的service方法,这说明:

    • 第一: Servlet对象是单例的 (单实例的 , 但是要注意Servlet对象是单实例的 ,

      但是Servlet类并不符合单例模式 , 我么称之为假单例

      之所以单例是因为Servlet对象的创建我们程序员管不着,这个对象的创建只能是Tomcat说了算 Tomcat只创建了一个,所以导致了单例,但是属于假单例)

      真单例模式,构造方法是私有化的

    • 第二: 无参数构造方法和init方法只在第一次用户请求的时候执行,也就是说,无参数构造方法执行一次,init方法也只被Tomcat服务器调用一次

    • 第三: 只要用户发送一次请求,service方法必然会被Tomcat服务器调用一次,发送一百次请求,调用一百次

③关闭服务器的时候,控制台输出了以下内容:

  • Aservlet's destroy method execute!
    
    • 通过以上输出可以得出以下结论:

      • Servlet中的destroy方法只被Tomcat服务器执行一次

      • destroy方法是在什么时候被调用的?

        • 是在服务器关闭的时候被调用
        • 因为服务器关闭的时候,要销毁AServlet对象的内存
        • 服务器在销毁AServlet对象内存之前,Tomcat服务器会自动调用AServlet对象的destroy方法
      • 研究:destroy方法调用的时候,对象销毁了还是没有销毁?

        • destroy方法执行的时候AServlet对象还没有被销毁

          destroy方法执行结束之后,AServlet对象的内存才会被销毁

  • Servlet对象更像人的一生:

    • Servlet的无参构造方法执行: 人出生了
    • Servlet对象的init方法执行: 标志着你正在接受教育
    • Servlet对象的service方法执行: 标志着你已经开始工作了
    • Servlet对象的destroy方法执行: 标志着你已经快死了,写遗言

12.GenericServlet

//GenericServlet实现了Servlet , ServletConfig和 java.io.Serializable
GenericServlet implements Servlet, ServletConfig,        java.io.Serializable

(1)当我们Servlet类中编写了一个有参数的构造方法,如果没有手动编写无参数构造方法,会出现什么问题?(报500错误)

  • 报错了: 500错误
  • 注意: 500是一个HTTP协议的错误状态码.
  • 500一般情况下是因为服务器端的java程序出现了异常,(服务器端的错误是500错误,服务器内部错误)
  • 如果没有无参数的构造方法,会导致出现500错误,无法实例化Servlet对象
  • 所以,一定要注意,在Servlet开发中,不建议程序员来定义构造方法,因为定义不当一不小心就会导致无法实例化Servlet对象

思考:Servlet的无参构造方法是在对象第一次创建的时候执行,并且只执行一次,而init方法也是在第一次创建的时候执行,并且只执行一次,那么这个无参数构造方法可以代替掉无参数构造方法吗:

  • 不能
  • Servlet规范中有要求,作为javaweb程序员来说,编写Servlet类的时候,不建议手动编写构造方法,很容易让无参构造方法消失,这个操作可能会导致Servlet对象无法实例化,所以init方法是有存在的必要的
  • init , service ,destroy方法中使用量最多的是哪个方法?
    • 使用最多的方法是service
  • 什么时候使用init方法呢?
    • 通常在init方法中进行初始化,并且这个初始化只需要初始化一次,例如:初始化数据库连接池,初始化线程池,
  • 什么时候使用destroy方法呢?
    • destroy方法也很少用,
    • 通常在destroy方法中进行资源的关闭,还有什么资源没保存的,保存一下

(2)我们编写一个Servlet类直接实现Servlet接口每次都要重写五个方法很繁琐,怎么解决?(适配器模式)

  • 我们只需要service方法,其他方法大部分情况下是不需要使用的,代码很丑陋

  • 所以使用适配器模式 Adapter:

    • 例子:

    • (1)先创建一个接口 MyInterface

    • public interface MyInterface {
          void m1();
          void m2();
          void m3();
          void m4();
          void m5();
          void m6();
          void m7();
          void core();
      }
      
    • (2)然后创建接口的配适器

    • GenericServlet 一个抽象类,里边有一个抽象方法core()是我们常用的方法

    • public abstract class GenericServlet implements MyInterface{
          public abstract void core();
          @Override
          public void m1() {
          }
          @Override
          public void m2() {
          }
          @Override
          public void m3() {
          }
          @Override
          public void m4() {
          }
          @Override
          public void m5() {
          }
          @Override
          public void m6() {
          }
          @Override
          public void m7() {
          }
      }
      
    • (3)最后创建类继承UserAdapter

    • 这样只用实现一个接口的方法即可

    • public class User extends GenericServlet{
          @Override
          public void core() {
          }
      }
      
  • 总结:以后所有的Servlet类都不要直接实现Servlet接口

  • 我们可以创建一个标准通用的Servlet , 起名 : GenericServlet

  • 以后所有的Servlet类都要继承GenericServlet类

  • GenericServlet就是一个适配器

  • 以后编写的所有Servlet类继承GenericServlet,重写service方法即可


(3)思考:GenericServlet类是否需要改进以下,更利于子类程序的编写

  • 思考第一个问题,我提供的GenericServlet之后,init方法还会执行吗?

    • 还会执行,会执行GenericServlet类中的init方法
  • 思考第二个问题:这个init方法是谁调用的呢?

    • Tomcat服务器调用的
  • 思考第三个问题:init方法中的ServletConfig方法是谁传过来的?

    • 都是Tomcat干的
    • Tomcat服务器先创建了Servlet对象,然后调用init方法,将ServletConfig对象传给了init方法

(4)Tomcat服务器的伪代码:

  • public class Tomcat {
        public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    
    //.....
    //Tomcat服务器伪代码
    //创建loginServlet对象(通过反射机制,调用无参构造方法来实例化loginServlet对象)
    Class clazz = Class.forName("com.sichen.javaweb.servlet.loginServlet");
    Object obj = clazz.newInstance();
    
    //向下转型
    Servlet servlet = (Servlet)obj;
    //创建servletConfig对象
    //Tomcat服务器要将ServletConfig对象实例化
    ServletConfig servletConfig = new ServletConfig();
            
    //调用servlet的init方法
    //小猫咪创建的servletConfig对象org.apache.catalina.core.StandardWrapperFacade@6af935f9
    //多态(Tomcat服务器完整的实现了Servelt规范)
    servlet.init(servletConfig);
    //调用servlet的service方法
    //....
    }
    

(5)整章重点重要 (模板设计模式)

模板设计模式

  • GenericServlet抽象类
package com.sichen.javaweb.servlet;
import jakarta.servlet.*;
import java.io.IOException;
public abstract class GenericServlet implements Servlet {
    //创建一个成员变量
    private ServletConfig servletConfig;

    /**
     * init方法中的ServletConfig对象是Tomcat创建好的
     * 这个对象目前在init方法的参数上,是一个局部变量
     * 那么ServletConfig对象肯定以后要在service方法上使用,那么怎么才能保证
     * ServletConfig对象在service方法中能够使用呢?
     * @param servletConfig
     * @throws ServletException
     */
    
    //在这里加一个final子类就不能重写init方法,也就不能破坏我写的结构
    @Override
    public final void init(ServletConfig servletConfig) throws ServletException {
//        System.out.println("小猫咪创建的servletConfig对象"+servletConfig);
        //使用this给成员变量赋值 = 传过来的servletConfig
        this.servletConfig = servletConfig;
        //在这里调用init方法
        this.init();
    }
    //解决子类重写init方法的问题,创建一个init方法,让子类可以重写
    //模板设计模式
    public void init(){
    }
    /**
     * 抽象方法,这个方法是最常用的所以要求此类必须实现service方法
     * @param servletRequest
     * @param servletResponse
     * @throws ServletException
     * @throws IOException
     */
    @Override
    public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException;
    @Override
    public void destroy() {
        System.out.println("Aservlet's destroy method execute!");
    }
    @Override
    public ServletConfig getServletConfig() {
        //这里的getServletConfig方法返回servletConfig,这样子类继承的时候就可以获取这个servletConfig
        return servletConfig;
    }
    @Override
    public String getServletInfo() {
        return null;
    }
}

  • GenericServlet抽象类的子类

  • package com.sichen.javaweb.servlet;
    import jakarta.servlet.ServletConfig;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.ServletRequest;
    import jakarta.servlet.ServletResponse;
    import java.io.IOException;
    public class Vip extends GenericServlet{
        //思考一个问题:
        //有没有一种可能,需要我在Vip类中重写init方法
        //当然有可能,于是重写init方法
        //    public void init(ServletConfig servletConfig) throws ServletException{
        //        System.out.println("重写的init方法");
        //    }
        
        //父类将init方法final了,子类没有办法重写这个init方法
        //如果这个时候还是想要重写init方法怎么办
        //在父类中写一个init方法,让原init方法调用init(),这边子类就可以重写了
        //模板设计模式
        //这里边的init方法也只执行一次,因为是重写的父类方法,而父类方法是在父类中的init中执行的,所以只执行一次
        @Override
        public void init(){
            System.out.println("这是子类init方法");
        }
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            System.out.println("vip可以享受更好的服务...");
            //这里就可以使用get方法获取ServletConfig
            ServletConfig config = this.getServletConfig();
            System.out.println("servletConfig对象"+config);
        }
    }
    


13.ServletConfig

(1)ServletConfig是什么? (是一个规范 / 接口)

  • jakarta.servlet.ServletConfig
  • 很显然ServletConfig是Servlet规范中的一员
  • ServletConfig是一个接口, (jakarta.servlet.Servlet也是一个接口)

(2)谁去实现这个接口呢? (web服务器实现)

  • ServletConfig对象是:org.apache.catalina.core.StandardWrapperFacade@fb8b8e6
  • 结论:Tomcat服务器实现了这个 ServletConfig 接口
  • 思考:如果Tomcat服务器换成了jetty服务器,输出ServletConfig对象的时候还是这个结果吗?
    • 不一定是,包名类名可能和Tomcat的不一样,但是他们都是实现了ServletConfig这个规范

(3)Servlet/ServletCondfig的关系

  • 一个Servlet对象中有一个ServletConfig对象, (Servlet/ServletCondfig对象是一对一)
  • 一百个Servlet对象,就应该有一百个ServletConfig对象

(4)ServletConfig对象是谁创建的呢?

  • 参照12-(4)Tomcat服务器的伪代码:
  • Tomcat服务器(Web服务器)创建的ServletConfig对象
  • 创建Servlet对象的时候,同时创建了ServletConfig对象

(5)ServletConfig是干什么的?

  • config = 全名是 Configuration
  • ServletConfig 对象被翻译为: Servlet对象的配置信息对象
  • 一个Servlet对象就有一个Servlet的配置信息对象ServletConfig

(6)ServletConfig对象中到底包含了什么信息呢?

  • web.xml文件中的标签中的配置信息

  •  <servlet>
         <servlet-name>configServlet</servlet-name>
         <servlet-class>com.sichen.javaweb.servlet.ConfigtextServlet</servlet-class>
     </servlet>
    
  • Tomcat服务器解析web.xml,将文件中的标签中的配置信息自动包装到ServletConfig对象中


(7)ServletConfig中有哪些方法?

  • 以下的四个方法,在自己编写的Servlet类当中也可以用this去调用,(这个Servlet继承了GenericServlet)

  • (1)getServletName() : 获取Servlet的name

  • (2)getServletContext() : 获取ServletContext对象

  • 通过下面两个方法可以获取到web.xml文件中的初始化参数配置信息

  • (3)getInitParameter(String name) : 通过初始化参数的name获取value (也就是值 )

  • String driver = config.getInitParameter("driver");
    out.print(driver);
    //输出com.mysql.cj.jdbc.Driver
    
  • (4)getInitParameterNames() : 获取所有的初始化参数的name

  • Enumeration<String> initParameterNames = config.getInitParameterNames();
            //遍历集合
            while (initParameterNames.hasMoreElements()){//是否有更多元素
                String s = initParameterNames.nextElement();//取元素
                out.print(s);//取name
                out.print("<br>");
            }
    
  • 两个方法组合使用:

  •             //循环取内容
    Enumeration<String> initParameterNames = config.getInitParameterNames();
            //遍历集合
            while (initParameterNames.hasMoreElements()){//是否有更多元素
                String s = initParameterNames.nextElement();//取元素
                String initParameter = config.getInitParameter(s);//通过name取value
                out.print(s +"="+ initParameter);
                out.print("<br>");
            }
    

初始化参数配置信息

  • <servlet>
            <servlet-name>configServlet</servlet-name>
            <servlet-class>com.sichen.javaweb.servlet.ConfigtextServlet</servlet-class>
            <init-param>
                <param-name>driver</param-name>
                <param-value>com.mysql.cj.jdbc.Driver</param-value>
            </init-param>
            <init-param>
                <param-name>url</param-name>
                <param-value>jdbc:mysql://localhost:3306/emp</param-value>
            </init-param>
            <init-param>
                <param-name>user</param-name>
                <param-value>root</param-value>
            </init-param>
            <init-param>
                <param-name>password</param-name>
                <param-value>acpl159357</param-value>
            </init-param>
        </servlet>
    
  • 因为GenericServlet接口继承了ServletConfig接口

  • 所以继承这个GenericServlet接口后,不用再获取ServletConfig对象,直接用this使用getInitParameterNames()方法和getInitParameter()方法即可


14.ServletContext

(1)通过ServletConfig对象获取ServletContext对象

  • //获取ServletContext对象
            //第一种方式:
            ServletContext application = config.getServletContext();
            out.print("<br>"+ application);
            //第二种方式:
            ServletContext application2 = this.getServletContext();
            out.print("<br>" + application2);
    

(2) ServletContext是什么?

  • ServletContext是一个接口,是Servlet规范中的一员
  • Servlet对象的环境对象 (Servlet对象的上下文对象)
  • ServletContext对象对应的其实是整个web.xml文件

(3)ServletContext是谁实现的?

  • Tomcat服务器(Web服务器)实现了这个接口

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

(4)ServletContext对象是谁创建的?在什么时候创建的?

  • ServletContext在Web服务器启动的时候创建
  • ServletContext是Web服务器创建
  • 对于一个webapp来说,ServletContext对象只有一个

(5)ServletContext对象在服务器关闭的时候被销毁

(6)总结

  • Tomcat是一个容器,一个容器可以放多个webapp,一个webapp对应一个ServletContext对象
  • 50个学生,每个学生都是一个Servlet,这50个学生都在同一个教室,那么这个教室相当于一个ServletContext对象
  • 放在ServletContext对象中的数据,所有Servlet一定是共享的
  • 比如:一个教室的空调是所有学生共享的

(7)ServletContext接口中有哪些常用方法?

①上下文初始化参数:
  • web.xml

  • <!--  上下文的初始化参数:是应用级的配置信息,一般一个项目共享的配置信息  -->
        <context-param>
            <param-name>pageSize</param-name>
            <param-value>10</param-value>
        </context-param>
        <context-param>
            <param-name>starIndex</param-name>
            <param-value>0</param-value>
        </context-param>
    
  • public String getInitParameter(String name);//通过上下文参数的name获取value
    public Enumeration<String> getInitPerameterNames();  //获取上下文参数的所有name
    //以上两个方法是ServletContext对象的方法,这个方法获取的是什么信息?是上下文初始化参数的配置信息
    

上下文初始化参数和初始化参数信息区别

  • 上下文初始化参数是全局属性:
    • 这些参数属于应用级的配置信息,一般一个项目中共享的配置信息会放到以上标签中
  • 初始化参数信息是局部属性
    • 如果你的配置信息只是想给一个Servlet作为参考,那么你放置到Servlet标签当中去即可,使用ServletConfig对象来获取

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

  • 在java源码中,永远不要把项目的根路径写死!

  • public String getContextPath();
    //方法的使用:
    String contextPath = servletContext.getContextPath();
    out.print(contextPath);//输出  /servlet03
    //应用的根路径     /servlet03
    

③动态获取文件的绝对路径 (文件的服务器路径)
public String getRealPath(String path);
//方法的使用;
//获取文件的绝对路径
//String realPath = appLication.getRealPath("index.html");不加 / 也行
//这个/代表着Web文件夹的根,不加/默认也是从这个根下找
String realPath = servletContext.getRealPath("/index.html");
out.print("文件的绝对路径是:" + realPath); 
//输出为:
//文件的绝对路径D:\IDEA\Servlet\out\artifacts\servlet03_war_exploded\index.html

//获取Web目录下的其他文件夹里的文件绝对路径:
String realPath1 = servletContext.getRealPath("/common/common.html");

④写项目的日志
//通过ServletContext的log方法也是可以记录日志的
//这个日志原始是是记录在Tomcat下的  D:\Tomcat\apache-tomcat-10.0.14\logs  文件夹里
//但是使用IDEA工具后就存储在和idea相关的文件下了
public void log(String message);
//方法使用:
servletContext.log("大家好我是思尘!");


public void log(String message , Throwable t);
//方法使用,可以定义一个条件,在日志文件中输出类似非法记录
int age = 17;
//当年龄小于十七岁的时候显示非法记录日志
if (age < 18) {
servletContext.log("对不起,您未成年,请绕行:" , 
new RuntimeException("小屁孩快走开,不适合你"));
        }

⑤Tomcat的日志文件
  • localhost_access_log.2022-01-13.txt   :访问日志文件
    catalina.2022-01-13.log             :Tomcat控制台运行日志
    //这两个启动服务器就会创建
    localhost.2022-01-13.log			:调用log方法输出的日志
    //第一次访问的时候才会创建
    

⑥ServletContext又叫"应用域"(存取删数据)
  • ServletContext还有另一个名字: 应用域 (后面还有其他域 例如:请求域,会话域)

  • 如果所有用户共享一份数据,并且这个数据很少的被修改,并且这个数据量不多,可以将这些数据放到ServletContext这个应用域中

  • 为什么是所有用户共享的数据?

    • 不是共享的没有意义,因为servletContext这个对象只有一个,只有共享的数据放进去才有意义
  • 为什么数据量要很小?

    • 因为数据量太大的话,太占用堆内存
    • 并且这个对象的生命周期很长,服务器关闭的时候,这个对象才会被销毁,所以说占用内存的较小的数据量可以存储进去
  • 为什么这些共享的数据很少修改?或者说几乎不修改?

    • 因为所有用户共享的数据,如果涉及修改操作,必然会引起线程并发所带来的安全问题,所以放在ServletContext对象中的数据一般不修改
  • 所有用户共享,数据量小,又不修改,这样的数据放到ServletContext这个应用域当中,会大大提高效率,因为应用域相当于一个缓存,放到缓存中的数据,下次在使用的时候,不需要从数据库中再次查找,大大提高效率

  • 怎么存数据

    • public void setAttribute(String name , Object value);
      //相当于map.put(k,v);
      //使用
      //这里是创建一个类,两个参数,name,password
      User user = new User("sichen" , "123");
      servletContext.setAttribute("userobj" , user);
      //这里的value什么类型都能存
      
  • 怎么取数据

    • public Object getAttribute(String name);
      //相当于 Object v = map.get(k); 
      //使用
       Object userobj = servletContext.getAttribute("userobj");
      out.print(userobj + "<br>");
      //输出是:因为User类中写了toString方法,所以输出
      //  User{name='sichen', password='123'}
      
  • 怎么删除数据

    • public void removeAttribute(String name); 
      //相当于 map.remove(k);
      

⑦获取MIME类型
  • MIME类型 : 在互联网通信过程中定义的一种文件数据类型 ,

    • 格式 : 大类型/小类型
    • 例如 : text/html image/jpeg
  • 方法 :

  • String getMimeType(String file) //其实是通过文件的后缀类型来获取
        //传一个文件的名字
    

综合案例 : 下载文件

  • 文件下载需求
    • 页面显示一个超连接
    • 点击超链接之后 , 弹出下载提示框
    • 完成图片文件下载

分析 :

  • 超链接指向的资源如果能够被浏览器解析 , 则在浏览器上展示 , 如果不能解析则弹出下载提示框 , 不能满足需求
  • 任何资源都必须弹出下载框
  • 使用响应头设置 资源的打开方式 attachment
    • content-disposition:attachment

问题: 中文文件名的问题

在下载提示框以及下载后的文件中 , 中文是不能被显示的,

  • 解决思路 :

    • 1.获取客户端使用的浏览器版本信息

    • //1. 获取user-agent请求头
      String agent = request.getHeader("user-agent");
      
    • 2.根据不同的版本信息 , 设置filename的编码方式

    • 找到一个DownLoadUtils.java的包 , 里边有一个方法

  • String getFileName(String agent, String filename)
    //将上边获取的agent , 和文件名称filename分别传进去 , 返回一个filename
    
  • DownLoadUtils.java

  • import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.Base64;
    
    public class DownLoadUtils {
        public static String getFileName(String agent, String filename) throws UnsupportedEncodingException {
    
            if (agent.contains("MSIE")) {
                // IE浏览器
                filename = URLEncoder.encode(filename, "utf-8");
                filename = filename.replace("+", " ");
            } else if (agent.contains("Firefox")) {
                // 火狐浏览器
                Base64.Encoder encoder = Base64.getEncoder();
                filename = "=?utf-8?B?" +encoder.encodeToString(filename.getBytes("utf-8")) + "?=";
            } else {
                // 其它浏览器
                filename = URLEncoder.encode(filename, "utf-8");
            }
            return filename;
        }
    }
    

步骤:

  • 1.定义页面 , 编辑超链接的href属性 , 属性值指向一个servlet , 传递资源的名称filename

  • <a href="/login/downloadServlet?filename=21-0.png">图片一</a>
    <a href="/login/downloadServlet?filename=1.avi">视频一</a>
    
  • 2.定义downloadServlet.java

    • package cn.sichen.web.servlet;
      import jakarta.servlet.*;
      import jakarta.servlet.http.*;
      import jakarta.servlet.annotation.*;
      
      import java.io.FileInputStream;
      import java.io.IOException;
      @WebServlet(name = "downloadServlet", value = "/downloadServlet")
      public class downloadServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doPost(request , response);
          }
          @Override
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              //1.获取请求参数 : 文件名称
              String filename = request.getParameter("filename");
              //2.使用字节输入流加载文件进内存
              //2.1 找到文件服务器路径
              ServletContext servletContext = this.getServletContext();
              String realPath = servletContext.getRealPath( "/img/" + filename);
              //2.2 用字节流关联
              FileInputStream fis = new FileInputStream(realPath);
              //2.3 指定response的响应头
              //2.3.1 设置响应头类型  content-type
              //获取文件的MIME类型的
              String mimeType = servletContext.getMimeType(filename);
              //这里要传两个参数 , 一个是要设置的属性名 , 一个是值
              response.setHeader("content-type" , mimeType);
              //2.3.2 设置响应头打开方式 : content-disposition
              response.setHeader("content-disposition" , "attachment;filename="+filename);
              //3 将输入流的数据写出到输出流中
              //获取输出流
              ServletOutputStream outputStream = response.getOutputStream();
              byte[] buff = new byte[1024*8];
              int len = 0;
              while ((len = fis.read(buff)) != -1){
                  outputStream.write(buff , 0 ,len);
              }
              fis.close();
      
          }
      }
      
      

15.HTTP协议

(1)什么是协议?

  • 协议是某些人,某些组织制定的规范,大家都是用这个规范,可以无障碍交流
  • 协议就是一套标准

(2)什么是HTTP协议?

  • http协议: 是W3C制定的一种超文本传输协议 (通信协议)

  • 这种协议游走在B和S之间,B和S之间发送数据,都要使用http协议,

    这样B和S之间才能解耦合 (B不依赖S,S不依赖B)


(3)什么是超文本?

  • 超文本说的就是: 不是普通文本, 比如流媒体:声音, 视频 ,图片等

  • HTTP协议支持: 不但可以传送普通文本,同样支持传递 声音,视频,图片等流媒体

  • W3C

    • 万维网联盟组织

    • 负责制定标准的: HTTP; HTML; HTML4.0;HTML5; xml; DOM…

    • 万维网之父: 蒂姆 伯纳斯 李

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

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

  • HTTP协议包括:

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

    • 不管你是哪个品牌的浏览器,都是这么发
    • 不管你是哪个品牌的web服务器,都是这么发

(4)HTTP的请求协议(B->S)(详细分析)

  • 怎么查看协议的内容

  • 使用浏览器 : F12 然后找到network,通过这个面板可以查看协议的内容

  • http的请求协议包括: (4部分)

    • 请求行
    • 请求头
    • 空白行
    • 请求体
  • http请求协议的具体报文 GET请求 (用户输入数据会在请求头里边)

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

  • POST /servlet04/postservlet HTTP/1.1							请求行
    Host: localhost:8080											请求头
    Connection: keep-alive
    Content-Length: 18
    Cache-Control: max-age=0
    sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
    sec-ch-ua-mobile: ?0
    sec-ch-ua-platform: "Windows"
    Upgrade-Insecure-Requests: 1
    Origin: http://localhost:8080
    Content-Type: application/x-www-form-urlencoded
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    Sec-Fetch-Site: same-origin
    Sec-Fetch-Mode: navigate
    Sec-Fetch-User: ?1
    Sec-Fetch-Dest: document
    Referer: http://localhost:8080/servlet04/index.html
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    Cookie: Idea-cf4f63f1=866f0263-8599-4304-a94b-acdb719e6fbb
    																空白行
    									username=jack&userpwd=123	   请求体
    
    • 请求行
      • 包括三部分
        • 第一部分 : 请求方式 (七种)
          • get (常用)
          • post (常用)
          • delete
          • put
          • head
          • options
          • trace
        • 第二部分 : URI : 统一资源标识符
          • URI和URL什么关系,
            • URL是包含URI的
            • http://localhost:8080/servlet04/index.html 这是URL
            • /servlet04/index.html 这是URI
          • 有什么区别
            • URI: 统一资源标识符:代表网络中某个资源的名字 , 但是通过URI是无法定位资源
            • URL: 统一资源定位符:代表网络中某个资源,.同时,通过URL是可以定位到该资源
        • 第三部分 : 协议版本号
    • 请求头
      • 请求的主机
      • 主机的端口
      • 浏览器的信息
      • 平台信息
      • cookie信息
    • 空白行
      • 是用来区分请求头和请求体的
    • 请求体
      • 向服务器发送的具体数据

  • get请求和post请求的区别

    • get请求请求行上发送 (在请求行的URI后边,以一个"?"隔开)

      • get请求发送数据的时候,数据会挂在URI的后面,并且在URI的后面添加一个"?" "?"后面是数据这样会导致发送的数据回显在浏览器的地址栏
        • http://localhost:8080/servlet04/getservlet?username=jack&userpwd=123
      • get请求只能发送普通的字符串,并且这个字符串的长度有限制的,这个不同浏览器有不同的限制
      • get请求不能发送大数据量
    • post请求请求体中发送

      • 不会回显到浏览器的地址栏上,也就是说post的数据在地址栏上看不到
      • post可以发送任何类型数据,包括普通字符串,流媒体等信息:视频,图片,声音等…
      • post请求可以发送大数据量,理论上没有长度限制
    • 不管你是get请求还是post请求,发送数据格式完全相同的,只是位置不同,格式都是

      • name=value&name=value&name=value

      • username=jack&userpwd=123

        • name是什么?

          • 以form表单为例: form表单中的input标签的name

          •  <form action="/servlet04/getservlet" method="get">
                 <!-- name就是这里起的名字 -->
                  用户名<input type="text" name="username" /><br>
                  密码 <input type="text" name="userpwd" /><br>
                  <input type="submit" value="login"/>
            </form>
            
        • value是什么?

          • 以form表单为例: form表单中的input标签的value (也就是用户输入的)
  • 什么时候使用get请求,什么时候使用post请求

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

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

    • get请求绝对安全的 , 因为get请求只是向服务器获取数据 不会对服务器造成危险

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

    • get请求支持缓存 ,

      • 任何一个get请求最终的"响应结果" 都会被浏览器缓存起来,在浏览器缓存中
        • 一个get请求的路径,对应 一个资源
        • 一个get请求的路径 对应 一个资源
      • 实际上你只要发送一个get请求,浏览器第一件事就是先从缓存中找,如果浏览器缓存中没有,再从服务器上去获取
    • post是不支持缓存的

      • post请求之后 , 服务器响应的结果不会在浏览器上缓存起来,因为这样没有意义
    • s

      • 大部分的form表单提交 , 都是post方式 , 因为form表单中药填写大量信息 , 这些数据是收集用户信息 , 一般都是要传递给服务器 , 服务器将这些数据保存 / 修改等
      • 如果表单中有敏感信息 , 还是建议使用post请求 , 因为get请求会回显到浏览器地址栏
      • 做文件上传 . 一定是post请求 , 要传的数据不是不同文本
      • 其他情况可以使用get请求
  • 有没有这么一种情况,我希望get请求不缓存?每次get都去服务器获取资源

    • 只要每一次get请求的请求路径不同即可.
    • 可以在路径后边添加一个每时每刻都在变化的"时间戳"
  • 怎么先服务器发送get请求,怎么向服务器发送post请求?

    • 到目前为止,只有一种情况可以发送post请求

    • 使用form表单,并且form表单中的method的属性值为post

    • <form action="/servlet04/postservlet" method="post">
              用户名<input type="text" name="username" /><br>
              密码 <input type="password" name="userpwd" /><br>
              <input type="submit" value="login"/>
      </form>
      
    • 其他所有的情况一律是get请求

      • 在浏览器的地址栏上输入URL,敲回车,属于get请求
      • 在浏览器上直接点击超链接的时候,属于get请求
      • 使用form表单提交数据的时候,没有method属性,默认是get

(5)HTTP的响应协议(S->B)(详细分析)

  • 怎么查看协议的内容

  • 使用浏览器 : F12 然后找到network,通过这个面板可以查看协议的内容

  • http响应协议包括: (4部分)

    • 状态行
    • 响应头
    • 空白行
    • 响应体
  • http响应协议的具体报文

  • HTTP/1.1 200			ok				//状态行
    Content-Type: text/html;charset=UTF-8   //响应头
    Content-Length: 111
    Date: Fri, 14 Jan 2022 13:47:01 GMT
    Keep-Alive: timeout=20
    Connection: keep-alive      			//响应头
    						     			//空白行
    <!doctype html>				 			//响应体
    <html>
    	<head>
    	<title>from get servlet</title>
    	</head>	
    	<body>
    	<h1>from get servlet </h1>
    	</body>
    </html>
    
    • 状态行
      • 三部分组成
        • 第一部分: 协议版本号 (HTTP/1.1)
        • 第二部分: 状态码 (200) http协议中规定的响应状态号,不同的响应结果对应不同的号码
          • 200: 表示请求响应成功,正常结束
          • 404: 表示访问的资源不存在,通常是因为要么是你路径写错了,要么是路径写对了,但是服务器中对应的资源没有启动成功,总之404一般是前端错误
          • 405: 表示前端发送的请求方式和后端请求的处理方式不一致所致的
            • 比如:前端的请求是POST,后端的处理方式是get方式进行处理,发生405
            • 比如:前端的请求是get,后端的处理方式是post方式进行处理,发生405
          • 500: 表示服务器端的程序出现异常,一般会认为是服务器端的错误导致
          • 1XX开始的是是说 服务器接收浏览器消息 , 但是没有完成 , 等待一段时间后 , 发送 1XX的状态码 , 询问客户端还要不要发送数据
          • 2XX开始 一般表示成功
          • 3XX开始
            • 一般表示重定向(302)
            • 访问缓存 (304)
          • 4XX开始的一般是浏览器端的错误
            • 请求路径没有对应的资源 (404)
            • 请求方式没有对应的doXxx方法 (405)
          • 5XX开始的一般是服务器端的错误
            • 服务器内部出现异常 (500)
        • 第三部分: 状态的描述信息
          • ok表示正常成功结束
          • not found 表示资源找不到
    • 响应头
      • 响应的内容类型
      • 相应的内容长度
      • 相应的时间
    • 空白行
      • 用来分隔响应头和响应体
    • 响应体
      • 响应体就是响应的正文,这些内容是一个长的字符串,这个字符串被浏览器渲染,解释并执行,最终展示出效果

16.HttpServlet

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

  • HttpServlet在那个包下?

    • jakarta.servlet.http.HttpServlet包下
  • 到目前为止,我们接触了Servlet 规范下那些接口?

    • - jakarta.servlet.Servlet                  //Servlet核心接口     (接口)
      - jakarta.servlet.ServletConfig            //Servlet配置信息接口   (接口)
      - jakarta.servlet.ServletContext           //Servlet上下文接口   (接口)
      - jakarta.servlet.ServletRequest           //Servlet请求接口   (接口)
      - jakarta.servlet.ServletResponse          //Servlet响应接口  (接口)
      - jakarta.servlet.ServletException         //Servlet异常   (类)
      - jakarta.servlet.GenericServlet           //标准通用的Servlet类   (抽象类)
      
  • http包下都有那些接口和类呢 jakarta.servlet.http 包下

    • - jakarta.servlet.http.HttpServlet          (HTTP协议专用的Servlet类) (抽象类)
      - jakarta.servlet.http.HttpServletRequest   (HTTP协议专用的请求对象)
      - jakarta.servlet.http.HttpServletResponse	(HTTP协议专用的相应对象)
      
  • 注意HttpServletRequest对象中封装了那些信息?

    • HttpServletRequest 简称 request对象
    • HttpServletRequest 中封装了请求协议的全部内容
    • Tomcat服务器 (WEB服务器) 将’请求协议’中的数据全部解析出来 , 然后将这些数据封装到了request对象里边了
    • 也就是说 , 我们只需要面向HttpServletRequest对象 , 就可以获取请求协议中的数据
  • HttpServletResponse对象是专门用来响应HTTP协议到浏览器的


(1)HttpServlet源码分析

  • package com.sichen.javaweb.servlet;
    import jakarta.servlet.http.HttpServlet;
    
    //调用service方法
    public abstract class helloServlet extends HttpServlet {
    	//用户第一次请求 , 创建HelloServlet对象的时候 , 会执行这个无参数构建方法
        public HelloServlet(){
        }
        
    }
    
    //--------------------------------------------------------------------------------
    // 创建对象后  调init方法
    //没有init方法 , 那么就会执行父类HttpServlet中的init方法
    //HttpServlet中也没有init方法 , 那么就会继续执行父类的父类 GenericServlet中的init方法
    public abstract class GenericServlet implements Servlet, ServletConfig, 			Serializable { 
        //用户第一次请求的时候 , HelloServlet对象第一次创建的时候
    public void init(ServletConfig config) throws ServletException {
            this.config = config;
            this.init();
        }
        //用户第一次请求的时候 , 带有参数的init方法会调用 这个没有参数的init方法
    public void init() throws ServletException {
        }
      }
    
    //--------------------------------------------------------------------------------
    //init方法执行完后 , 调用service方法
    
    			//这个service就是一个模板类
    public abstract class HttpServlet extends GenericServlet {
    //用户发送第一次请求的时候 , 这个service方法会执行
    //用户执行一次请求 , 这个service方法就会执行一次
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
            HttpServletRequest request;
            HttpServletResponse response;
            try {
                //将ServletRequest 和 ServletResponse 向下转型为带有Http的HttpServletRequest 和 HttpServletResponse
                request = (HttpServletRequest)req;
                response = (HttpServletResponse)res;
            } catch (ClassCastException var6) {
                throw new ServletException(lStrings.getString("http.non_http"));
            }
        //调用重载的service方法
            this.service(request, response);
        }
     
     							   
     //这个service方法的两个参数都是带有Http的
            						//这个service是一个模板方法
     //在该方法中定义核心骨架 , 具体的实现步骤延迟到子类去完成
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求方式  
        //这个请求方式最终可能是 : ""
        //注意 : req.getMethod() 方法获取的是请求方法 , 可能是七种之一
        //  GET , HEAD , POST , PUT , DELETE , OPTIONS , TRACE
            String method = req.getMethod();
            long lastModified;
            if (method.equals("GET")) {
                lastModified = this.getLastModified(req);
                if (lastModified == -1L) {
                    this.doGet(req, resp);
                } else {
                    long ifModifiedSince;
                    try {
                        ifModifiedSince = req.getDateHeader("If-Modified-Since");
                    } catch (IllegalArgumentException var9) {
                        ifModifiedSince = -1L;
                    }
                    if (ifModifiedSince < lastModified / 1000L * 1000L) {
                        this.maybeSetLastModified(resp, lastModified);
                        this.doGet(req, resp);
                    } else {
                        resp.setStatus(304);
                    }
                }
            } else if (method.equals("HEAD")) {
                lastModified = this.getLastModified(req);
                this.maybeSetLastModified(resp, lastModified);
                this.doHead(req, resp);
            } else if (method.equals("POST")) {
                this.doPost(req, resp);
            } else if (method.equals("PUT")) {
                this.doPut(req, resp);
            } else if (method.equals("DELETE")) {
                this.doDelete(req, resp);
            } else if (method.equals("OPTIONS")) {
                this.doOptions(req, resp);
            } else if (method.equals("TRACE")) {
                this.doTrace(req, resp);
            } else {
                String errMsg = lStrings.getString("http.method_not_implemented");
                Object[] errArgs = new Object[]{method};
                errMsg = MessageFormat.format(errMsg, errArgs);
                resp.sendError(501, errMsg);
            }
        }
        //请求是get请求就调用doGet方法
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String msg = lStrings.getString("http.method_get_not_supported");
            this.sendMethodNotAllowed(req, resp, msg);
        }
        //请求是post方法,就调用post方法
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String msg = lStrings.getString("http.method_post_not_supported");
          this.sendMethodNotAllowed(req, resp, msg);
        }
    }
    //-------------------------------------------------------------------------------
    /*
    通过以上代码分析:
    	假设前端发送的是get请求 , 后端程序员重写的是doPost()
    	假设前端发送的是post请求 , 后端程序员重写的是doGet()
    	会发生什么?
    	子类中没有这个方法,就会调用父类的方法 , 
    	就会报405的错误
    	405表示前端的错误 , 发送的请求和后端的方式不对 , 不是服务器需要的请求方法
    	
    通过以上源代码可以知道:
        只要HttpServlet类中的doGet方法 , 或是Dopost方法执行了 , 必然405
        	
    怎么避免405的错误呢
       	后端重写了doGet方法 , 前端一定要发送get请求
       	后端重写了doPost方法 , 前端一定要发送post请求
       	这样可以避免405错误
       
    这样前端要发送什么请求 , 是后端说了算 , 后端让发送什么请求 , 前端必须发送什么请求 , 不然就报错
    */
    
    
  • 我们编写的HelloServlet 直接继承HttpServlet ,直接重写HttpServlet类中的 service()方法可以吗?

    • 可以 , 只不过你享受不到405错误 , 享受不到HTTP协议专属的服务
    • 405错误是一种保护机制
    • 一般不建议重写service方法

1642249107494


(2)注意: 编写Servlet类最终步骤

  • 我们以后编写Servlet类的时候,实际上是不会直接继承GenericServlet的,因为我们是B/S结构的系统,这种系统是基于HTTP超文本传输协议的,在Servlet规范中,提供了一个类叫做HttpServlet,他是专门为HTTP协议准备的一个Servlet类,我们编写的Servlet类要继承HttpServlet (HttpServlet是HTTP协议专用的), 使用HttpServlet处理HTTP协议更便捷,但是你要知道他们之间的继承结构:

  • jakarta.servlet.Servlet  (接口)   [爷爷]
    jakarta.servlet.GenericServlet implements Servlet (抽象类)   [儿子]
    jakarta.servlet.http.HttpServlet extends GenericServlet  (抽象类)  [孙子]
        //我们以后编写Servlet类直接继承HttpServlet就行了
    
  • 到今天我们终于得到了Servlet类的开发步骤

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

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

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

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

    • 注意 : 表单中要加项目名

    • 				项目名   web.xml文件中的跳转路径
      <form action="/servlet06/he" method="post">
          <input type="submit" value="提交">
      

(3)HttpServletRequest

  • out.print(request);
    //org.apache.catalina.connector.RequestFacade@1964d5ef
    
  • HttpServletRequest是一个接口 , 全限定名称 : jakarta.servlet.http.HttpServletRequest

  • HttpServletRequest接口是Servlet规范中的一员

  • HttpServletRequest接口的父接口是 : ServletRequest

    • public interface HttpServletRequest extends ServletRequest {}
      
  • HttpServletRequest接口的实现类谁写的 ? HttpServletRequest对象是谁给创建的 ?

    • 通过测试 : org.apache.catalina.connector.RequestFacade

    • public class RequestFacade implements HttpServletRequest {}
      
    • 测试结果 : Tomcat服务器(WEB服务器)实现了HttpServletRequest接口 , 还是说明了Tomcat服务器实现了Servlet规范

    • 而对于我们java程序员来说 , 实际上不需要关心这个 , 我们只需要面向接口编程即可 , 我们关心的是HttpServletRequest接口中有哪些方法 , 这些方法可以完成哪些功能?

  • HttpServletRequest对象中都有什么信息 ? 都包装了什么信息 ?

    • HttpServletRequest对象是Tomcat服务器负责创建的 ,

    • 这个对象中封装了HTTP请求协议的全部内容

    • 实际上用户发送请求的时候 , 遵循了HTTP协议 , 发送的是HTTP的请求协议 ,

      Tomcat服务器将HTTP协议中的信息以及数据解析出来 , 然后Tomcat服务器把这些信息封装到HttpServletRequest对象当中 , 传给了我们Java程序员

    • java程序员面向HttpServletRequest接口编程 , 调用方法就可以获取请求信息了

  • request对象和response对象的生命周期 :

    • request对象和response对象 一个是请求对象 , 一个是响应对象 , 这两个对象只在当前请求中有效
    • 一次请求对应一个request.
    • 一次响应对应一个response.
①HttpServletRequest接口中有哪些常用的方法 ?
String      getParameter(String name)//通过name获取value这个一维数组的第一个元素,(这个方法最常用)
Map<String,String[]>    getParameterMap()      //这个是获取整个map集合  
Enumeration<String>	   getParameterNames()	  //这个是获取map中的name
String[]   getParameterValues(String name)	//这个是通过name获取value , 一般用在复选框的地方 , 获取整个一维数组
//以上的四个方法和获取用户输入的数据有关
  • 思考 : 如果是你 , 前端的form表单提交了数据之后 , 你准备怎么储存这些数据 , 你准备采用什么样的数据结构去存储这些数据呢 ?

    • 前端提交的数据格式是 :

    • username=jack&password=123456&aihao=s&aihao=d&aihao=t
      
    • 注意 : 前端表单提交的数据 , 假设提交了120这样的数字 , 其实是以字符串的形式提交的 , 所以前端提交的是字符串 , 后端获取的永远是一个字符串

    • 我会采用Map集合来存储

      • Map<String , String>  //这是不正确的
        //key存储String
        //value存储String
        //这种方法是不对的 ,
        //如果采用以上的数据结构存储会发现key重复的时候value覆盖
                
           key         value
         //-------------------
           username     jack
           password     123456
             aihao        s
             aihao        d   (后边会覆盖前边的)
             aihao        t   (后边会覆盖前边的)
        //-----------------------------------------------------
        
            //用这种方法存储
        Map<String , String[]>     
            key存储String
            value存储String[]
            
           key         value
         //-------------------
           username     {"jack"}
           password     {"123456"}
             aihao      {"s", "d", "t"}
        
    • 注意 : 通常是这样使用的 :

      • package com.sichen.javaweb.servlet;
        
        import jakarta.servlet.ServletException;
        import jakarta.servlet.http.HttpServlet;
        import jakarta.servlet.http.HttpServletRequest;
        import jakarta.servlet.http.HttpServletResponse;
        
        import java.io.IOException;
        import java.io.PrintWriter;
        import java.util.Enumeration;
        
        public class RequestTextServlet extends HttpServlet {
        
            @Override
            protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                    response.setContentType("text/html");
                    PrintWriter out = response.getWriter();
                //获取的是所有的name
        Enumeration<String> parameterNames = request.getParameterNames();
                while (parameterNames.hasMoreElements()){
                    String s = parameterNames.nextElement();
                    //这里获取的是对应name中的所有value , 然后下边通过增强for循环遍历所有的value
                    String[] parameterValues = request.getParameterValues(s);
        
                    out.print(s +"=");
                    //通过增强for循环遍历所有的value
                    for (String values : parameterValues){
                        out.print(values+",");
                    }
                    out.println("");
                }
            }
        
        }
        
        

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

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

  • 只在一次请求中存活

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

  • void setAttribute(String name ,Object obj);  //向域当中绑定数据
    Object getAttribute(String name); //从域当中根据name获取数据
    void removeAttribute(String name); //将域当中绑定的数据移除
    
  • 请求域和应用域的选用原则

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

③Servlet中的转发机制 (RequestDispatcher)
  • 执行了Aservlet之后 , 跳转到Bservlet (这个跳转可以使用Servlet的转发机制)

    • //这样可以吗? 在Aservlet中new一个Bservlet对象 ,
      // 然后调用Bservlet对象的doGet方法 , 把request对象传过去?
      Bservlet bservlet = new Bservlet();
      bservlet.doGet(request , response);
      //不行 , 因为你自己new的对象不受服务器的管理 , 违背了Servlet规范
      //
      
  • 怎么进行转发:

    • 第一步 : 获取请求转发器对象

    • RequestDispatcher    getRequestDispatcher(java.lang.String path)
      //相当于吧"/b"这个路径包装到请求转发器中 , 实际上是把下一个跳转的资源路径通知给Tomcat服务器了
       RequestDispatcher dispatcher = request.getRequestDispatcher("/b");  
      //括号中放要转发的资源的路径
      
    • 第二步 : 调用请求转发器RequestDispatcher的 forward方法 ,进行转发

    • 注意 : 转发的时候这两个参数很重要

    • void forward(ServletRequest request, ServletResponse response)
      //将这个传过去
      dispatcher.forward(request , response);
      
    • //两步合一
      request.getRequestDispatcher("/b").forward(request , response);
      
  • 这么做有什么好处呢?

    • 将数据放到ServletContext应用域中 , 当然是可以的 , 但是应用域范围太大 , 占用资源太多,不建议使用

    • 可以将数据放到request域当中 , 然后Aservlet转发到Bservlet中 , 在保证Aservlet和Bservlet在同一次请求当中 , 这样就可以做到两个Servlet ,或者多个Servlet共享一份数据

    • //Aservlet中在request对象中绑定数据 , 转发给Bservlet
      public class Aservlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              //获取当前系统时间
              Date date = new Date();
              //向request域中绑定数据
              request.setAttribute("systime",date);
              //获取转发器对象
              RequestDispatcher dispatcher = request.getRequestDispatcher("/b");
              //转发参数
              dispatcher.forward(request , response);
          }
      }
      
      //Bservlet 得到数据 ,并输出
      public class Bservlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              Object systime = request.getAttribute("systime");
              response.setContentType("text/html");
              PrintWriter out = response.getWriter();
              out.println("request域当中获取的系统时间" + systime );
          }
      }
      
  • 转发的下一个资源必须是一个Servlet吗?

    • 不一定 , 只要是Tomcat服务器当中的合法资源 , 都是可以转发的 例如 : html…

    • 注意 : 转发的时候路径要注意 : 转发的路径以"/"开始 , 不添加项目名

    • request.getRequestDispatcher("/index.html").forward(request , response);
      
  • 关于request对象中两个非常容易混淆的方法

    • String username = request.getParameter("username");
      
      Object obj = request.getAttribute("name");
      //以上两个方法的区别是什么?
      //第一个方法 : 获得的是用户浏览器上提交的数据
      //第二个方法 : 获得的是请求域当中绑定的数据 
      

HttpServletRequest的其他常用方法
①获取客户端IP地址,远程主机和远程端口
  • java.lang.String	getRemoteAddr()   //获取IP地址
    request.getRemoteAddr();
    //输出  127.0.0.1  因为我是本机访问
    
    java.lang.String	getRemoteHost()  //获取远程主机
    int	               getRemotePort()     //获取远程端口
    
②解决post请求(get请求) 请求体中文编码问题(Tomcat 9及之前)
  • //设置请求体的字符集  (处理psot请求的请求体中文乱码问题 , 这种方式并不能解决get请求的乱码问题)   
    //Tomcat10之后的字符集默认就是 UTF-8
    //Tomcat9之前(包括9) 如果前端请求体提交的数据是中文 , 就会出现乱码现象 , 怎么解决,就用下边的代码
    void	setCharacterEncoding(java.lang.String env)
    request.setCharacterEncoding("UTF-8");
    
  • Get请求乱码怎么解决?

  • 修改CATALINA_HOME/conf/server.xml配置文件 增加下边的代码即可

  • 1642473504311
  • 注意 : Tomcat 8之前Get请求会出现乱码的 , 但是Tomcat8之后Get请求就解决了乱码问题 , 默认就是UTF-8,不用再增加上边的代码了

④动态获取根路径 (虚拟目录)
  • //也是使用contextpath(); 这个方法 和ServletContext中的方法一样
    String  getcontextPath();
    //使用
    String request.getcontextPath();  //  输出:  /servlet08
    
⑤获取请求方式
  • String getMethod();
    //使用
    String request.getMethod();   //  输出: 例子: POST
    
⑥获取请求的URI(统一资源标识符)
  • String getRequestURI();
    //使用
    request.getRequestURI();   //输出:  /servlet08/request
    
⑦获取Servlet 路径
  • String getServletPath();
    //使用
    request.getServletPath();  //输出: /request
    

(4)HttpServletResponse

①设置响应状态码
setStatus(int sc)

②设置响应头
setHeader(String name , String value)
③重定向
//方法一
public void sendRedirect(String location) 
//使用
response.sendRedirect("/项目名/b");

//方法二
//设置响应状态码 
response.setStatus(302);
//设置响应头 
response.setHeader("location" , "/虚拟目录/路径")

**注意:**跳转的资源只要是服务器中的合法资源即可跳转


④获取输出流

(1)字符输出流 : PrintWriter getWriter()

PrintWriter pw = response.getWriter();
//这个流不用关闭
//这里使用 pw.write(String s)方法 来输出字符 
//因为使用这个流不需要刷新 , 它是response调用的方法 ,  因为response在一次请求之后就会销毁 , 这个流也随之销毁 

这个流还有一个方法 : print() , 这个方法输出的对象可以自动刷新

(2)字节输出流 : ServletOutputStream getOutputStream

response.setContentType("text/html;charset=utf-8");
ServletOutputStream outputStream = response.getOutputStream();
//这里要注意,在输出的时候 , 要在后边加.getBytes("utf-8")
outputStream.write("你好".getBytes("UTF-8"));

⑤解决响应乱码(中文输出乱码)问题 (Tomcat 9及之前)
  • response.setContextType("text/html");
    PrintWriter out = response.getWriter();
    out.print("我是一个Java菜鸟");
    //在Tomcat 9 及之前的版本 , 这样输出到浏览器 , 中文会乱码 , 解决方法如下
    //response.setContextType("text/html;charset=UTF-8");
    
  • 注意 :

  • 2020年2月28日 Tomcat10发布之后Tomcat就在 conf/web.xml 中使用

  • 将默认请求和响应字符编码设置为 UTF-8。 , 不再需要进行配置即可直接输入输出中文 , 以及大多数语言


(5)跳转和重定向的区别

  • 在一个web应用中通过两种方式 , 可以进行资源的跳转

    • 第一种方式 : 转发
    • 第二种方式 : 重定向
  • 转发和重定向的区别?

    • 代码上有什么区别?

      • 转发 (是一次请求!)

        • //获取请求转发器
          RequestDispatcher dispatcher =  request.getRequestDispatcher("/转发到的路径");
          //调用转发器的forward方法 , 完成转发
          dispatcher.forward(request,response);
          //合并一行代码
          request.getRequestDispatcher("/转发到的路径,以斜杠"/"开始 , 不用加项目名").forward(request,response);
          //传这两个参数是为了 : 将数据绑定到request中 , 可以多个Servlet共享数据 , 这多个Servlet是一个请求域 , 相当于是一次请求
          
      • 重定向

        • //重定向时的路径当中需要以项目名开始
          //原因是 response具有响应能力 , 他可以把这个路径重新响应给浏览器 , 浏览器能够自发的向服务器发送一个请求
          //response对象将这个路径 "/servlet10/b"响应给了浏览器
          //浏览器又自发的向浏览器发送了一次全新的请求 http://localhost:8080/servlet10/b
          //所以浏览器一共发送了两次请求
          //第一次 : http://localhost:8080/servlet10/a
          //第二次 : http://localhost:8080/servlet10/b
          //最终浏览器地址栏的地址会显示最后一次请求的路径 , 所以会导致浏览器地址栏的路径改变
          response.sendRedirect("项目名/b");
          
    • 形式上有什么区别?

      • 转发 (一次请求)
        • 在浏览器上发送的请求是 : http://localhost:8080/servlet10/a 最终请求结束之后 , 浏览器地址栏上的地址还是这个 , 没变
      • 重定向 (两次请求)
        • 将重定向中的路径反馈给浏览器 , 浏览器自发的重新发送一次请求 给浏览器 , 所以是两次请求 , 最终浏览器的地址栏会发生改变
    • 转发和重定向的本质区别 :

      • 转发 : 是由Web服务器来控制的 , A资源转发到B资源 , 这个跳转动作是由Tomcat服务器内部来进行操作的

      ​ 转发只能在内部资源内跳转

    • 重定向 : 是由浏览器控制的 , 具体跳转到那个页面 , 是由浏览器说了算 可有重定向到其他服务器的资源

    • 转发和重定向应该如何选择?

      • 什么时候使用转发?
        • 如果在上一个Servlet对象中向Servlet域当中绑定了数据 , 希望从下一个Servlet当中把request域当中的数据取出来 , 使用转发机制
      • 什么时候使用重定向?
      • 其他情况一般都使用重定向 , 重定向使用较多 ,
        • 比如 , 新增数据 , 删除数据的时候 , 要用doPost方法 , 但是返回新增成功后 , 跳转回之前页面的时候 , 之前页面中只有doGet方法 , 这样会报405错误 ,
        • 使用重定向就可以解决这个问题
    • 转发会出现浏览器的刷新问题

      • 就是浏览器在点击刷新的时候 , 会重新发送一次 最近的请求 , 如果使用转发的话 , 会重复执行多次请求 , 而这些请求会重新执行编写的程序中的方法 ,
      • 比如新增数据 , 每刷新一次 , 就会执行一次新增操作 ,造成在数据库中增加很多数据

17.关于一个web站点的欢迎页面

(1)什么是一个web站点的欢迎页面?

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

(2)怎么设置你的欢迎页面

  • 第一步: 在idea的WEB目录下新建一个login.html

  • 第二步 : 在web.xml文件中进行以下配置

  • <welcome-file-list>
            <welcome-file>login.html</welcome-file>
    </welcome-file-list>
    
  • 第三步 : 启动服务器 , 浏览器地址栏输入地址

  • http://localhost:8080/ + 项目名

如果在webapp根目录下设置一个文件夹 , 文件夹中在设置一个文件夹 , 文件夹中是欢迎页面 , 那么怎么找到这个欢迎页面呢?

  • <welcome-file-list>
            <welcome-file>page1/page2/index.html</welcome-file>
    </welcome-file-list>
    
  • 注意 : 路径不需要以 “/” 开始 , 并且路径默认从webapp的根下开始找

  • 一个webapp是可以设置多个欢迎页的 , 越靠上的优先级越高 ,找不到的继续向下找 ,

    都找不到就报404

  • <welcome-file-list>
            <welcome-file>login.html</welcome-file>
            <welcome-file>page1/page2/index.html</welcome-file>
    </welcome-file-list>
    
  • 注意 : 在WEB的根下设置一个index.html 就不用在web.xml文件下重新设置欢迎页面 ,

    这是因为Tomcat服务器默认是以index.html 为初始页面的

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

    • 一个是在webapp内部的web.xml文件中 (在这个地方配置的属于局部变量)
    • 一个是在CATALINA_HOME/conf/web.xml文件中进行配置 (在这个地方配置属于全局变量)
  • 注意 : 局部优先


(3)欢迎页可以是一个Servlet吗?

  • 可以

  • 静态资源 : XXX.html

  • 动态资源 : Servlet

  • 	 <servlet>
            <servlet-name>welcome</servlet-name>
            <servlet-class>com.sichen.javaweb.servlet.welcomeServlet</servlet-class>
        </servlet>
        <servlet-mapping>
            <servlet-name>welcome</servlet-name>
            <url-pattern>/welcome</url-pattern>
        </servlet-mapping>
        
        <welcome-file-list>
            <welcome-file>welcome</welcome-file>
        </welcome-file-list>
    

18.关于WEB-INF目录

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

19.多线程处理

Tomcat服务器是支持多线程的

大部分时间我们我们要考虑的是代码放在多线程的环境下,数据的安全的呢


20.大家目前位置都接触过那些缓存机制?

  • 堆内存当中的字符串常量池
    • 例如: "abc"先是在字符串常量池中查找,如果有,直接拿来用,如果没有则新建,然后再放入字符串常量池中
  • 堆内存中的整数型常量池
    • [-128 ~ 127] 一共256个Integer类型的引用,放在整数型常量池中,没有超出这个范围的话,直接从常量池中获取
  • 连接池 (Connection Cache)
    • 这里所说的连接池是说:java语言连接数据库的连接对象:java.sql.Connection对象
    • JVM是一个进程,MYSQL数据库是一个进程,进程和进程之间打开通道是很费劲的,是很耗费资源的,怎么办?
      • 可以先创建好N个Connection连接对象,再将连接对象放到一个集合当中,我们把这个放油Connection对象的集合称为;连接池,每一次用户连接的时候,不需要新建连接对象,省去了新建的环节,直接从连接池中获取连接对象,大大提升访问效率
    • 连接池:
      • 效率高,安全,
      • 最大连接数,最小连接数,
      • 用户访问只能从连接池中获取对象,无法重新创建,防止了大量用户连接.,到达一定量就不能连接了

  • 线程池
    • Tomcat服务器本身就是支持多线程的
    • Tomcat服务器是在用户发送一次请求后就,新建一个Thread线程对象吗?
      • 当然不是,实际上是在Tomcat服务器启动的时候,会先创建好N多个线程Thread对象,然后将线程对象放到集合中去,称为线程池,用户发送请求之后,需要有一个对应的线程对象,来处理这个请求,这个时候线程对象就会直接从线程池中拿,效率较高
      • 所有的web服务器(或者说是应用服务器)都是支持多线程的,都有线程池机制
  • redis
    • NoSQL数据库,非关系型数据库,缓存数据库
  • 向ServletContext这个应用域中存放数据,也等同于将数据存放到缓存池当中了
    • 第一 : 所有用户共享的数据
    • 第二 : 这个共享的数据量很小
    • 第三 : 这个共享的数据很少的修改
    • 在以上三个条件都满足的情况下 , 使用这个应用域对象 , 可以大大提高我们程序的执行效率
    • 实际上向应用域中绑定数据 , 就相当于把数据放到了缓存(Cache)当中 , 然后用户访问的时候直接从缓存中取 , 减少io操作 , 大大提升系统的性能 , 所以缓存技术是提高系统性能的重要手段

13.transient修饰的属性不会被序列化(最后了解下)

表示可以被序列化

java.io.Serializable

区分类的序列号: 只要版本号一样就认为是一个class

private static final long serialVersionUID = 1L;

21.练习:使用纯Servlet实现单表的CRUD操作

  • 使用纯粹的Servlet完成单表 [对部门的] 的增删改查操作 (B/S结构的)

  • 实现步骤:

    • 第一步 : 准备一个数据库表

      • 写一个sql脚本 (执行sql脚本 , 在cmd中 , 这个数据库下 ,source 加)

      • drop table if exists dept;
        create table dept(
          deptno int primary key,
            dname	varchar(255),
            loc   	varchar(255)
        );
        insert into dept(deptno,dname,loc) values(10,'xiaoshoubu','shanghai');
        insert into dept(deptno,dname,loc) values(20 ,'yanfabu','beijing');
        insert into dept(deptno,dname,loc) values(30 ,'jishubu','guangzhou');
        insert into dept(deptno,dname,loc) values(40 ,'meitibu','shenzhen'); 
        commit;
        select * from dept;
        
  • 第二步: 准备一套HTML页面 , (项目原型) [前端工具HBuilder]

    • 把HTML页面准备好
    • 然后将HTML页面中的链接都跑通 (页面流转没问题)
    • 应该设计那些页面呢?
      • 欢迎页面 welcome.html
      • 列表页面 list.html (以列表页面为核心 , 展开其他操作)
      • 新增页面 add.html
      • 修改页面 edit.html
      • 删除页面
      • 查询页面 detail.html
readonly 只读 , 解决那些属性不能修改的东西
  • 第三步 : 分析我们系统包括那些功能

    • 什么叫做一个功能?
      • 只要你这个操作连接了数据库 , 就是一个独立的功能
    • 包括那些功能
      • 查看部门列表
      • 新增部门
      • 删除部门
      • 查看部门详情
      • 跳转到修改页面
      • 修改部门
  • 第四步 : 在idea当中搭建开发环境

    • 创建一个webapp
    • 向webapp中添加MySQL驱动
      • 必须在WEB-INF目录下必须新建一个lib包放mysql驱动
    • jdbc的工具类
    • 将所有的HTML页面拷贝到web目录下
  • 第五步 : 实现第一个功能 :查看部门列表

    • 我们应该怎么去实现一个功能呢?

      • 建议 : 你可以从后端往前端一步一步写 , 也可以从前端一步一步往后端写

      • 不要想起来什么写什么 , 你写代码的过程 ,最好是程序的执行过程 , 程序执行哪里,你就写哪里 , 这样一个顺序流下来之后 , 不会出现什么错误 , 意外

      • 从那里开始?

        • 假设从前段开始 , 那么一定是从用户点击按钮开始的
      • 第一 : 先修改前端页面的超链接

        • <a href="/oa/dept/list" >查看部门列表</a>
          
      • 第二 : 编写xml文件

        • <servlet>
                  <servlet-name>list</servlet-name>
                  <servlet-class>com.sichen.oa.action.DeptListServlet</servlet-class>
              </servlet>
              <servlet-mapping>
                  <servlet-name>list</servlet-name>
                  <!-- 注意web.xml文件中的路径也是以"/"开始的 , 但是不需要加项目名-->
                  <url-pattern>/dept/list</url-pattern>
              </servlet-mapping>
          
      • 第三 : 编写DeptListServlet类继承HttpServlet ,重写doget方法 (因为超链接是get请求)

        • package com.sichen.oa.action;
          
          import jakarta.servlet.ServletException;
          import jakarta.servlet.http.HttpServlet;
          import jakarta.servlet.http.HttpServletRequest;
          import jakarta.servlet.http.HttpServletResponse;
          
          import java.io.IOException;
          
          public class DeptListServlet extends HttpServlet {
              @Override
              protected void doGet(HttpServletRequest request, HttpServletResponse response)
                      throws ServletException, IOException {
          
              }
          }
          
      • 第四 : 在DeptListServlet类中doGet方法中连接数据库 , 查询所有的部门 , 动态的展示部门列表

        • 分析list.html页面中的哪部分是固定死的 , 哪部分是要动态展示的
          list.html页面中的内容所有的双引号要替换成单引号 , 因为out.print("")中有一个双引号,会发生冲突
          
        • DeptListServlet.java

          package com.sichen.oa.action;
          
          import com.sichen.oa.utils.DButil;
          import jakarta.servlet.ServletException;
          import jakarta.servlet.http.HttpServlet;
          import jakarta.servlet.http.HttpServletRequest;
          import jakarta.servlet.http.HttpServletResponse;
          
          import java.io.IOException;
          import java.io.PrintWriter;
          import java.sql.Connection;
          import java.sql.PreparedStatement;
          import java.sql.ResultSet;
          import java.sql.SQLException;
          
          public class DeptListServlet extends HttpServlet {
              @Override
              protected void doGet(HttpServletRequest request, HttpServletResponse response)
                      throws ServletException, IOException {
                  //设置响应类型以及字符集 防止中文乱码问题
                  response.setContentType("text/html;charset=UTF-8");
                  PrintWriter out = response.getWriter();
          
                    out.print("            <!DOCTYPE html>");
                    out.print("  <html>");
                    out.print("  <head>");
                    out.print("      <meta charset='utf-8'>");
                    out.print("      <title>部门列表页面</title>");
                    out.print("  </head>");
                    out.print("  <body>");
                    out.print("  <h1 align='center' >部门列表</h1>");
                    out.print("  <hr>");
                    out.print("  <table border='1px' align='center' width='50%'>");
                    out.print("      <tr>");
                    out.print("          <th>序号</th>");
                    out.print("          <th>部门编号</th>");
                    out.print("          <th>部门名称</th>");
                  out.print("          <th>部门地址</th>");
                    out.print("          <th>操作</th>");
                    out.print("      </tr>");
                          //上边一部分是死的
          
                          //动态输出一个页面
                  //连接数据库 , 查询所有的部门
                  Connection conn = null;
                  PreparedStatement ps = null;
                  ResultSet rs = null;
                  try {
                      //获取连接
                      conn = DButil.getconnection();
                      //获取预编译的数据库操作对象
                      String sql = "select deptno,dname,loc from ceshi";
                      ps = conn.prepareStatement(sql);
                      //执行SQL语句
                      rs = ps.executeQuery();
                      //处理结果集
                      int i = 0;
                      while (rs.next()){
                          String deptno = rs.getString("deptno");
                          String dname = rs.getString("dname");
                          String loc = rs.getString("loc");
                      //这一部分是动态的
                          out.print("      <tr>");
                          out.print("          <td>"+(++i)+"</td>");
                          out.print("          <td>"+deptno+"</td>");
                          out.print("          <td>"+dname+"</td>");
                          out.print("          <td>"+loc+"</td>");
                          out.print("          <td>");
                          out.print("              <a href=''>删除</a>");
                          out.print("              <a href=''>修改</a>");
                          out.print("              <a href=''>详情</a>");
                          out.print("          </td>");
                          out.print("      </tr>");
                      }
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }finally{
                      //释放资源
                      DButil.close(conn , ps , rs);
                  }
                  //下边一部分是死的
                  out.print("  </table>");
                  out.print("  <hr >");
                  out.print("  <a href='add.html' >新增部门</a>");
                  out.print("  </body>");
                  out.print("  </html>");
              }
          }
          
        • 写完之后会发现只使用Servlet开发代码很繁琐

      • 第六步 : 实现详情功能

        • 假设从前端开始 , 那么一定是从用户点击详情按钮开始的

        • <a href='写一个路径'>详情</a>
          

          详情是需要连接数据库的 , 所以这个超连接点击之后也是需要执行java代码的 , 所以要将这个超连接的路径修改一下

        • /*注意 :这个路径是加项目名的*/ <a href='/oa/dept/detail'>详情</a>
          
      • 技巧 : get请求的格式要记住 URL后边加一个"?"后边加请求传递的名字和值

        • out.print("<a href='"+contextPath+"/dept/detail?deptno="+deptno+"'>详情</a>");
          
        • DeptDetailServlet.java

          package com.sichen.oa.action;
          
          import com.sichen.oa.utils.DButil;
          import jakarta.servlet.ServletException;
          import jakarta.servlet.http.HttpServlet;
          import jakarta.servlet.http.HttpServletRequest;
          import jakarta.servlet.http.HttpServletResponse;
          
          import java.io.IOException;
          import java.io.PrintWriter;
          import java.sql.Connection;
          import java.sql.PreparedStatement;
          import java.sql.ResultSet;
          import java.sql.SQLException;
          
          public class DeptDetailServlet extends HttpServlet {
              @Override
              protected void doGet(HttpServletRequest request, HttpServletResponse response)
                      throws ServletException, IOException {
                  //获取请求传过来的deptno
                  String deptno = request.getParameter("deptno");
          
                  response.setContentType("text/html;charset=UTF-8");
                  PrintWriter out = response.getWriter();
          
                     out.print("         <!DOCTYPE html>");
                     out.print(" <html>");
                     out.print("     <head>");
                     out.print("         <meta charset='utf-8'>");
                     out.print("         <title>部门详情</title>");
                     out.print("     </head>");
                     out.print("     <body>");
                     out.print("         <h1>部门详情</h1>");
                     out.print("         <hr >");
          
                  //编写数据库
                  Connection conn = null;
                  PreparedStatement ps = null;
                  ResultSet rs = null;
                  try {
                      //获取连接对象
                      conn = DButil.getconnection();
                      //获取预操作对象
                      String sql = "select dname,loc from ceshi where deptno=?;
                      ps = conn.prepareStatement(sql);
                      ps.setString(1,deptno);
                      rs = ps.executeQuery();
                      //处理结果集
                      //这里用if 而不用while是因为deptno在数据库中是主键 , 只能有一条记录,不用循环了
                      if (rs.next()){
                          String dname = rs.getString("dname");
                          String loc = rs.getString("loc");
          
                          out.print("                 部门编号 : "+deptno+" <br>");
                          out.print("                 部门名称 : "+dname+" <br>");
                          out.print("                  部门地址 : "+loc+" <br> ");
                      }
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }finally{
                      //释放资源
                      DButil.close(conn,ps,rs);
                  }
                  out.print("         <input type='button' value='后退' οnclick='window.history.back()' />");
                  out.print("     </body>");
                  out.print(" </html>");
              }
          }
          
      • 第七步 : 删除部门

        • 怎么开始? 从哪里开始 ? 从前端页面开始 , 用户点击删除按钮的时候 , 应该提示用户是否删除 , 因为删除操作是比较危险的 , 任何系统在进行删除操作之前是必须提醒用户的 , 因为这个删除操作可能是用户误操作的 , (在前端页面上写js代码 , 来提示用户是否删除)

        • 在DeptListServlet.java中加上这一段代码

        • //在删除中修改为以下代码
          out.print("<a href='javascript:void(0)' οnclick='del("+deptno+")'>删除</a>");
          
        • out.print("<script type='text/javascript'>");
          out.print("function del(dno){");
          out.print("var ok = window.confirm('亲 , 删除了不可恢复哦');" );
          out.print("if (ok){");
          out.print("document.location.href = '"+contextPath+"/dept/delete?deptno='+dno ;");
          out.print("}");
          out.print("}");
          out.print("</script>");
          
        • 然后编写DeptDeleteServlet.java

        • package com.sichen.oa.action;
          
          import com.sichen.oa.utils.DButil;
          import jakarta.servlet.ServletException;
          import jakarta.servlet.http.HttpServlet;
          import jakarta.servlet.http.HttpServletRequest;
          import jakarta.servlet.http.HttpServletResponse;
          
          import java.io.IOException;
          import java.sql.Connection;
          import java.sql.PreparedStatement;
          import java.sql.SQLException;
          
          public class DeptDeleteServlet extends HttpServlet {
              //定义的这个值是后边判断删除影响的数量的
              int s = 0;
              @Override
              protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                  String deptno = request.getParameter("deptno");
                  Connection conn = null;
                  PreparedStatement ps = null;
                  try {
                      conn = DButil.getconnection();
                      //数据库操作删除 , 修改的时候最好开启事务 ,这是设置事务不自动提交
                      conn.setAutoCommit(false);
                      String sql = "delete from ceshi where deptno=?";
                      ps = conn.prepareStatement(sql);
                      ps.setString(1 , deptno);
                      s = ps.executeUpdate();
                          //提交事务
                          conn.commit();
                  } catch (SQLException e) {
                      //这个代码要放在catch中 , 如果出现异常 , conn不为空说明是有操作的 , 但是出现异常,那么就把数据回滚到事务开始之前的状态 , 所以要放在异常处理的里边 
                      if (conn!=null){
                          try {
                              conn.rollback();
                          } catch (SQLException ex) {
                              ex.printStackTrace();
                          }
                      }
                      e.printStackTrace();
                  }finally{
                      DButil.close(conn,ps,null);
                  }
                  //最后判断删除是否成功 , 然后跳转页面
                  if (s==1) {
                      //删除成功
                      //仍然跳转到部门列表页面
                      //部门列表页面显示需要执行另一个Servlet , 怎么办 ,转发机制
                      request.getRequestDispatcher("/dept/list").forward(request , response);
                  }else {
                      //删除失败
                      request.getRequestDispatcher("/deletefalse.html").forward(request , response);
                  }
              }
          }
          
          
        - 删除成功或者失败的时候的一个处理 , (这里我们使用的是转发机制 , 因为我们到这里还没有学重定向机制)
        
        - ```java
              //最后判断删除是否成功 , 然后跳转页面
              if (s==1) {
                  //删除成功
                  //仍然跳转到部门列表页面
                  //部门列表页面显示需要执行另一个Servlet , 怎么办 ,转发机制
                  //转发的下一个资源不仅能是一个java程序 , 还可以是一个静态网页
                  request.getRequestDispatcher("/dept/list").forward(request , response);
              }else {
                  //删除失败
                  request.getRequestDispatcher("/deletefalse.html").forward(request , response);
              }
        
      • 第八步 :新增部门

        • 在list页面中 ,点击新增部门

        • add.html

        • <!DOCTYPE html>
          <html>
          	<head>
          		<meta charset="utf-8">
          		<title>新增部门</title>
          	</head>
          	<body>
          		<h1>新增部门</h1>
          		<hr>
          		<form action="/oa/dept/add" method="post">
          			部门编号<input type="text" name="deptno" /><br>
          			部门名称<input type="text" name="dname"/><br>
          			部门位置<input type="text" name="loc" /><br>
          			<input type="submit" value="保存"/>
          		</form>
          		
          	</body>
          </html>
          
        • DeptAddServlet.java

        • package com.sichen.oa.action;
          
          import com.sichen.oa.utils.DButil;
          import jakarta.servlet.ServletException;
          import jakarta.servlet.http.HttpServlet;
          import jakarta.servlet.http.HttpServletRequest;
          import jakarta.servlet.http.HttpServletResponse;
          
          import java.io.IOException;
          import java.sql.Connection;
          import java.sql.PreparedStatement;
          import java.sql.ResultSet;
          import java.sql.SQLException;
          
          public class DeptAddServlet extends HttpServlet {
              @Override
              protected void doPost(HttpServletRequest request, HttpServletResponse response)
                      throws ServletException, IOException {
                  int count = 0;
                  //获取部门信息 ,
                  //注意乱码问题 , (Tomcat10之后不会出现这个问题)
                  request.setCharacterEncoding("UTF-8");
                  String deptno = request.getParameter("deptno");
                  String dname = request.getParameter("dname");
                  String loc = request.getParameter("loc");
          
                  //连接数据库,执行insert语句
                  Connection conn = null;
                  PreparedStatement ps = null;
                  ResultSet rs = null;
                  try {
                      conn = DButil.getconnection();
                      String  sql = "insert into ceshi(deptno,dname,loc) values(?,?,?)";
                      ps = conn.prepareStatement(sql);
                      ps.setString(1,deptno);
                      ps.setString(2,dname);
                      ps.setString(3,loc);
                      count = ps.executeUpdate();
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }finally{
                      DButil.close(conn,ps,rs);
                  }
                  if (count == 1){
                      //执行成功 , 跳转到列表页面
                      //转发是一次请求 , 他转发的时候也是一个post请求 , 但是list中是doGet方法 , 会出现问题
                      //目前解决方法只能是在list中添加一个doPost方法 , 在doPost方法中调doGet方法
                      request.getRequestDispatcher("/dept/list").forward(request,response);
                  }else {
                      //保存失败 , 跳转错误页面
                      request.getRequestDispatcher("/deletefalse.html").forward(request,response);
                  }
              }
          }
          
        • 注意 : 最后的保存成功之后 , 转发到/dept/list的时候 , 会出现405 , 为什么?

          • 第一 : 保存的是post请求 , 底层执行的是doPost方法.
          • 第二 : 转发是一次请求 , 之前是post , 之后还是post , 因为它是一次请求
        • 第三 : /dept/list 中只有一个doGet方法

        • 怎么解决? 两种方法

          • 第一种 : 在.dept/list 中添加doPost方法 , 然后在doPost方法中调用doGet方法
        • 第二种 : 使用重定向

      • 第九步 : 修改部门

        • DeptEditServlet.java

        • package com.sichen.oa.action;
          
          import com.sichen.oa.utils.DButil;
          import jakarta.servlet.ServletException;
          import jakarta.servlet.http.HttpServlet;
          import jakarta.servlet.http.HttpServletRequest;
          import jakarta.servlet.http.HttpServletResponse;
          
          import java.io.IOException;
          import java.io.PrintWriter;
          import java.sql.Connection;
          import java.sql.PreparedStatement;
          import java.sql.ResultSet;
          import java.sql.SQLException;
          
          public class DeptEditServlet extends HttpServlet {
              @Override
              protected void doGet(HttpServletRequest request, HttpServletResponse response)
                      throws ServletException, IOException {
                  response.setContentType("text/html;charset=UTF-8");
                  PrintWriter out = response.getWriter();
                  //获取应用根路径
                  String contextPath = request.getContextPath();
          
                  out.print("<!DOCTYPE html>");
                  out.print("<html>");
                  out.print("<head>");
                  out.print("<meta charset='utf-8'>");
                  out.print("<title>修改页面</title>");
                  out.print("</head>");
                  out.print("<body>");
                  out.print("<h1>修改部门</h1>");
                  out.print("<hr>");
                  //只要是向服务器提交数据 , 就用post请求
                  out.print("<form action='"+contextPath+"/dept/modify' method='post'>");
          
                  //获取部门编号
                  String deptno = request.getParameter("deptno");
                  //连接数据库 , 根据部门编号查询部门信息
                  Connection conn = null;
                  PreparedStatement ps = null;
                  ResultSet rs = null;
                  try {
                      conn = DButil.getconnection();
                      String sql = "select dname,loc from ceshi where deptno=? ";
                      ps = conn.prepareStatement(sql);
                      ps.setString(1,deptno);
                      rs = ps.executeQuery();
                      //这个结果集中只有一条记录
                      if (rs.next()){
                          //rs.getString里边的参数 , 是查询结果的列名 ,
                          String dname = rs.getString("dname");
                          String loc = rs.getString("loc");
          
                          out.print("部门编号<input type='text' name='deptno' value='"+deptno+"' readonly/><br>");
                          out.print("部门名称<input type='text' name='dname' value='"+dname+"'/><br>");
                          out.print("部门位置<input type='text' name='loc'  value='"+loc+"'/><br>");
          
                      }
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }finally{
                      DButil.close(conn,ps,rs);
                  }
                  out.print("<input type='submit' value='修改'/>");
                  out.print("</form>");
                  out.print("</body>");
                  out.print("</html>");
              }
          }
          
        • 跳转到DeptModifyServlet.java 去执行修改操作

        • package com.sichen.oa.action;
          
          import com.sichen.oa.utils.DButil;
          import jakarta.servlet.ServletException;
          import jakarta.servlet.http.HttpServlet;
          import jakarta.servlet.http.HttpServletRequest;
          import jakarta.servlet.http.HttpServletResponse;
          
          import java.io.IOException;
          import java.sql.Connection;
          import java.sql.PreparedStatement;
          import java.sql.SQLException;
          
          public class DeptModifyServlet extends HttpServlet {
              @Override
              protected void doPost(HttpServletRequest request, HttpServletResponse response)
                      throws ServletException, IOException {
                  String contextPath = request.getContextPath();
                  //解决请求体的中文乱码问题
                  request.setCharacterEncoding("UTF-8");
                  //获取表单中的数据
                  String deptno = request.getParameter("deptno");
                  String dname = request.getParameter("dname");
                  String loc = request.getParameter("loc");
          
                  //连接数据库 , 执行更新语句
                  Connection conn = null;
                  PreparedStatement ps = null;
                  int count = 0;
                  try {
                      conn = DButil.getconnection();
                      String sql = "update ceshi set dname=?,loc=? where deptno=?";
                      ps = conn.prepareStatement(sql);
                      ps.setString(1,dname);
                      ps.setString(2,loc);
                      ps.setString(3,deptno);
                      count = ps.executeUpdate();
          
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }finally {
                      DButil.close(conn, ps, null);
                  }
                  if (count == 1){
                      request.getRequestDispatcher("/dept/list").forward(request,response);
                  }else{
                      request.getRequestDispatcher("/dept/deletefalse.html").forward(request,response);
                  }
              }
          }
          

22.路径格式 :

超链接中的路径 : /项目名/具体路径

xml文件中的路径 : /具体路径

浏览器访问时的路径 : /项目名/具体路径

idea中的这个路径就是 /项目名

转发中的路径 /具体路径即可

重定向中的路径 /项目名/具体路径

1.相对路径 : 通过相对路径 , 不可以确定唯一资源

  • 如 : ./index.html 不以 / 开头 , 而是以 . 开头

  • 注意 :

    规则 : 找到访问的当前资源和目标资源之间的相对位置关系

    • . / : 当前目录
    • …/ : 后退一级目录

2.绝对定位 : 通过绝对路径可以确定唯一资源

  • 如 : http://location/sichen/index.html --> 化简写法 : /sichen/index.html
  • 以 / 开头的路径
1642514744106

23.javaBean

构成

package com.sichen.javaweb.bin;

import java.io.Serializable;
import java.util.Objects;

/**
 * 一个普通的javabean
 * 什么是javabean
 * java咖啡
 * bean 豆子
 * javabean 咖啡豆
 * 咖啡是由咖啡豆研磨而成
 * 寓意 java是由一个一个javabean组成的
 * 一个javabean一般是有规范的
 *          (1)类必须被public修饰
 *          (2)无参数的构造方法
 *          (3)属性私有化(private)
 *          (4)对外提供setter和getter方法
 *          重写toString
 *          重写Hashcode 加 equals
 *          实现java.io.Serializable接口 (可序列化)
 *
 */
public class user implements Serializable {
    private String id;
    private String name;

    public user() {
    }

    public user(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        user user = (user) o;
        return Objects.equals(id, user.id) && Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }

    @Override
    public String toString() {
        return "user{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

作用 :

一般是用来封装数据的

概念:

(1)成员变量

(2) 属性 : setter 和 getter方法截取后的产物

​ 例如 : getUsername() --> Username --> username

在一般情况下 , 属性名和变量名一样


方法 :

(1) getProperty() 获取属性

(2) setProperty() 设置属性

(3) populate() 将获取的数据集合封装为一个javabean对象


BeanUtils工具类

对于参数较多的类 , 如果还使用下边的方法显得十分麻烦

//2.获取请求参数
String name = req.getParameter("name");
String password = req.getParameter("password");
//3.封装user对象
User loginuser = new User();
loginuser.setName(name);
loginuser.setPassword(password);

这个时候 , 可以使用 BeanUtils 工具类

  • 需要导入两个依赖

1643993537437

//2.获取所有请求参数
Map<String, String[]> map = req.getParameterMap();
//3.创建User对象
User loginuser = new User();
//3.2使用BeanUtils封装
try {
    //这个方法 populate可以将获取的数据map集合封装到User对象中
   BeanUtils.populate(loginuser , map);
} catch (IllegalAccessException e) {
   e.printStackTrace();
} catch (InvocationTargetException e) {
   e.printStackTrace();
}

24.验证码(防止恶意注册)

①先将验证码的图片放在本地文件夹中 ,然后使用的时候从中间选一张出来

②使用随机生成的验证码 (常用)

package cn.sichen.web.servlet;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

@WebServlet(name = "yanzhengServlet", value = "/yanzhengServlet")
public class yanzhengServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request , response);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        int width = 100;
        int height = 50;
        //1.创建一个对象 , 能在内存中画图(验证码的画图对象)
        //这里要传三个参数 , 宽 高 和图片类型 , 一般使用RGB的图片类型
        BufferedImage image = new BufferedImage(width , height , BufferedImage.TYPE_INT_RGB);
        //2.美化图片
        //2.1填充背景色 , 获取画笔对象
        Graphics g = image.getGraphics();
        //设置画笔颜色
        g.setColor(Color.pink);
        //填充   四个参数 , 从那个位置开始 , 填充大小
        g.fillRect(0,0,width , height);
        //2.2 画边框  drawRect()
        g.setColor(Color.blue);
        g.drawRect(0,0,width-1,height-1);
        //验证码的全部字符
        String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        //生成随机脚标
        Random random = new Random();
        //创建一个字符串缓冲区
   		StringBuilder sb = new StringBuilder();
        for (int x = 1; x <= 4; x++) {
            int i = random.nextInt(str.length());
            //获取字符
            char c = str.charAt(i);//随机字符
            //将字符放入缓冲区
            sb.append(c);
            //2.3 写验证码
            g.drawString(c+"" , width/5*x , height/2);
        }
        //将缓冲区字符转为String类型的
        String checkCode_session = sb.toString();
        HttpSession session = request.getSession();
        session.setAttribute( "checkCode_session",checkCode_session);

        //2.4 画干扰线
        g.setColor(Color.GREEN);
        //g.drawLine(1,1,30,30);
        //随机生成坐标点
        for (int x = 1 ; x<=5 ; x++) {
            int x1 = random.nextInt(width);
            int x2 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int y2 = random.nextInt(height);
            g.drawLine(x1 , y1 , x2 , y2);
        }
        //3.将这个图片输出到页面展示(response)
        ImageIO.write(image , "jpg" , response.getOutputStream());
    }
}

register.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>register</title>
  <script>
    /*1.点击超链接 或者图片需要换一张
        给图片和超链接绑定单击事件
    * */
    window.onload = function (){
      //获取图片对象
      var img = document.getElementById("checkCode");
      //绑定单击事件
      img.onclick = function (){
        //这里只写这个的话 , 会因为浏览器换存导致图片无法切换 , 可以加一个?传一个时间戳
        // img.src = "/login/yanzhengServlet";
        var date = new Date().getTime();
        img.src = "/login/yanzhengServlet?"+date;
      }
      var a = document.getElementById("change");
      a.onclick = function (){
        var date = new Date().getTime();
        img.src = "/login/yanzhengServlet?"+date;
      }
    }
  </script>
</head>
<body>
  <img id="checkCode" src="/login/yanzhengServlet"/>
  <a id="change" href="">看不清换一张?</a>
</body>
</html>

Servlet的注解 :

  • @WebServlet
  • name 属性 : 用来指定Servlet的名字 , 等同于 :
  • urlPatterns 属性 : 用来指定Servlet的映射路径 , 等同于 : 可以写多个路径 . 解决类爆炸问题
  • load

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


---

# 24.验证码(防止恶意注册)

①先将验证码的图片放在本地文件夹中 ,然后使用的时候从中间选一张出来

②使用随机生成的验证码 (常用)

```java
package cn.sichen.web.servlet;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

@WebServlet(name = "yanzhengServlet", value = "/yanzhengServlet")
public class yanzhengServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request , response);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        int width = 100;
        int height = 50;
        //1.创建一个对象 , 能在内存中画图(验证码的画图对象)
        //这里要传三个参数 , 宽 高 和图片类型 , 一般使用RGB的图片类型
        BufferedImage image = new BufferedImage(width , height , BufferedImage.TYPE_INT_RGB);
        //2.美化图片
        //2.1填充背景色 , 获取画笔对象
        Graphics g = image.getGraphics();
        //设置画笔颜色
        g.setColor(Color.pink);
        //填充   四个参数 , 从那个位置开始 , 填充大小
        g.fillRect(0,0,width , height);
        //2.2 画边框  drawRect()
        g.setColor(Color.blue);
        g.drawRect(0,0,width-1,height-1);
        //验证码的全部字符
        String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        //生成随机脚标
        Random random = new Random();
        //创建一个字符串缓冲区
   		StringBuilder sb = new StringBuilder();
        for (int x = 1; x <= 4; x++) {
            int i = random.nextInt(str.length());
            //获取字符
            char c = str.charAt(i);//随机字符
            //将字符放入缓冲区
            sb.append(c);
            //2.3 写验证码
            g.drawString(c+"" , width/5*x , height/2);
        }
        //将缓冲区字符转为String类型的
        String checkCode_session = sb.toString();
        HttpSession session = request.getSession();
        session.setAttribute( "checkCode_session",checkCode_session);

        //2.4 画干扰线
        g.setColor(Color.GREEN);
        //g.drawLine(1,1,30,30);
        //随机生成坐标点
        for (int x = 1 ; x<=5 ; x++) {
            int x1 = random.nextInt(width);
            int x2 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int y2 = random.nextInt(height);
            g.drawLine(x1 , y1 , x2 , y2);
        }
        //3.将这个图片输出到页面展示(response)
        ImageIO.write(image , "jpg" , response.getOutputStream());
    }
}

register.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>register</title>
  <script>
    /*1.点击超链接 或者图片需要换一张
        给图片和超链接绑定单击事件
    * */
    window.onload = function (){
      //获取图片对象
      var img = document.getElementById("checkCode");
      //绑定单击事件
      img.onclick = function (){
        //这里只写这个的话 , 会因为浏览器换存导致图片无法切换 , 可以加一个?传一个时间戳
        // img.src = "/login/yanzhengServlet";
        var date = new Date().getTime();
        img.src = "/login/yanzhengServlet?"+date;
      }
      var a = document.getElementById("change");
      a.onclick = function (){
        var date = new Date().getTime();
        img.src = "/login/yanzhengServlet?"+date;
      }
    }
  </script>
</head>
<body>
  <img id="checkCode" src="/login/yanzhengServlet"/>
  <a id="change" href="">看不清换一张?</a>
</body>
</html>

Servlet的注解 :

  • @WebServlet
  • name 属性 : 用来指定Servlet的名字 , 等同于 :
  • urlPatterns 属性 : 用来指定Servlet的映射路径 , 等同于 : 可以写多个路径 . 解决类爆炸问题
  • load

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值