转自 千峰教育B站视频资料
一、web服务器
思考:我们设计好的的网页如何才能够让互联网用户通过浏览器访问呢?
1.1 什么是web服务器?
web服务器就是web项目的容器,我们将开发好的web项目部署到web容器中,才能使用网络中的用户通过浏览器进行访问。
1.2 静态资源和动态资源
web项目中的资源根据其特性可以分为两类:
静态网页:网页界面中的数据始终保持不变(HTML/CSS/JS/图片)
动态网页:网页中的数据根据用户不同的请求可以会发生变化,也就是说网页中的数据是由程序根据用户的请求意图动态产生的
1.3 常用服务器产品
-
Tomcat(Apache开源,主流的web服务器之一,多应用于Javaweb项目开发)
-
Jetty(运行效率高于Tomcat)
-
WebLogic (Oracle,收费)
-
WebSpere(IBM)
-
Nginx(web静态资源服务器)
1.4 Tomcat简介
Tomcat是Apache开源组织(apache.org)共享的Jakarta项目中的一个核心向,Tomcat开源免费、支持Java中的动态网页技术Servlet和JSP规范,因此Java开发者多使用Tomcat.
Tomcat目前最新版本是10.1(alpha),在企业主流使用8.5和9,我们的学习主要基于8.5.72版本;各个版本之间的最大区别就是对于JavaEE版本及Servlet规范的支持、依赖的JDK版本。Tomcat8.x全面支持Servlet3.x规范及JavaEE4规范。
Tomcat基于其先进的技术、稳定的性能深受Java开发者的青睐。
Tomcat官网:Apache Tomcat® - Welcome!
1.5 Tomcat的安装
1.5.1 下载
(Tomcat v8.5.72)下载页面地址:Apache Tomcat® - Apache Tomcat 8 Software Downloads
1.5.2 解压安装
如果下载的是压缩包,则解压即可使用
解压注意事项:
建议解压的目标目录层级不要多
不建议解压到中文目中
1.5.3 Tomcat的目录结构
-
bin
该目录存放的是可执行的二进制文件(startup.bat用于启动Tomcat、shutdown.bat用于停止Tomcat) -
conf
存放的是Tomcat的配置文件(server.xml可以配置Tomcat的端口,web.xml) -
lib
此目录存放了Tomcat服务器运行web项目所需的基础类库 -
logs
存放Tomcat服务器的运行日志、记录了服务器启动、运行异常及关闭等操作的记录 -
temp
临时目录,存放Tomcat运行过程中产生的临时文件 -
webapps
存放Tomcat管理的web项目的目录,此目录中默认部署了Tomcat管理器等几个web项目 -
work
Tomcat可以运行动态网页,动态网页就是在服务器上将数据加载到网页生成的页面,此目录就是存放Tomcat生成的文件
1.6 Tomcat启动和关闭
方式1:双击运行...\apache-tomcat-8.5.72\bin\starup.bat
关闭窗口,服务器就关闭了
方式2:双击运行...\apache-tomcat-8.5.72\bin\tomcat8.exe
关闭窗口,服务器就关闭了
方式3:双击运行...\apache-tomcat-8.5.72\bin\tomcat8w.exe
(这种方式启动服务器之后,如果关闭了窗口,服务器依然在运行,我们就不能重复启动)
Tomcat无法启动:
-
Tomcat是基于Java语言的web服务器,它的运行需要依赖JDK,因此在安装Tomcat之前要确保计算机上安装了JDK并正确配置环境变量(特别是JAVA_HOME这个一定要配置正确)
1.7 web项目部署
web项目部署:将web项目交给Tomcat管理,当用户访问Tomcat时,Tomcat可以将web项目中的资源响应给用户浏览器。
方式1:直接将web项目拷贝到Tomcat的
webapps
目录 (访问路径就是项目名)方式2:将web项目的路径配置到Tomcat中
拷贝web项目的路径
E:\JavaWeb\workspace\html\demo1
在Tomcat的conf目录中
Catalina\localhost
创建一个xml文件(xml文件名可以自定义,建议和项目名称一致)在xml文件中如下配置:
path配置web项目的访问路径
docBase配置web项目的目录路径
<Context path="/demo1" docBase="E:\JavaWeb\workspace\html\demo1"></Context>
1.8 浏览器访问web服务器
部署项目完成之后,建议可以重启一次Tomcat
打开浏览器输入网址:http://host-ip:tomcat-port/web-Path/index.html
-
http://
web服务的HTTP传输协议 -
host-ip
服务器所在计算机的IP -
tomcat-port
Tomcat服务器占用的网络端口(默认8080,可以在conf/server.xml
中修改端口),如果tomcat端口为80,则可以省略 -
web-Path
Tomcat中部署的web项目的访问路径-
如果是直接将项目拷贝到 tomcat的 webapps中,并且没有进行其他配置,这个路径就是项目文件夹名称
-
如果是通过在
Catalina\localhost
配置xml文件的形式部署项目,则项目的访问路径就是Context标签path属性的值
-
示例:
1.9 Tomcat服务器请求响应流程
1.10 Tomcat端口配置
Tomcat默认端口是8080,我们可以通过修改
conf/server.xml
配置文件,修改服务器端口;
端口可以是1~65535中的数字,但是2000以下的很多端口被系统服务绑定,建议不要使用;
如果将服务器端口修改为80,则浏览器访问的时候无需添加端口号
http://192.168.155.1/wolf/index.html
<!--conf/server.xml 69行--> <Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
二、HTTP协议
web服务器可以接受浏览器的请求,并将服务器中的web项目资源响应给浏览器,浏览器与服务器之间进行网络通信遵循HTTP协议。
2.1 什么是HTTP协议?
超文本传输协议(HTTP,HyperText Transfer Protocol) ( 浏览器----> web服务器)
网络中的通信协议:
-
TCP协议,基于连接的安全传输协议 (客户端和服务器先建立连接,再通过连接发送数据)
-
UDP协议,基于广播/分发的非安全传输协议 (不会建立网络连接)
HTTP超文本传输协议是运行于TCP协议的基础之上、基于请求与响应模式、无状态的应用层协议,是互联网中应用最为广泛的一种网络协议。
2.2 HTTP协议特点
-
基于连接通信:当浏览器与服务器进行通信时,会首先建立网络连接,通过网络连接进行通信
-
短连接:在HTTP1.0中,浏览器向服务器发送请求,建立连接,但是这个连接只作用于浏览器和服务器的一次请求响应,这次请求响应完成之后则断开连接。
-
长连接:在HTTP1.1中,浏览器请求与服务器建立连接、进行请求和响应之后,会等待几秒钟,在这几秒内如果浏览器有新的请求,则直接使用之前的这个连接进行请求和数据响应,如果过了几秒钟没有新的请求,则将连接断开。
-
-
请求与响应模式:首先由浏览器向服务器发送请求,服务器再对请求进行响应,如果没有浏览器的请求服务器是不会主动向浏览器进行响应的。
-
无状态:服务器不会感知同一个客户端的多次请求(就是当服务器接收到客户端请求之后,不能识别这个客户端是否请求我)
-
简单灵活:实现简便、可以传输不同类型的数据(客户端----文件\文本---->服务器)
2.3 HTTP协议通信规则
通信协议:客户端与服务器之间共同遵守的规则
HTTP协议是就请求和响应模式,浏览器向服务器发送请求时,需要准守
HTTP请求规则
,服务器对浏览器进行响应时也遵守HTTP响应规则
。
2.3.1 http请求规则
说明:当在浏览器中发送请求时,浏览器已经实现了HTTP请求协议,基于这个协议发送请求的。
通过浏览器查看步骤(这种方式没法查看到所有的HTTP请求内容):
打开浏览器
F12打开调试窗口、点击 network
在浏览器输入网址进行访问
在network窗口中查看请求头信息(Request Headers)
通过自定义的HTTP服务器,接收浏览器请求,查看HTTP请求规则:
-
自定义HTTP服务器
package com.qfedu.http.request; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /** * @Description * @Author 千锋涛哥 * 公众号: Java架构栈 */ public class MyHttpServer { public static void main(String[] args) throws IOException { //创建一个网络服务器(可以通过浏览器请求这个服务) ServerSocket serverSocket = new ServerSocket(9999); //当浏览器请求我这个服务器之后,就建立网络连接(socket对象) Socket socket = serverSocket.accept(); //通过socket对象的输入流, InputStream inputStream = socket.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); //读取 浏览器发送的 HTTP请求 String content = null; while( (content = reader.readLine())!=null){ System.out.println(content); } //暂时未浏览器请求进行响应 } }
-
HTTP请求内容:
2.3.2 http响应规则
自定义“浏览器”查看HTTP响应规则
-
自定义“浏览器”
package com.qfedu.http.response; import jdk.internal.util.xml.impl.Input; import java.io.*; import java.net.Socket; /** * @Description * @Author 千锋涛哥 * 公众号: Java架构栈 */ public class MyBrowser { public static void main(String[] args) throws IOException { //将上一个案例中浏览器发送给 MyHttpServer的 http请求内容,发送给百度 // 1.向百度服务器发送链接请求 Socket socket = new Socket("www.baidu.com", 80); // 2.通过链接中的输出流,将HTTP请求内容发送给百度 OutputStream outputStream = socket.getOutputStream(); PrintWriter out = new PrintWriter(outputStream); out.println("GET /s HTTP/1.1"); out.println("Host: www.baidu.com:80"); out.println("Connection: keep-alive"); out.println("sec-ch-ua: \"Google Chrome\";v=\"95\", \"Chromium\";v=\"95\", \";Not A Brand\";v=\"99\""); out.println("sec-ch-ua-mobile: ?0"); out.println("sec-ch-ua-platform: \"Windows\""); out.println("Upgrade-Insecure-Requests: 1"); out.println("User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"); out.println("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"); out.println("Sec-Fetch-Site: none"); out.println("Sec-Fetch-Mode: navigate"); out.println("Sec-Fetch-User: ?1"); out.println("Sec-Fetch-Dest: document"); out.println("Accept-Encoding: gzip, deflate, br"); out.println("Accept-Language: zh-CN,zh;q=0.9"); out.println(""); out.flush(); //3.通过输入流接受百度的响应数据(HTTP响应规则) BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String responseContent = null; while( (responseContent = reader.readLine())!=null){ System.out.println(responseContent); } } }
-
HTTP响应内容
-
使用自定义HTTP服务器响应浏览器请求(遵循HTTP响应规则)
package com.qfedu.http.request; import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * @Description * @Author 千锋涛哥 * 公众号: Java架构栈 */ public class MyHttpServer { public static void main(String[] args) throws IOException { //创建一个网络服务器(可以通过浏览器请求这个服务) ServerSocket serverSocket = new ServerSocket(9999); //当浏览器请求我这个服务器之后,就建立网络连接(socket对象) Socket socket = serverSocket.accept(); //通过socket对象的输入流, InputStream inputStream = socket.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); //读取 浏览器发送的 HTTP请求 String content = null; while( !"" .equals(content = reader.readLine())){ System.out.println(content); } // 自定义HTTP服务器响应浏览器 PrintWriter out = new PrintWriter(socket.getOutputStream()); out.println("HTTP/1.1 200 OK"); out.println("Connection: keep-alive"); out.println("Content-Length: 154"); out.println("Content-Type: text/html;charset=utf-8"); out.println("Date: Sun, 07 Nov 2021 17:52:42 GMT"); out.println("Location: https://www.baidu.com/"); out.println("P3p: CP=\" OTI DSP COR IVA OUR IND COM \""); out.println("P3p: CP=\" OTI DSP COR IVA OUR IND COM \""); out.println("Server: BWS/1.1"); out.println("X-Frame-Options: sameorigin"); out.println("X-Ua-Compatible: IE=Edge,chrome=1"); out.println(""); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<title>这是自定义服务器响应的页面</title>"); out.println("</head>"); out.println("<body>"); out.println("<label style='color:red'>这是自定义服务器响应的内容</label>"); out.println("</body>"); out.println("</html>"); out.flush(); out.close(); } }
2.4 HTTP响应状态码
当浏览器向服务器发送请求、服务器对浏览器进行响应时,会响应给浏览器一个状态码,不同的状态码表示服务器对请求的不同处理。
2.4.1 响应状态码分类
-
1xx 表示浏览器请求服务器,服务器未做任何操作
-
2xx 表示服务器正常响应,并且响应成功
-
3xx 表示服务器只对浏览器的请求进行了部分处理,通知浏览器进行下一步操作
-
4xx 表示浏览器端(客户端)错误:404、401
-
5xx 表示服务器端资源错误
2.4.2 常见状态码
-
200 表示响应成功
-
302 表示服务器临时重定向
-
304 表示服务器资源没有变化
-
404 访问的资源不存在
-
500 访问的服务器端资源错误
三、Servlet基础使用
3.1 Servlet简介
问题:浏览器可以通过http协议请求web服务器,访问web服务器上的web资源,web资源又分为静态资源和动态资源,静态资源可以直接存储在web服务器上供浏览器访问,动态资源该如何访问呢 ?
Servlet是服务器端的Java程序、能够接收HTTP请求、处理HTTP请求、并对HTTP请求进行响应的
动态网页技术。
Servlet是JavaEE(JavaWeb)规范的一个重要组成部分。
Servlet的作用:
-
接收客户端的HTTP请求(浏览器)
-
根据用户请求进行数据处理
-
动态生成网页(网页中的数据是根据客户端请求动态改变的)
-
将生成的包含动态数据的网页响应给客户端
3.2 创建Java web工程
Servlet是JavaEE规范的一部分,Servlet的开发需要依赖JavaEE环境,之前创建的单纯的Java应用已经不能满足Servlet开发所需的环境需求,我们要创建Java web工程。
Java工程 : 只引入了JDK的标准库(JavaSE)
Java web工程:引入了Java企业级开发环境(JavaEE)
-
File--->New--->Project...
-
选择
Java Enterprise
-
如何配置Tomcat ?
-
点击
New
按钮,选择Tomcat Server
-
配置Tomcat服务器
-
-
选择 web application项目框架及版本
-
输入项目名称
-
Java web工程目录
3.3 创建Servlet类
Servlet是一个Java程序,是一个能够接收HTTP请求的Java类,因此需要实现HTTP协议。
在JavaEE库中有一个类
javax.servlet.http.HttpServlet
实现了HTTP协议,我们创建的类只要继承这个HttpServlet
类,就实现了HTTP协议,就能够接受HTTP请求。
-
创建一个类继承
javax.servlet.http.HttpServlet
; -
继承HttpServlet的类就能够接收HTTP请求,我们把这样的类称之为Servlet类,类以
***Servlet
格式命名; -
在我们创建的Servlet类中,重写
doPost
/doGet
用于处理用户不同的请求
3.4 配置Servlet类的URL
Servlet创建完成之后,需要配置url访问路径,然后将web项目运行在Tomcat之上,就能够通过配置的url访问Servlet类。Servlet自3.0规范开始支持两种配置方式:
基于web.xml配置文件进行配置
基于注解配置
3.4.1 基于web.xml配置Servlet
-
打开Java web工程中
web/WEB-INF/web.xml
文件 -
配置如下:
<!-- 配置BookListServlet类的访问路径 --> <!-- servlet标签: 配置类路径 --> <servlet> <!-- 配置与servlet-mapping标签的匹配表示,理论上可以是任意字符串, 只要多个servlet标签的servlet-name不重复就可以,实际开发中建议使用当前Servlet类 --> <servlet-name>BookListServlet</servlet-name> <!--Servlet类路径--> <servlet-class>com.qfedu.test1.BookListServlet</servlet-class> </servlet> <!-- servlet-mapping标签:配置url--> <servlet-mapping> <!--servlet-mapping标签的servlet-name属性与对应的 servlet标签的servlet-name属性一致--> <servlet-name>BookListServlet</servlet-name> <!-- url-pattern配置Servlet的访问路径,必须以 / 开头 --> <url-pattern>/book-list</url-pattern> </servlet-mapping>
3.4.2 基于注解配置Servlet
-
在创建的Servlet类上添加
@WebServlet
注解,在注解后的参数中配置url,url也必须以/
开头
/** * @Description 根据图书ID查询一个图书信息 * @Author 千锋涛哥 * 公众号: Java架构栈 */ @WebServlet("/book-query") public class BookQueryServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("BookQueryServlet--------doPost"); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("BookQueryServlet--------doGet"); } }
3.5 IDEA部署web项目
通过IDEA,将java web项目部署到Tomcat中并运行Tomcat。
3.5.1 配置服务器
-
File---Settings
3.5.2 部署web项目
将web项目部署到Tomcat
-
点击IDEA右上角:
-
添加Tomcat服务器
-
部署项目
-
配置web项目的访问路径
3.5.3 启动Tomcat
如果在创建web项目时选择了web服务器,则直接点击IDEA右上角运行即可
:
-
播放按钮:直接运行
-
甲壳虫:debug运行
3.5.4 Tomcat启动完成工作
-
构建web项目
-
将web项目拷贝到Tomcat
-
启动Tomcat
-
打开浏览器,访问当前项目首页
3.6 浏览器访问测试
通过浏览器发送HTTP请求,访问tomcat中web项目中的Servlet类
3.7 GET请求与POST请求
Servlet类中的doGet和doPost是用来处理浏览器不同请求方式的HTTP请求,在HTTP协议中,HTTP请求有多种请求方式(get\post\delete\put\option等),不同请求方式传递的数据是不同的。
Servlet中的doGet方法用于处理客户端的get方式请求
Servlet中的doPost方法用于处理客户端的post方式请求
3.7.1 GET请求
-
使用Get方式请求服务器,传递的参数会拼接到URL后面,以
?
分隔url和参数,多个参数以&
符号分开;<form action="https://www.baidu.com" method="get"> <input type="text" name="id"/><br/> <input type="text" name="name"/><br/> <input type="submit" value="get提交"/><br/> </form>
-
get方式是明文提交、提交的数据量小、安全性较差;
-
get方式传输效率较高,浏览器地址栏输入网址默认采用get方式提交
-
get方式提交数据的场景:
-
浏览器地址栏
-
网页中的超链接
<a href="http://localhost:80/demo1/book-query">请求BookQueryServlet</a>
-
form表单 method="get"提交
-
3.7.2 POST请求
-
使用POST方式请求服务器,参数是通过请求正文进行传递的(request body);
<form action="http://localhost:9999" method="post"> <input type="text" name="id"/><br/> <input type="text" name="name"/><br/> <input type="submit" value="POST提交"/><br/> </form>
-
POST使用密文传递数据,数据量大,安全性高;
-
POST以牺牲传输效率为代价保障了安全性,因此性能较get低。
-
POST提交数据的场景:form表单 method="post"提交
3.8 Servlet响应动态网页
案例:根据客户端请求的bookId,动态查询图书信息,生成HTML文档,响应给客户端
流程图 |
---|
![]() |
实现代码:
package com.qfedu.test1.dto; /** * @Description 图书实体类 * @Author 千锋涛哥 * 公众号: Java架构栈 */ public class Book { private String bookId; private String bookName; private String bookAuthor; private double bookPrice; private String bookImgPath; //无参构造器 //全参构造器 //get 和 set方法 }
package com.qfedu.test1; import com.qfedu.test1.dto.Book; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; /** * @Description 根据图书ID查询一个图书信息 * @Author 千锋涛哥 * 公众号: Java架构栈 */ @WebServlet("/book-query") public class BookQueryServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("BookQueryServlet--------doPost"); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("BookQueryServlet--------doGet"); //动态生成HTML // 1.接收到浏览器请求时传递的图书ID (bookId) String bid = request.getParameter("bookId"); // 2.根据bid查询数据库图书表(伪代码) Map<String, Book> bookMap = new HashMap<>(); bookMap.put("1001",new Book("1001","Java","张三",55.66,"")); bookMap.put("1002",new Book("1002","C++","李四",33.44,"")); bookMap.put("1003",new Book("1003","Python","王五",44.55,"")); // book就是根据用户请求查询到的动态数据 Book book = bookMap.get(bid); // 3.将查询到图书信息生成网页,将网页响应给浏览器:通过IO流(输出流)向浏览器响应一个网页数据 // 这个out对象,就是用于响应浏览器的输出流,通过这个输出流写出什么数据,浏览器就可以接受到什么数据 // a.设置响应头 response.setCharacterEncoding("utf-8"); response.setContentType("text/html"); // b.通过输出流响应网页数据 PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head><meta charset='utf-8'><title>这是Servlet响应的网页</title></head>"); out.println("<body>"); out.println("<table style='border:1px deepskublue solid; width:1000px;' align='center' border='1' cellspacing='0'>"); out.println("<tr><th>图书编号</th><th>图书名称</th><th>图书作者</th><th>图书价格</th><th>图书封面</th></tr>"); out.println("<tr>"); out.println("<td>"+book.getBookId()+"</td>"); out.println("<td>"+book.getBookName()+"</td>"); out.println("<td>"+book.getBookAuthor()+"</td>"); out.println("<td>"+book.getBookPrice()+"</td>"); out.println("<td>"+book.getBookImgPath()+"</td>"); out.println("</tr>"); out.println("</table>"); out.println("</body>"); out.println("</html>"); out.flush(); out.close(); } }
四、Servlet原理解析
4.1 ServletAPI核心类与接口
4.2 Servlet类处理请求的流程
准备工作:
-
新建一个Javaweb工程:servlet-demo2
-
新建一个Servlet类:TestServlet
创建servlet类的步骤: 1.创建一个命名为 **Servlet 2.继承javax.servlet.http.HttpServlet类 3.重写doGet和doPost方法 4.配置当前Servlet类的访问路径
<servlet> <servlet-name>TestServlet</servlet-name> <servlet-class>com.qfedu.servlets.TestServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>TestServlet</servlet-name> <url-pattern>/test</url-pattern> </servlet-mapping>
-
为了测试Servlet处理请求的流程,我们重写了从HttpServlet继承的多个方法:
-
init
-
service(ServletRequest,ServletResponse)
-
service(HttpServletRequest,HttpServletResponse)
-
doGet
-
doPost
-
destroy
package com.qfedu.servlets; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @Description * @Author 千锋涛哥 * 公众号: Java架构栈 * * 创建servlet类的步骤: * 1.创建一个命名为 **Servlet * 2.继承javax.servlet.http.HttpServlet类 * 3.重写doGet和doPost方法 * 4.配置当前Servlet类的访问路径 */ public class TestServlet extends HttpServlet { @Override public void init(ServletConfig config) throws ServletException { System.out.println("------------init"); super.init(config); } @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { System.out.println("------------Servlet接口定义的service"); super.service(req, res); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("------------HttpServlet类定义的service"); super.service(req, resp); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("------------doGet"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("------------doPost"); } @Override public void destroy() { System.out.println("------------destroy"); super.destroy(); } }
-
Servlet请求处理流程 |
---|
![]() |
4.3 Servlet实例的生命周期[重点]
当客户端的请求到达Tomcat,Tomcat会创建一个线程来接收、处理、响应客户端请求,客户端在请求某个Servlet类时,线程需要通过这个Servlet类的实例来调用service方法、调用doGet/doPost..方法来处理响应请求,这个Servlet类的实例是何时创建、何时销毁的呢?
Servlet实例的生命周期指的是一个Servlet类的实例从创建到销毁的过程。
-
Servlet类是单实例多线程的,一个Servlet类自始至终只会创建一个对象;
-
如果当前Servlet类没有配置
<load-on-startup>1</load-on-startup>
:当客户端第一次请求Servlet时,创建当前Servlet类的实例,然后使用这个实例调用service(ServletRequest, ServletResponse)方法——service(HttpServletRequest, HttpServletResponse)方法——doGet/doPost处理客户端请求;当客户端请求再次到达时将不会重新创建Servlet实例,直接使用第一次创建的实例调用方法进行响应;
-
如果当前Servlet类配置了
<load-on-startup>1</load-on-startup>
:当服务器启动时就会创建Servlet类的实例,无论客户端第一次请求这个Servlet类,还是再次请求都不会创建Servlet类实例,直接使用服务器启动时创建的Servlet实例来接收、处理、响应客户端请求;
-
当服务器关闭时,Serlvet类的实例会被销毁。
配置load-on-startup的两种方式:
-
xml配置
<servlet> <servlet-name>TestServlet</servlet-name> <servlet-class>com.qfedu.servlets.TestServlet</servlet-class> <!--如果有多个Servlet都配置了load-on-startup,里面的数字就是在服务器中创建实例的顺序--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>TestServlet</servlet-name> <url-pattern>/test</url-pattern> </servlet-mapping>
-
注解配置:
@WebServlet(value = "/test",loadOnStartup = 1)
4.4 线程安全问题
因为Servlet实例是单例模式,当多个客户端并发访问同一个Servlet类时,Tomcat会创建多个线程,多个线程会使用同一个Servlet实例,有可能会导致线程安全问题,如何保证线程安全呢?
4.4.1 方案1:实现SingleThreadModel接口
我们可以让Servlet类实现SingleThreadModel接口,每个线程都会创建servlet实例,避免了多线程使用通过Servlet实例的请求,但是使用这种方式会导致对客户端的请求响应效率变低,增加了服务器因频繁创建和销毁Servlet实例的开销,因此此种方式不建议使用,已经过时。
4.4.2 方案2:使用synchronized同步锁
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) { synchronized (this) { // 业务代码 } }
4.4.3 建议:在Servlet实例中尽量不使用成员变量
如果将变量定义为成员变量,则这个变量在多个线程中是共享的,就有可能因为多个线程同时修改这个变量导致并发问题,因此我们可以将变量定义在处理业务的doXX方法中,定义为局部变量之后,每个线程都有属于自己的局部变量。
五、Servlet开发技术
Servlet动态网页技术,为客户端请求提供动态响应:
Servlet如何接收客户端请求
Servlet如何为客户端提供响应
5.1 HttpServletRequest对象
我们在Servlet类中的doGet/doPost/doXX 方法中通过 request 对象接收客户端请求信息.
客户端向服务器发送的请求信息都会被封装到request对象,request(HttpServletRequest类)提供了多个方法可以用于获取http请求中的数据。
5.1.1 接收请求数据
5.1.2 request对象常用方法
接收请求行数据
//getMethod:获取客户端请求方式 String method = request.getMethod(); //getRequestURL: 获取客户端的请求URL(不包含url上的参数) String url = request.getRequestURL().toString(); //getParameter : 获取请求行中url参数,根据参数的key获取参数的value // 如果客户端是通过输入框提交数据,则参数要和输入框的name属性值一致 String p1 = request.getParameter("k1"); //getProtocol :获取客户端提交数据的协议及版本 String protocol = request.getProtocol();
接收请求头数据
//getHeaderNames :获取请求头中所有的key Enumeration<String> en = request.getHeaderNames(); while(en.hasMoreElements()){ String key = en.nextElement(); //getHeader : 根据请求头中的key获取对应value String value = request.getHeader(key); System.out.println(key+":"+value); }
接收请求正文数据
// 获取请求正文 // getInputStream :获取客户端请求的输入流 ServletInputStream inputStream = request.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); // 从输入流中读取请求正文 String s = reader.readLine(); System.out.println(s);
5.1.3 request对象接收表单数据案例
图书添加操作:需要从图书添加页面提交数据到Servlet
book-add.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>提交图书</title> </head> <body> <form action="http://localhost/demo3/BookAddServlet" method="get"> <p>图书编号:<input type="text" name="bookId"/></p> <p>图书名称:<input type="text" name="bookName"/></p> <p>图书作者:<input type="text" name="bookAuthor"/></p> <p>图书价格:<input type="text" name="bookPrice"/></p> <p><input type="submit" value="Get提交"/></p> </form> <hr/> <form action="http://localhost/demo3/BookAddServlet" method="post"> <p>图书编号:<input type="text" name="bookId"/></p> <p>图书名称:<input type="text" name="bookName"/></p> <p>图书作者:<input type="text" name="bookAuthor"/></p> <p>图书价格:<input type="text" name="bookPrice"/></p> <p><input type="submit" value="Post提交"/></p> </form> </body> </html>
BookAddServlet
package com.qfedu.servlets; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @Description 添加图书 * @Author 千锋涛哥 * 公众号: Java架构栈 */ @WebServlet("/BookAddServlet") public class BookAddServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //getParameter 方法参数字符串 必须要和 form表单中输入框的name属性一致 // 此方法获取的客户端提交的数据一律为String类型,根据需要可以进行类型转换 int id = Integer.parseInt(request.getParameter("bookId"));//5 String name = request.getParameter("bookName"); String author = request.getParameter("bookAuthor"); double price = Double.parseDouble(request.getParameter("bookPrice")); System.out.println("图书编号:"+id); System.out.println("图书名称:"+name); System.out.println("图书作者:"+author); System.out.println("图书价格:"+price); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 如果页面采用form表单post方式提交数据,数据是通过 请求正文 传递的 // 1.我们可以通过请求正文获取数据 //ServletInputStream inputStream = request.getInputStream(); //BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); //String s = reader.readLine(); //System.out.println("请求正文数据:"+s); // 2.form表单post方式提交的数据,也可以通过request对象的getParameter方法接收, // 之前是不能打开request输入流 int id = Integer.parseInt(request.getParameter("bookId"));//5 String name = request.getParameter("bookName"); String author = request.getParameter("bookAuthor"); double price = Double.parseDouble(request.getParameter("bookPrice")); System.out.println("图书编号:"+id); System.out.println("图书名称:"+name); System.out.println("图书作者:"+author); System.out.println("图书价格:"+price); } }
5.1.4 request对象处理中文乱码问题
客户端向服务器的Servlet类提交数据中包含中文,可能会出现中文乱码问题
1. 为什么会产生乱码问题?
-
客户端提交的数据通过网络发送到服务器,传输的过程数据数据通常会进行编码,服务器会对数据进行解码;如果服务器使用的解码方式与网页的原始编码不一致,将会导致服务器的解码出现乱码
2.get方式提交数据的乱码问题
-
get方式提交的数据会拼接在请求行的URL后面进行传递,不同的浏览器处理方式是不一样的,有的浏览器会进行编码,有的浏览器则直接提交;
-
数据到达服务器之后,服务器会根据参数的编码方式对参数进行解码,如果没有编码则服务器直接接受,如果进行了服务器能够解析的编码,服务也会进行转换。
-
结论:GET方式提交的请求行参数,是通过服务器进行处理的。
-
解决方案:在Tomcat的conf/server.xml中配置URL的编码方式
<Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="utf-8" />
3.post方式提交数据的乱码问题
-
post方式提交表单数据是通过请求正文进行传递的,会对数据进行编码;Tomcat会对URL传递的参数进行解码,但是不能对请求正文进行解码,因此需要我们在Servlet类中接收数据之前对客户端提交的请求数据进行编码设置:
request.setCharacterEncoding("utf-8");
-
页面表单提交数据-post方式提交:
<form action="http://localhost/demo3/BookAddServlet" method="post"> <p>图书编号:<input type="text" name="bookId"/></p> <p>图书名称:<input type="text" name="bookName"/></p> <p>图书作者:<input type="text" name="bookAuthor"/></p> <p>图书价格:<input type="text" name="bookPrice"/></p> <p><input type="submit" value="Post提交"/></p> </form>
-
Servlet类对post提交的数据进行编码设置:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //在servlet中接收数据之间,通过request对象设置http请求数据的编码方式 request.setCharacterEncoding("utf-8"); int id = Integer.parseInt(request.getParameter("bookId")); String name = request.getParameter("bookName"); String author = request.getParameter("bookAuthor"); double price = Double.parseDouble(request.getParameter("bookPrice")); }
5.2 HttpServletResponse对象
Servlet类中doGet/doPost/doXX等方法都有一个HttpServletResponse对象,用于响应客户端请求
5.2.1 response对象常用方法
//【设置响应状态行】 //setStatus : 设置状态行中的状态码 response.setStatus(200); //【设置响应头】 //setContentType: 设置响应头中的Content-Type属性,设置响应客户端的数据格式 response.setContentType("text/html"); //等价于: response.setHeader("Content-Type","text/html"); //setContentLength:设置响应客户端的数据长度(一般无需设置) response.setContentLength(1024); //等价于: //setHeader : 设置其他的响应头属性 response.setHeader("Connection","keep-alive"); //【设置响应正文】 //setCharacterEncoding:设置响应客户端的数据编码格式 response.setCharacterEncoding("utf-8"); // 通过response对象获取输出流出 // 字节流(如果要响应文件数据给客户端,则需要使用字节流) ServletOutputStream outputStream = response.getOutputStream(); // 字符流(如果响应文本数据-HTML文档,则使用字符流) PrintWriter out = response.getWriter();
5.2.2 查询成绩案例
package com.qfedu.servlets; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.Enumeration; /** * @Description 查询成绩 * @Author 千锋涛哥 * 公众号: Java架构栈 */ @WebServlet("/gradeQuery") public class GradeQueryServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("~~~~~~~~~~~~~~~doGet"); //1.接收学号和课程编号 String stuNum = request.getParameter("stuNum"); String curseId = request.getParameter("curseId"); //2.查询数据库 int grade = 95; //3.通过response响应浏览器 response.setStatus(200); response.setContentType("text/html"); response.setHeader("Connection","keep-alive"); //【设置响应正文】 //setCharacterEncoding:设置响应客户端的数据编码格式 response.setCharacterEncoding("utf-8"); // 通过response对象获取输出流出 PrintWriter out = response.getWriter(); //通过流写出的数据,就会以响应正文的形式传输给客户端浏览器,如果浏览器可以识别数据,则直接显示 out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<meta charset='utf-8'>"); out.println("<title>学生成绩查询结果</title>"); out.println("</head>"); out.println("<body>"); out.println("<div style='padding:50px; font-size:50px; text-align:center; border:1px gray solid; margin:auto'>"); out.println("<label>您的成绩为:</label>"); out.println("<label style='color:red'>"+grade+"</label>"); out.println("</div>"); out.println("</body>"); out.println("</html>"); out.flush(); out.close(); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取请求正文 // getInputStream :获取客户端请求的输入流 ServletInputStream inputStream = request.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); // 从输入流中读取请求正文 String s = reader.readLine(); System.out.println(s); } }
5.3 Servlet动态网页案例
学生信息管理系统:
学生登录
学生查询成绩
5.3.1 案例流程分析
5.3.2 项目环境搭建
-
数据库环境准备: 创建一个名为
db_sgms
,然后创建如下数据表:-- 学生信息表 create table students( stu_num char(5) primary key, stu_name varchar(20) not null, stu_gender char(2) not null, stu_age int not null, stu_pwd varchar(20) not null ); -- 课程信息表 create table courses( course_id char(6) primary key, course_name varchar(50) not null ); -- 学生成绩表 create table grades( gid int primary key auto_increment, snum char(5) not null, cid char(6) not null, score int not null );
-
创建一个Java web工程:
servlet-demo4
-
搭建JDBC环境
-
导入JDBC所需的驱动jar、数据库连接池jar、DBUtils.jar
-
在web/WEB-INF创建lib目录
-
将三个jar包拷贝到lib目录
-
选择拷贝到lib目录三个jar文件----右键----Add as library
-
-
配置连接池信息
-
在项目中创建
com.qfedu.sgms.utils
包 -
在包中创建druid.properties配置druid连接池信息
# 数据库连接信息 driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/db_sgms?characterEncoding=utf8 username=root password=@QFedu123 # 连接池属性 # 连接池的初始化连接数<创建数据库连接池时默认初始化的连接的个数> initialSize=10 # 连接池的最大连接数 maxActive=50 # 最小空闲连接数(当数据库连接使用率很低时,连接池中的连接会被释放一部分) minIdle=5 # 超时等待时间(单位:ms) maxWait=30000
-
在utils包中创建连接池工具类:DruidUtils
package com.qfedu.sgms.utils; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.*; import java.io.InputStream; import java.sql.*; import java.util.Properties; /** * @Description 数据库连接池工具类 * @Author 千锋涛哥 * 公众号: Java架构栈 */ public class DruidUtils { private static DruidDataSource druidDataSource; static{ try { InputStream is = DruidUtils.class.getResourceAsStream("druid.properties"); Properties properties = new Properties(); properties.load(is); druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } /** * 获取数据库连接池数据源对象 * @return */ public static DataSource getDataSource(){ return druidDataSource; } /** * 从数据库连接池中获取数据库连接对象 * @return */ public static Connection getConnection(){ Connection connection = null; try { connection = druidDataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } }
-
-
5.3.3 完成数据库操作
根据项目业务流程图,完成需要数据库操作
学生登录实现:
根据输入的学号和密码查询学生信息
,如果查询到了说明学号密码输入正确,登录成功;
根据输入的学号查询学生
,如果查询到了说明学号正确,再比较输入的密码和查询出来的密码是否一致,如果一致则登录成功;查询成绩:
根据学号和课程号从成绩表中查询成绩
根据学号和密码查询学生
-
创建实体类 Student类
package com.qfedu.sgms.dto; /** * @Description 学生信息实体类 * @Author 千锋涛哥 * 公众号: Java架构栈 */ public class Student { private String stuNum; private String stuName; private String stuGender; private int stuAge; private String stuPwd; }
-
创建DAO,完成对应数据库操作
package com.qfedu.sgms.dao; import com.qfedu.sgms.dto.Student; import com.qfedu.sgms.utils.DruidUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; /** * @Description * @Author 千锋涛哥 * 公众号: Java架构栈 */ public class StudentDAO { /** * 根据学号和密码查询学生信息 * @param stuNum * @param stuPwd * @return 如果学号密码正确返回Student,否则返回null */ public Student queryStudentByNumAndPwd(String stuNum,String stuPwd){ Student student = null; try{ String sql = "select stu_num stuNum,stu_name stuName,stu_gender stuGender,stu_age stuAge,stu_pwd stuPwd from students where stu_num=? and stu_pwd=?"; QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource()); student = queryRunner.query(sql,new BeanHandler<Student>(Student.class),stuNum,stuPwd); }catch (Exception e){ e.printStackTrace(); } return student; } }
根据学号、课程号查询成绩
-
创建实体类 Grade类
package com.qfedu.sgms.dto; /** * @Description * @Author 千锋涛哥 * 公众号: Java架构栈 */ public class Grade { private String snum; private String sname; //此属性在成绩表中没有,但是关联学生信息可以查询 private String cid; private String cname; //此属性在成绩表也没有,但是关联课程表可以查询 private int score; }
-
创建GradeDAO查询成绩
package com.qfedu.sgms.dao; import com.qfedu.sgms.dto.Grade; import com.qfedu.sgms.utils.DruidUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; /** * @Description * @Author 千锋涛哥 * 公众号: Java架构栈 */ public class GradeDAO { /** * 根据学号和课程号查询这个学生这门课的成绩 * @param snum * @param cid * @return */ public Grade queryGradeBySnumAndCid(String snum,String cid){ Grade grade = null; try{ //注意查询出的列名要取别名,和grade对象的属性一致 String sql = "select s.stu_num snum,s.stu_name sname,c.course_id cid,c.course_name cname ,g.score score from students s INNER JOIN grades g INNER JOIN courses c on s.stu_num = g.snum and g.cid = c.course_id where s.stu_num=? and c.course_id=?"; QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource()); grade = queryRunner.query(sql,new BeanHandler<Grade>(Grade.class),snum,cid); }catch (Exception e){ e.printStackTrace(); } return grade; } }
5.3.4 实现LoginPageServlet登录界面
LoginPageServlet就是为用户提供一个登陆页面
package com.qfedu.sgms.servlets; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @Description * @Author 千锋涛哥 * 公众号: Java架构栈 */ @WebServlet("/login") public class LoginPageServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //响应一个登录界面 response.setStatus(200); response.setContentType("text/html;charset=utf-8"); response.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<meta charset='utf-8'>"); out.println("<title>学生成绩查询系统-登录</title>"); out.println("</head>"); out.println("<body>"); out.println("<form action='' method='post'>"); out.println("<h3>学生成绩查询系统—学生登录</h3>"); out.println("<p>学号:<input type='text' name='stuNum' placeholder='学生学号'/></p>"); out.println("<p>密码:<input type='password' name='stuPwd' placeholder='登录密码'/></p>"); out.println("<p><input type='submit' value='登录'/></p>"); out.println("</form>"); out.println("</body>"); out.println("</html>"); out.flush(); out.close(); } }
5.3.5 实现CheckServlet验证登录
CheckServlet接收用户在页面中输入学号和密码、调用StudentDAO查询验证
-
创建CheckServlet类
-
修改
LoginPageServlet
中的 form表单action属性为CheckServlet的访问路径// 点击登录之后跳转到另一个CheckServlet,在一个项目中action的值可以只写 CheckServlet的url out.println("<form action='CheckServlet' method='post'>");
-
在CheckServlet类验证客户端提交的学号和密码
package com.qfedu.sgms.servlets; import com.qfedu.sgms.dao.StudentDAO; import com.qfedu.sgms.dto.Student; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @Description 验证学生登录 * @Author 千锋涛哥 * 公众号: Java架构栈 */ @WebServlet("/CheckServlet") public class CheckServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 接收学号和密码 request.setCharacterEncoding("utf-8"); String num = request.getParameter("stuNum"); String pwd = request.getParameter("stuPwd"); //2.调用StudentDAO中的方法,根据学号和密码查询学生 StudentDAO studentDAO = new StudentDAO(); Student student = studentDAO.queryStudentByNumAndPwd(num, pwd); //3.判断查询结果,响应客户端 response.setStatus(200); response.setContentType("text/html;charset=utf-8"); response.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<meta charset='utf-8'>"); //根据登录验证的不同结果响应给客户端不同的页面 if(student == null){ //登录失败:响应客户端登录页面 out.println("<title>学生成绩查询系统-登录</title>"); out.println("</head>"); out.println("<body>"); //点击登录之后跳转到另一个CheckServlet,在一个项目中action的值可以只写CheckServlet的url out.println("<form action='CheckServlet' method='post'>"); out.println("<h3>学生成绩查询系统—学生登录</h3>"); out.println("<p>学号:<input type='text' name='stuNum' placeholder='学生学号'/></p>"); out.println("<p>密码:<input type='password' name='stuPwd' placeholder='登录密码'/></p>"); out.println("<p><input type='submit' value='登录'/></p>"); out.println("</form>"); }else{ //登录成功:响应客户端系统的主页 out.println("<title>学生成绩查询系统-主页面</title>"); out.println("</head>"); out.println("<body>"); out.println("登录成功!"); } out.println("</body>"); out.println("</html>"); out.flush(); out.close(); } }
5.3.6 业务与视图分离
通过上个步骤CheckServlet的实现,我们发现一些问题:
如果登录验证失败,需要响应登录页面,登录界面已经在LoginPageServlet中写过,在CheckServlet又写了一遍,不满足我们代码复用性原则,不便于代码维护;
在CheckServlet中,业务代码与视图代码混在一起,不满足单一原则的编程规范
业务与视图分离:
-
CheckServlet只负责学生登录校验的业务处理;
-
如果登录成功需要呈现主界面,可以创建单独的IndexPageServlet来响应主页面;
-
如果登录失败需要呈现登录界面,可以重复使用LoginPageServlet来响应登录页面;
5.3.7 实现IndexPageServlet主页面
IndexPageServlet显示成绩查询的主页面,并且能够输入学号和课程号查询成绩
package com.qfedu.sgms.servlets; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @Description 提供登录成功之后的主页面 * @Author 千锋涛哥 * 公众号: Java架构栈 */ @WebServlet("/IndexPageServlet") public class IndexPageServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //响应一个登录界面 response.setStatus(200); response.setContentType("text/html;charset=utf-8"); response.setCharacterEncoding("utf-8"); PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<meta charset='utf-8'>"); out.println("<title>学生成绩查询系统-主页面</title>"); out.println("</head>"); out.println("<body>"); out.println("<table border='1' width='100%' height='700'>"); out.println("<tr height='100'><td colspan='2'><label>学生成绩查询系统欢迎您!</label></td></tr>"); out.println("<tr>"); out.println("<td width='200'>2-1</td>"); out.println("<td align='center' valign='top'>"); //查询成绩的表单 out.println("<form action='GradeQueryServlet' method='post'>"); out.println("<h3>查询成绩</h3>"); out.println("<p>学号:<input type='text' name='stuNum' placeholder='学生学号'/></p>"); out.println("<p>课程:<input type='text' name='courseId' placeholder='课程编号'/></p>"); out.println("<p><input type='submit' value='查询'/></p>"); out.println("</form>"); out.println("</td>"); out.println("</tr>"); out.println("</table>"); out.println("</body>"); out.println("</html>"); out.flush(); out.close(); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
5.3.8 登录失败转发到LoginPageServlet
-
CheckServlet转发到LoginPageServlet,并传递参数
//3.判断查询结果,响应客户端 //根据登录验证的不同结果响应给客户端不同的页面 if(student == null){ //登录失败:响应客户端登录页面,提示“登录失败,学号或密码错误!” //转发到下一个Servlet是可以通过request传递数据过去的 request.setAttribute("tips","登录失败,学号或密码错误!"); //转发到LoginPageServlet:在当前Servlet类的doPost方法转到,也会转发到下一个Servelt的doPost request.getRequestDispatcher("login").forward(request,response); }else{ //登录成功:响应客户端系统的主页 }
-
LoginPageServlet接收参数响应登录页面
@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("-----doPost"); //1. doPost调用doGet doGet(request,response); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("-----doGet"); //2.接收从CheckServlet传递的提示信息 String tips = (String) request.getAttribute("tips"); //响应一个登录界面 //...代码省略 //显示提示信息 if(tips != null){ out.println("<label style='color:red'>"+tips+"</label>"); } //...代码省略 }
5.3.9 登录成功重定向IndexPageServlet
-
CheckServlet重定向IndexPageServlet
//3.判断查询结果,响应客户端 //根据登录验证的不同结果响应给客户端不同的页面 if(student == null){ //登录失败:响应客户端登录页面,提示“登录失败,学号或密码错误!” //转发到下一个Servlet是可以通过request传递数据过去的 request.setAttribute("tips","登录失败,学号或密码错误!"); //转发到LoginPageServlet:在当前Servlet类的doPost方法转到,也会转发到下一个Servelt的doPost request.getRequestDispatcher("login").forward(request,response); }else{ //登录成功:响应客户端系统的主页 //重定向到IndexPageServlet:无需传递参数到IndexPageServlet,所以我们可以使用重定向 response.sendRedirect("IndexPageServlet"); }
5.3.10 实现GradeQueryServlet查询成绩
GradeQueryServlet接收客户端输入的学号、课程ID,查询成绩
-
创建GradeQueryServlet
package com.qfedu.sgms.servlets; import com.qfedu.sgms.dao.GradeDAO; import com.qfedu.sgms.dto.Grade; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @Description 根据客户端传递的学号、课程号查询成绩 * @Author 千锋涛哥 * 公众号: Java架构栈 */ @WebServlet("/GradeQueryServlet") public class GradeQueryServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //如果允许客户端get和post方式提交,我们可以在doGet中调用doPost,业务代码写在doPost doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.接收客户端输入的学号、课程ID String snum = request.getParameter("stuNum"); String cid = request.getParameter("courseId"); //2.调用GradeDAO中的方法根据学号、课程号查询成绩 GradeDAO gradeDAO = new GradeDAO(); Grade grade = gradeDAO.queryGradeBySnumAndCid(snum, cid); //3.查询到成绩之后,将成绩传递到GradePageServlet // 由GradePageServlet响应给浏览器一个页面,并把成绩显示出来 request.setAttribute("grage",grade); request.getRequestDispatcher("GradePageServlet").forward(request,response); } }
-
修改IndexPageServlet响应的主页面,form标签的action属性为
GradeQueryServlet
-
直接修改到 5.3.7 的代码
-
5.3.11 实现GradePageServlet显示成绩
GradePageServle响应给客户端一个成绩结果页面,并显示成绩
package com.qfedu.sgms.servlets; import com.qfedu.sgms.dto.Grade; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @Description 成绩查询结果页面 * @Author 千锋涛哥 * 公众号: Java架构栈 */ @WebServlet("/GradePageServlet") public class GradePageServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1.接收转发时传递的成绩 Grade grade = (Grade) request.getAttribute("grade"); //2.设置响应头 response.setStatus(200); response.setContentType("text/html;charset=utf-8"); response.setCharacterEncoding("utf-8"); //3.响应成绩结果页面 PrintWriter out = response.getWriter(); out.println("<!DOCTYPE html>"); out.println("<html>"); out.println("<head>"); out.println("<meta charset='utf-8'>"); out.println("<title>学生成绩查询系统</title>"); out.println("</head>"); out.println("<body>"); out.println("<table border='1' width='100%' height='700'>"); out.println("<tr height='100'><td colspan='2'><label>学生成绩查询系统欢迎您!</label></td></tr>"); out.println("<tr>"); out.println("<td width='200'>2-1</td>"); out.println("<td align='center' valign='top'>"); //查询成绩的表单 : 修改action属性为 GradeQueryServlet out.println("<h3>查询结果</h3>"); if(grade != null){ out.println("<table width='200' border='1' cellspacing='0'>"); out.println("<tr><td>学号</td><td>"+grade.getSnum()+"</td></tr>"); out.println("<tr><td>姓名</td><td>"+grade.getSname()+"</td></tr>"); out.println("<tr><td>课程ID</td><td>"+grade.getCid()+"</td></tr>"); out.println("<tr><td>课程名</td><td>"+grade.getCname()+"</td></tr>"); out.println("<tr><td>成绩</td><td><label style='color:red;font-weight:bold'>"+grade.getScore()+"</label></td></tr>"); out.println("</table>"); }else{ out.println("<label style='color:red;font-weight:bold;font-size:20px'>学号或课程号有误!</label>"); } out.println("<a href='IndexPageServlet'>继续查询</a>"); out.println("</td>"); out.println("</tr>"); out.println("</table>"); out.println("</body>"); out.println("</html>"); out.flush(); out.close(); } }
5.4 转发和重定向
当客户端请求到了某个Servlet类之后,Servlet类进行处理,但是并不使用这个Servlet来响应客户端,而是要使用另一个Servlet来响应。
5.4.1 转发
1. 转发跳转流程
2. 转发的特点
-
转发是在服务器端,两个Servlet之间的请求行为;
-
浏览器只对服务器进行了一次请求;
-
浏览器的地址不会改变,浏览器请求ServletA,ServletA转到ServletB由ServletB响应浏览器,浏览器显示的是ServletA的访问地址;
-
转发过程中,可以通过request对象传递数据;
3. 转发的代码实现
-
ServletA 转发到 ServletB
在ServletA中: |
---|
request.getRequestDispatcher("ServletB的URL ").forward(request,response); |
4. 数据传递
-
ServletA 转发到 ServletB,并传递参数
-
传数据
//ServletA:在转发操作之前,将需要传递给ServletB的参数设置到request对象中,可以传递不同类型数据 //设置转发传递的数据 request.setAttribute("stuNum","10001"); request.setAttribute("stuAge",21); //如果直接给简单类型数据,则会自动装箱为对应的封装类对象 request.setAttribute("stu",new Student(...)); //转发 request.getRequestDispatcher("ServletB的URL").forward(request,response);
-
取数据
//ServletB:在ServletB的doGet/doPost方法中,通过request对象获取ServletA传递的参数 //Object obj = request.getAttribute(String key); String snum = (String)request.getAttribute("stuNum"); int sage = (Integer)request.getAttribute("stuAge"); Student stu = (Studennt)request.getAttribute("stu");
5.4.2 重定向
1. 重定向跳转流程
-
客户端请求ServletA,ServletA响应给客户端一个新的请求地址ServletB,让客户端重新向ServeltB发送请求。
2. 重定向特点
-
重定向是客户端的行为
-
浏览器对服务器发送了两次请求;
-
重定向是由浏览器再次发送请求,浏览器地址会改变为转发的地址;
-
不能通过request对象将ServletA中的数据传递给ServletB
-
如果ServletA重定向到ServletB的时候有数据要传递到ServletB该如何实现?——url传值
3. 重定向代码实现
在ServletA中 |
---|
response.sendRedirect("ServletB访问URL "); |
4. 数据传递
//ServletA:在响应浏览器重定向地址的时候,在URL声明参数 response.sendRedirect("ServletB?key=value");
//ServletB: 获取浏览器请求的参数 String value = request.getParameter("key");