会话技术
1、会话
会话
一次会话中包含多次请求和响应
一次会话
浏览器第一次给服务器西元发送请求,会话建立,
直到有一方断开为止(服务器或客户端关闭)
功能
在一次会话的范围内的多次请求之间共享数据
HTTP协议无状态,即浏览器发送多次请求,服务器发送多次响应,每次请求响应和其它的请求响应之间不共享数据
栗子:将多件商品分多次加入购物车,是多次请求响应,最终进入购物车结算又是一次请求响应,它们彼此间应该交换数据,否则不知道结算的商品是什么
方式
客户端会话技术 Cookie
将数据存到客户端
服务器端会话技术 Session
将数据存到服务器端
2、Cookie 客户端会话技术,将数据存到客户端
第一次请求得到浏览器响应的数据后,将其存到本地,下一次发送请求时带着这些数据
快速入门
(1)创建Cookie对象,绑定数据
new Cookie(String name, String value);
(2)第一次响应时,服务器发送Cookie对象给浏览器
response.addCookie(Cookie cookie);
(3)下一次发送请求时,服务器获取浏览器携带的Cookie数组,遍历得到数据
Cookie[] request.getCookies();
/*
Cookie快速入门
*/
@javax.servlet.annotation.WebServlet("/demo01cookie")
public class Demo01Cookie extends javax.servlet.http.HttpServlet {
protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
//1、创建Cookie对象
Cookie c = new Cookie("msg", "hello");
//2、服务器发送响应时将Cookie对象发送给浏览器
response.addCookie(c);
}
protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {
this.doPost(request, response);
}
}
@WebServlet("/demo02cookie")
public class Demo02Cookie extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//3、服务器获取下一次请求携带的Cookie对象数组,遍历得到数据
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
System.out.println(name + ":" + value);
//msg:hello
//Idea-8296e770:710ed074-221b-45d7-a3b5-9d177d11d78a
}
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
实现原理
服务器中的两个Servlet Demo01Cookie发送cookie Demo02Cookie获取cookie
底层都是通过HTTP协议的请求和响应来完成的
浏览器请求Demo1Cookie的资源,Demo1Cookie发送cookie给浏览器,cookie中保存的数据是msg:hello,发送的形式是响应头Set-Cookie:msg=hello
浏览器收到响应头,根据HTTP协议的响应头约束,将响应头中的数据msg=hello保存到浏览器中
下一次浏览器发送请求时,将msg=hello放在请求的消息头中Cookie:msg=hello,发送给服务器,然后服务器可以使用getCookies方法获取数据
cookie的细节
一次可不可以发送多个cookie?可以
Cookie c1 = new Cookie(“msg”, “hello”);
Cookie c2 = new Cookie(“name”, “zhangsan”);
response.addCookie(c1);
response.addCookie(c2);
响应头SetCookie:mag=hello SetCookie:name=zhangsan
请求头Cookie:msg=hello;msg=zhangsan
cookie在浏览器中保存多久?
默认情况下,当浏览器关闭后,Cookie数据被销毁
可以设置Cookie的生命周期,进行持久化存储
cookie.setMaxAge(int seconds);
整数 将cookie数据写到硬盘的文件中,持久化存储,
seconds表示cookie存活时间为多少秒,到时间文件会自动删除
负数 默认值 当浏览器关闭后,Cookie数据被销毁
0 删除cookie信息
cookie能不能存中文?
tomcat8之前,cookie不能直接存储中文数据
需要将中文数据转码,一般使用URL编码(%+2个16进制数字表示一个字节)
tomcat8之后,cookie支持中文数据
但不支持特殊字符,如空格([32])。
需要使用URL编码来存储,URL解码来解析特殊字符
cookie共享问题?
假设在一个tomcat服务器中部署了多个web项目,
这些web项目中的cookie能不能共享?
默认情况下,cookie不能共享
cookie.set(String path)可以设置cookie的获取范围。
默认情况下设置的是当前web项目的虚拟目录,
如setPath("/day16");则在该虚拟目录下的资源才能获取该cookie
若设置cookie.setPath("/");则表示当前服务器的根路径下的所有项目都可以
获取该cookie不同的tomcat服务器下的cookie能否共享?
可以。
如tieba.baidu.com和news.baidu.com部署在不同的服务器上
(因为一个服务器支持不了这么大的用户访问量)
但它们之间的cookie应该共享,因为它们都归属于百度这个项目
其中tieba和news属于二级域名,.baidu.com属于一级域名
使用cookie.setDomain(String path)设置相同的一级域名
cookie.setDomain(".baidu.com"),
则tieba.baidu.com和news.baidu.com之间的cookie可以共享
cookie的特点
cookie存储数据在客户端浏览器
客户端浏览器的防卫级别或安全级别比较低,数据的安全性比较低,很容易丢失或被篡改
浏览器对于单个cookie的大小有限制(4kb左右),且对于同一个域名下的总cookie数量也有限制(20个左右),这里指的是持久化存储在硬盘中的cookie
cookie的作用
(1)cookie一般用于存储少量的不太敏感的数据
(2)在不登录的情况下,完成服务器对客户端的身份识别,如保存用户的一些个性化设置
实现:服务器将设置以cookie的形式发送并存到浏览器,下次再访问该网页时浏览器发送
请求会携带这些cookie,服务器就可以获取这些cookie信息
如果登录了进行的设置,可以存进数据库
案例 记住上一次的访问时间
需求
(1)访问一个servlet,如果是第一次访问,则提示:您好,欢迎您首次访问
(2)如果不是第一次访问,则提示:欢迎回来,您上次的访问时间为:显示时间字符串
分析
服务器中创建一个servlet
判断是否有一个名字为lastTime的cookie,
有则不是第一次
响应数据:欢迎回来,您上次的访问时间为:显示时间字符串
写回cookie,更新时间
没有则是第一次
响应数据:您好,欢迎您首次访问
写回cookie
不管有没有,都需要将Cookie:lastTime=时间 写到客户端保存
@WebServlet("/demo03lasttime")
public class Demo03LastTime extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应消息体的content-type
response.setContentType("text/html;charset=utf-8");
//获取所有cookie,判断是否有名为lastTime的cookie
Cookie[] cookies = request.getCookies();
boolean flag = false;//没有名为lastTime的cookie
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
if ("lastTime".equals(cookie.getName())) {
//有该cookie,不是第一次访问
flag = true;
//1、响应数据 先获取lastTime的值,即上次访问的时间
String lastTime = cookie.getValue();
//给时间解码
System.out.println("解码前的时间:" + lastTime);
lastTime = URLDecoder.decode(lastTime, "utf-8");
System.out.println("解码后的时间:" + lastTime);
response.getWriter().write("<h1>欢迎回来,您上次的访问时间为:" + lastTime + "</h1>");
//2、写回cookie到浏览器保存,先获取当前时间的字符串
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String time = sdf.format(date);
System.out.println("编码前的时间:" + time);
//给时间编码,因为tomcat8不能直接存储特殊字符,如空格
time = URLEncoder.encode(time, "utf-8");
System.out.println("编码后的时间:" + time);
cookie.setValue(time);
//设置cookie的存活时间为一个月
cookie.setMaxAge(60 * 60 * 24 * 30);
response.addCookie(cookie);
break;
}
}
}
if (cookies == null || cookies.length == 0 || flag == false) {
//没有该cookie,是第一次访问
//1、响应数据
response.getWriter().write("<h1>您好,欢迎您首次访问</h1>");
//2、写回cookie到浏览器保存,先获取当前时间的字符串
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String time = sdf.format(date);
System.out.println("编码前的时间:" + time);
//给时间编码,因为tomcat8不能直接存储特殊字符,如空格
time = URLEncoder.encode(time, "utf-8");
System.out.println("编码后的时间:" + time);
Cookie cookie = new Cookie("lastTime", time);
//设置cookie的存活时间为一个月
cookie.setMaxAge(60 * 60 * 24 * 30);
response.addCookie(cookie);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
lastTime.jsp
<%@ page import="java.net.URLDecoder" %>
<%@ page import="java.util.Date" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page import="java.net.URLEncoder" %><%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2020/12/28
Time: 10:22
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<%
//设置响应消息的content-type
//response.setContentType("text/html;charset=utf-8");//上面已经指定了
//设置flag表示是否有lastTime这个响应头
boolean flag = false;
//获取cookies
Cookie[] cookies = request.getCookies();
//遍历cookie数组
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
//有该响应头则不是首次访问
if ("lastTime".equals(cookie.getName())) {
flag = true;
//响应数据 先获取cookie的值,即上一次的访问时间
String lastTime = cookie.getValue();
//解码时间为UTF-8格式
URLDecoder.decode(lastTime, "utf-8");
%>
<h1>欢迎回来,您上次的访问时间为:<%=lastTime%></h1>
<%
//写回响应头,更新时间 先获取当前时间
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String time = sdf.format(date);
//编码时间为URL格式
time = URLEncoder.encode(time, "utf-8");
cookie.setValue(time);
cookie.setMaxAge(60 * 60 * 24 * 30);
response.addCookie(cookie);
break;
}
}
}
//获取到的cookies数组为空或长度为0或不存在lastTime这个响应头
if (cookies == null || cookies.length == 0 || flag == false) {
%>
<h1>您好,欢迎您首次访问</h1>
<%
//写回消息头
//获取当前时间
Date date = new Date();
//格式化时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String time = sdf.format(date);
time = URLEncoder.encode(time, "utf-8");
Cookie cookie = new Cookie("lastTime", time);
cookie.setMaxAge(60 * 60 * 24 * 30);
response.addCookie(cookie);
}
%>
</body>
</html>
2、JSP
概念
Java Server Pages Java服务器端页面
既可以定义HTML标签,又可以定义Java代码的页面
在body标签中通过<%%>定义Java代码
用于简化书写
原理
浏览器请求index.jsp
服务器
(1)解析请求消息,找是否有index.jsp资源
(2)有,将index.jsp转换为index_jsp.java文件
(3)编译index_jsp.java文件,生成index_jsp.class字节码文件
(4)由该字节码文件index_jsp.class提供访问,给浏览器做出响应
上述的java文件是一个servlet,因为只有servlet才可以被外界访问,则jsp本质上也是一个servlet
服务器启动时输出信息Using CATALINA_BASE,该目录包含了当前项目的配置信息conf和logs
访问index.jsp时会生成一个work文件夹,放置生成的index_jsp.java文件和index_jsp.class文件
每次访问jsp页面都会如果jsp中的内容变化了,就会更新对应的Java文件的内容
其中的index_jsp.java继承了HttpJspBase,而HttpJspBase继承了HttpServlet
index_jsp.java中的_jspService方法使用pageContext.getOut().write()将
index.jsp中的内容输出到页面上
之前通过定义servlet来做出响应时,输出页面标签的工作需要手工完成,
而使用jsp,只需要在jsp中定义标签,它转换为java文件时会自动将定义的标
签输出到页面上
在tomcat目录下也包含一个work文件夹,放置运行时产生的资源文件
jsp的脚本
定义Java代码的方式
(1)<% Java代码 %> 转换为Java文件后Java代码定义在_jspService方法中,则service方法中可以定义什么,该脚本中就可以定义什么
(2)<%! Java代码 %> 转换为Java文件后Java代码定义在成员位置,则成员位置可以定义什么,该脚本就可以定义什么,如成员变量、成员方法、静态代码块
但在jsp中或servlet中尽量不要定义成员变量,因为可能会引发线程安全问题
(3)<%= Java代码 %> 转换为Java文件后Java代码定义在_jspService方法中,定义的Java代码会被输出到页面上,输出语句中可以定义什么,该脚本就可以定义什么
jsp的内置对象
在jsp页面中不需要获取和创建,可以直接使用的对象
如out对象和request response对象,其在service方法中已经进行了声明,因此可以直接在jsp的<%%>中使用
jsp一共有9个内置对象
out.write()/print()和response.getWriter().write()的区别
out输出语句定义在什么位置,就会在什么位置输出,但response.getWriter的输出语句不管
定义在什么位置都会先于out输出
原理:服务器中的response对象和out对象都有字符输出流,因此都有缓冲区,浏览器请求
jsp页面时,tomcat做出响应之前会先找response的缓冲区,将其中的数据拼到响应体之后
建议都使用out进行输出,防止打乱布局
3、Session
概念
服务器端会话技术
在一次会话的多次请求之间共享数据,将数据保存在服务器端的对象HttpSession中
快速入门
HttpSession对象
Object getAttribute(String name);
void setAttribute(String name, Object value);
void removeAttribute(String name);
(1)获取session对象
HttpSession session = request.getSession();
(2)存储数据
session.setAttribute(“msg”, “hello session”);
(3)获取数据
Object msg = session.getAttribute(“msg”);
@WebServlet("/demo01session")
public class Demo01Session extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//使用session共享数据
//获取session
HttpSession session = request.getSession();
//存储数据
session.setAttribute("msg", "hello session");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
@WebServlet("/demo02session")
public class Demo02Session extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取session
HttpSession session = request.getSession();
Object msg = session.getAttribute("msg");
System.out.println(msg);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
原理
服务器中的2个servlet分别获取了session,获取的是同一个session
浏览器先请求第一个servlet,再请求第二个servlet
服务器怎么确保在一次会话中多次获取的session是同一个?
session的实现依赖于cookie
第一次获取session,没有cookie,会在内存中创建一个新的session对象,其拥有
一个唯一的id=…
服务器做出响应时会发送一个响应头set-cookie:JSESSIONID=…,也就是给浏览器
发送了一个id为…的cookie
浏览器收到该响应头,会将cookie信息存到浏览器内部,下次请求服务器资源时会
携带该cookie,通过请求头cookie:JSESSIONID=…,服务器根据该id查找内存中是否有
对应的session对象
细节
当客户端关闭后,服务器不关闭,2次获取的session是否是同一个?
默认情况下不是。因为客户端关闭意味着会话结束,再次获取将没有对应的cookie信息
可以通过
新建一个cookie,new Cookie(“JSESSIONID”, seesion.getId());
设置cookie存活时间 cookie.setMaxAge(60 * 60);
将这个cookie加入到响应中 response.addCookie(cookie);
则即使关闭浏览器,一小时内获取的session还是同一个
当客户端不关闭,服务器关闭后,2次获取的session是否是同一个?
不是同一个。因为服务器关闭,内存被释放,session对象被销毁。
问题:
京东上将商品加入购物车,购物车通过Map集合实现,且Map集合定义在
session对象中。
如果加入购物车后,京东服务器重启了,导致session对象不是同一个,购
物车中的商品数据就会丢失
需要确保不是一个session对象的情况下,数据不丢失
session的钝化
在服务器正常关闭之前,将session对象序列化到硬盘上
session的活化
在服务器启动后,将session文件反序列化为内存中的session对象
idea无法完成session的钝化和活化,需要使用本地的tomcat服务器
(1)将idea项目中的out目录下部署的当前项目复制到桌面,
将项目内的文件,如WEB-INF jsp文件等压缩为war包(压缩后改后
缀为.war),如day16.war
(2)将war包复制到tomcat的webapps文件夹中,然后通过bin文件
夹中的startup.bat启动本地tomcat,则war包对应的项目会被部署,会
在webapps文件夹中生成一个war包对应的项目文件夹day16,从而可
以通过浏览器访问其中的资源,如localhost:8080/day16/demo01session
(3)tomcat的work文件夹存放程序运行中动态生成的数据,如jsp转换
的Java文件,session被序列化之后的文件
正常关闭tomcat(双击shutdown.bat)后,tomcat/work/Catalina/
localhost/day16文件夹中会生成一个SESSIONS.ser文件,存放序列
化的session对象
再次启动tomcat(双击startup.bat)后,该ser文件会被自动读取并
删除,即将session文件反序列化为session对象,还原到服务器内存中
虽然序列化前后的session对象的地址值不同,但session对象包含
的数据和对应的id都是一样的,再次访问资源,仍然可以获取session对
象存储的数据
idea可以完成钝化,但无法活化
idea中启动服务器后,打印信息会显示当前服务器的配置文件所在目录
Using CATALINA_BASE
其中也有一个work目录,
访问资源如localhost:8080/day16/demo01session后将服务器关闭,
work/Catalina/localhost/day16文件夹中会生成一个SESSIONS.ser文件
但再次启动服务器会先将work目录删除,再创建一个work目录,
因此无法自动读取ser文件并进行反序列化
一般部署项目都是将项目放到tomcat的webapps文件夹下,
tomcat的work目录不会被自动删除
session什么时候被销毁?
服务器关闭
session对象调用invalidate()方法
session默认失效时间为30min,如登录一个网站后30min内没有进行任何操作,
session会失效,被服务器销毁,会提示重新登录
tomcat/conf/web.xml中可以设置失效时间,也可以在每个项目自己的web.xml中
设置,进行覆盖
<Session-config>
<Session-timeout>30</Session-timeout>
</Session-config>
特点
session用于存储一次会话的多次请求的数据,存在服务器端
重定向是2次请求,2次请求之间共享数据可以使用session或ServletContext,但ServletContext范围太大,一般使用session
session可以存储任意类型,任意大小的数据
session和cookie的区别
(1)session存储数据在服务器,cookie存储数据在客户端
(2)session存储数据没有大小限制,cookie有
(3)session数据存储在服务器,相对安全;cookie相对不安全
(4)session一般存储的数据量比较大
案例-验证码
需求
(1)访问带有验证码的登录页面login.jsp
(2)用户输入用户名、密码以及验证码
如果用户名或密码有误,跳转到登录页面,提示:用户名或密码错误
如果验证码输入有误,跳转到登录页面,提示:验证码错误
如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您
分析
login.jsp包含用户名输入框、密码输入框、验证码图片和验证码输入框,以及登录按钮
点击登录,将输入的数据提交到LoginServlet。
LoginServlet
设置request的编码
获取提交的参数的Map集合
获取验证码
将用户信息封装到User对象
先判断验证码是否正确,再判断用户名和密码是否正确,避免数据库的开销
判断程序生成的验证码和用户输入的验证码是否一致,其中程序生成的
验证码图片在一个单独的servlet中,需要单独请求
请求验证码图片和用户数据的提交是2次请求,需要共享数据
生成验证码图片后将其存到session中,在LoginServlet中获取session中
的验证码进行比较
不一致,提示:验证码错误,并将用户信息存到request中,转发到登录页面
一致,判断用户名和密码是否正确
正确 存储数据到session中 重定向到主页success.jsp
不正确 提示:用户名或密码错误 并将用户信息存到request中,转发到
登录页面
登录失败跳转到login.jsp 使用Java代码通过request中存储的数据显示错误信息
login.jsp
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2020/12/28
Time: 20:16
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
<script>
window.onload = function () {
var img = document.getElementById("img");
img.onclick = function () {
img.src = "/day16/checkcode?time=" + new Date().getTime();
};
};
</script>
<style>
div {
color: red;
}
</style>
</head>
<body>
<form action="/day16/loginServlet" method="post">
<table>
<tr>
<td>
<label for="username">用户名:</label>
</td>
<td>
<input type="text" placeholder="请输入用户名" name="username" id="username" />
</td>
</tr>
<tr>
<td>
<label for="password">用户名:</label>
</td>
<td>
<input type="password" placeholder="请输入密码" name="password" id="password" />
</td>
</tr>
<tr>
<td>
<label for="checkCode">验证码:</label>
</td>
<td>
<input type="text" name="checkCode" id="checkCode" />
</td>
</tr>
<tr>
<td colspan="2">
<img src="/day16/checkcode" id="img" />
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="登录">
</td>
</tr>
</table>
</form>
<div>
<%=request.getAttribute("cc_error") == null ? "" : request.getAttribute("cc_error")%>
</div>
<div>
<%=request.getAttribute("login_error") == null ? "" : request.getAttribute("login_error")%>
</div>
</body>
</html>
LoginServlet
package servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置request编码
request.setCharacterEncoding("utf-8");
//获取提交的参数Map集合
Map<String, String[]> map = request.getParameterMap();
//获取参数
String[] usernames = map.get("username");
String[] passwords = map.get("password");
String[] checkCodes = map.get("checkCode");
//判断验证码是否正确
//获取CheckCodeServlet中生成的验证码
String checkCode = (String) request.getSession().getAttribute("checkCode");
//删除session中存储的验证码,防止登录成功后后退网页验证码不变
request.getSession().removeAttribute("checkCode");
if (checkCode != null && checkCode.equalsIgnoreCase(checkCodes[0])) {
//验证码正确,验证用户名和密码是否正确
//这里需要调用UserDao查询数据库
if ("zhangsan".equals(usernames[0]) && "123".equals(passwords[0])) {
//用户名和密码正确
//将用户信息存进session 因为重定向是2次请求
request.getSession().setAttribute("username", usernames[0]);
//重定向到success.jsp
response.sendRedirect(request.getContextPath() + "/success.jsp");
} else {
//用户名或密码错误
//存储提示信息到request,并转发到login.jsp
request.setAttribute("login_error", "用户名或密码错误");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
} else {
//验证码错误,将提示信息存进request,并转发到login.jsp
request.setAttribute("cc_error", "验证码错误");
//转发不需要虚拟目录
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
CheckCodeServlet
package servlet;
import javax.imageio.ImageIO;
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.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
@WebServlet("/checkcode")
public class CheckCodeServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//创建一个对象,在内存中代表图片(验证码图片对象)
int width = 100;
int height = 50;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
//美化图片
//填充背景色
Graphics g = image.getGraphics();//获取画笔对象
g.setColor(Color.pink);//设置画笔颜色
g.fillRect(0, 0, width, height);
//画边框
g.setColor(Color.blue);
g.drawRect(0, 0, width - 1, height - 1);//边框有一个像素的宽度
//写验证码
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random r = new Random();//随机角标
//存储验证码到session中共享
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 4; i++) {
int index = r.nextInt(str.length());//随机生成字符串中的索引
char c = str.charAt(index);//随机字符
sb.append(c);
g.drawString(c + "", width / 5 * (i + 1), height / 2);
}
String checkCode = sb.toString();
request.getSession().setAttribute("checkCode", checkCode);
//画干扰线
g.setColor(Color.green);
for (int i = 0; i < 10; i++) {
int x1 = r.nextInt(width);
int x2 = r.nextInt(width);
int y1 = r.nextInt(height);
int y2 = r.nextInt(height);
g.drawLine(x1, x2, y1, y2);
}
//将图片输出到页面展示
ImageIO.write(image, "jpg", response.getOutputStream());
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
success.jsp
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2020/12/28
Time: 21:05
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>主页</title>
</head>
<body>
<h1><%=request.getSession().getAttribute("username")%>,欢迎您</h1>
</body>
</html>
4、JSP指令
作用
用于配置JSP页面,导入资源文件
格式
<%@ 指令名称 属性名1=属性值1 属性名2=属性值2 … %>
如<%@ page contentType=“text/html;charset=UTF-8” language=“java” %>
分类
page
配置JSP页面的
contentType 等同于response.setContentType()
设置响应消息体的MIME类型和字符集
设置当前JSP页面的编码(使用高级开发工具如idea时,charset改成什么,
当前页面编码就会自动变为什么。如果不是高级开发工具,可以使用pageEncoding
来设置当前页面的编码)
language
一般是java
buffer
缓冲区大小,默认8kb 页面中的数据一般使用out对象来输出,即out对象的缓冲区
大小,out对象相当于一个字符输出流
import
导入Java代码需要的包 将来转换为Java文件时也会变为import语句
如<%@ page import=“java.util.List” %>
errorPage
错误页面
即当前页面发生异常后,会跳到指定的错误页面 使得用户看到的是我们定义好的页
面,如服务器正忙。。。而不是具有错误信息的页面
isErrorPage
表示当前页面是否为错误页面
默认为false,标识为true后可以使用内置对象exception的方法getMessage()来获取
异常信息,将其输出到日志文件中
include
用来包含页面的,当多个页面的部分内容一样
如<%@include file=“top.jsp”%>
taglib
导入资源,一般用来导入标签库,如JSTL
如<%@ taglib prefix=“c” uri=“http://java.sun.com/jsp/jstl/core” %>
prefix 前缀 如prefix=c 使用标签时就是<c:***>
5、JSP注释
html注释
只能注释html代码片段 会将注释的代码放到response响应体中,显示在页面源代码里,但会告诉浏览器不要解析
<!-- -->
jsp注释 推荐使用
可以注释所有代码 不会将注释的代码放到response响应体中,页面源码不会显示,浏览器看不到注释的代码
<%-- --%>
6、JSP内置对象
在jsp页面中不需要创建,直接使用的对象。因为其已经在转换后的Java文件的service方法中进行了声明
转换后的Java文件在CATALINA_BASE\work\Catalina\localhost\day16\org\apache\jsp
一共9个
域对象 用来共享数据
pageContext
真实类型 PageContext
作用 当前页面共享数据
获取其它8个内置对象 get***()方法 如getException() getPage()
request
HttpServletRequest
一次请求访问的多个资源之间共享数据(转发)
session
HttpSession
一次会话的多个请求之间共享数据
application
ServletContext
所有用户之间共享数据,唯一的对象,服务器开启被创建,服务器关闭被销毁
响应对象
response
HttpServletResponse
当前页面(servlet)对象,即this
page
Object
输出对象 将数据输出到页面上
out
JspWriter
配置对象 servlet的配置对象
config
ServletConfig
异常对象 需要将isErrorPage设置为true
exception
Throwable(Exception的父类)
7、MVC开发模式
jsp演变历史
早期只有servlet,没有jsp。要给客户端响应动态页面(包括静态的html标签数据,动态的程序获取的数据),只能使用response输出数据,很麻烦。
后来有了jsp,简化了servlet的开发。既可以写标签,也可以写Java代码。而且jsp页面不需要编译,直接就可以运行,也不需要重新部署,重启服务器。
但一旦项目比较大,代码可读性很差,很难维护,也很难分工。
再后来,Java的web开发,借鉴了mvc开发模式,规定代码的书写位置,使得程序的设计更加合理。
mvc
model 模型
进行业务操作 如查询数据库,封装对象
JavaBean
view 视图
展示数据
JSP
controller 控制器
(1)获取客户端输入
(2)调用模型进行业务操作获取数据
(3)将数据交给视图展示 这里使用域对象共享数据
Servlet
浏览器请求资源,先经过控制器,控制器调用模型进行业务操作(如查询数据库,
封装对象),操作完模型将数据返回给控制器,然后控制器将数据给视图来展示,从而
给浏览器响应。
优点
耦合性低,方便维护,利于分工协作 代码分为三部分,各司其职
重用性高 如控制器调用模型获取了数据之后,可以将一部分数据交给JSP展示,一部分数据交给移动端展示。这里就可以复用模型和控制器
生命周期成本低
部署快
缺点
使得项目架构变得复杂,对开发人员要求高
JSP页面最好只用于展示,不写Java代码。一定要写Java代码可以使用EL表达式和JSTL标签
8、EL表达式
概念
Expression Language 表达式语言
作用
替换和简化jsp页面中Java代码的编写
语法
${表达式}
JSP默认支持EL表达式,要使得EL表达式不被解析,可以
在JSP的page配置中设置isELIgnored=“true”,表示忽略当前jsp页面中所有EL表达式
直接在EL表达式的$前加\ 即${表达式} 表示忽略该EL表达式
使用
运算
算术运算符 + - * /(div) %(mod)
比较运算符 > < >= <= == !=
逻辑运算符 &&(and) ||(or) !(not)
空运算符 empty
用于判断字符串、集合、数组对象是否为null以及长度是否为0
${empty list}
list对象为null或长度为0才会返回true
${not empty list}
list对象不为null且长度>0
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2020/12/30
Time: 14:44
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>EL表达式</title>
</head>
<body>
<%--在页面上输出false--%>
$(3 > 4)
<%--算术运算符--%>
$(3 + 4)<br>
$(3 / 4)<br>
$(3 div 4)<br>
$(3 % 4)<br>
$(3 mod 4)<br>
<%--比较运算符--%>
$(3 == 4)<br> <%--false--%>
<%--逻辑运算符--%>
$(3 > 4 && 3 < 4)<br> <%--false--%>
$(3 > 4 and 3 < 4)<br> <%--false--%>
</body>
</html>
获取值
EL表达式只能从域对象中获取值
${域名称.键名}
从指定域中获取指定键的值
域名称
pageScope 从pageContext域中获取值
requestScope 从request域中获取值
sessionScope 从session域中获取值
applicationScope 从application域中获取值
举例
在request域中存了name=张三
${requestScope.name}
没有获取到值会显示空字符串,不会打乱页面布局
${键名}
表示依次从最小的域中查找是否有指定键对应的值,直到找到为止
page<request<session<application
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2020/12/30
Time: 15:15
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>EL表达式获取域中的值</title>
</head>
<body>
<%
//在域中存储值
request.setAttribute("name", "张三");
session.setAttribute("age", "22");
%>
${requestScope.name} <%--页面上输出张三--%>
${sessionScope.age} <%--页面上输出22--%>
</body>
</html>
获取对象、List集合、Map集合的值
对象
${域名称.键名.属性名}
本质上是先获取键名对应的对象,
然后调用对象的get方法来获取属性值
举例
User user = new User();
user.setName(“张三”);
user.setAge(22);
user.setBirthday(new Date());
request.setAttribute(“u”, user);
直接${requestScope.u}
获取的是user的地址值
${requestScope.u.name}
可以获取对象的属性值(对象的属性指的是对应的类中的get
和set方法名去掉get和set,再变为小写 即getName->Name->name)
原理:先在request域中找是否有u这个键对应的值,
有的话再调用getName方法
获取name这个属性的值,没有getName方法会报错
${requestScope.u.birthday.year}
获取year
原理:获取了birthday这个属性的值后,
调用getYear方法获取year这个属性对应的值
如果要格式化日期对象,可以在User类中添加一个方法getBirStr()
public String getBirStr() {
if(birthday != null) {
//格式化日期
SimpleDateFormat sdf =
new SimpleDateFormat(“yyyy年MM月dd日 HH:mm:ss”);
birthday = sdf.format(birthday);
//返回字符串
return birthday;
} else {
return “”;
}
}
然后${u.birStr}
就可以获取格式化之后的生日
上面的getBirStr方法被称为逻辑视图,没有对应的成员变量,只是为了在
页面上更好地展示数据而提供的方法
因此要获取对象指定的属性值***,需要这个对象对应的类中有get***这个
方法,否则会报错
List集合
${域名称.键名[索引]}
举例
List list = new List();
list.add(“aaa”);
list.add(“bbb”);
List.add(user);
request.setAttribute(“list”, list);
$(requestScope.list)
输出{aaa,bbb}
$(requestScope.list[0])
输出aaa
索引越界,输出的是空字符串
$(requestScope.list[2].name)
输出张三
Map集合
$(域名称.键名.key名)
$(域名称.键名["key名"])
举例
Map map = new HashMap();
map.put(“sname”, “李四”);
map.put(“gender”, “男”);
map.put(“user”, user);
$(requestScope.map.gender)
输出男
$(requestScope.map["gender"])
输出男
$(requestScope.map.user.name)
输出张三
$(requestScope.map["user"].name)
输出张三
隐式对象
EL表达式中不用创建,可以直接使用的对象
EL表达式中一共有11个隐式对象 pageScope requestScope sessionScope applicationScope
pageContext 是jsp中的内置对象,也是EL中的隐式对象
获取jsp其它8个内置对象
$(pageContext.***)
如$(pageContext.request)
$(pageContext.request.contextPath)
在jsp页面动态获取虚拟目录
9、JSTL标签
概念
JavaServer Pages Tag Library JSP标准标签库
由apache组织提供的开源的免费的jsp标签
作用
用于简化和替换jsp页面上的Java代码
使用步骤
导入jstl相关jar包
引入标签库
taglib指令 <%@ taglib prefix="" uri="" %>
prefix 标签前缀
uri
http://java.sun.com/jsp/jstl/core 高版本 一般用这个
http://java.sun.com/jstl/core
使用标签
常用标签
if if语句
<c:if test=""></c:if>
test是必需的属性,接收boolean表达式,一般结合EL表达式一起使用
表达式为true,显示if标签体内容
表达式为false,不显示if标签体内容
没有else情况,需要再定义一个if标签
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %><%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2020/12/30
Time: 18:55
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>jstl的if标签</title>
</head>
<body>
<%
//判断request域中的一个List集合是否为空,如果不为空则显示遍历的集合
List list = new ArrayList();
request.setAttribute("list", list);
list.add("aaa");
%>
<c:if test="${not empty requestScope.list}">
<c:forEach begin="1" end="${list.size()}" var="i" step="1" varStatus="s">
${s.index} ${s.count} ${i} <%--1 1 1--%>
</c:forEach>
<c:forEach items="${list}" var="i" varStatus="s">
${s.index} ${s.count} ${i} <%--0 1 aaa--%>
</c:forEach>
</c:if>
</body>
</html>
choose switch语句
完成数字编号对应星期几的案例
(1)域中存储1个数字
(2)使用choose标签取出数字 相当于switch
(3)使用when标签做数字判断 相当于case
(4)otherwise标签做其它情况的声明 相当于default
<c:choose>
<c:when test="${number == 1}">星期一</c:when>
<c:when test="${number == 2}">星期二</c:when>
<c:when test="${number == 3}">星期三</c:when>
<c:when test="${number == 4}">星期四</c:when>
<c:when test="${number == 5}">星期五</c:when>
<c:when test="${number == 6}">星期六</c:when>
<c:when test="${number == 7}">星期日</c:when>
<c:otherwise>数字不在范围内</c:otherwise>
</c:choose>
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2020/12/30
Time: 19:20
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>jstl的choose标签</title>
</head>
<body>
<%--完成数字编号对应星期几的案例
1、域中存储1个数字
2、使用choose标签取出数字 相当于switch
3、使用when标签做数字判断 相当于case
4、otherwise标签做其它情况的声明 相当于default
--%>
<%
request.setAttribute("number", 3);
%>
<c:choose>
<c:when test="${number == 1}">星期一</c:when>
<c:when test="${number == 2}">星期二</c:when>
<c:when test="${number == 3}">星期三</c:when>
<c:when test="${number == 4}">星期四</c:when>
<c:when test="${number == 5}">星期五</c:when>
<c:when test="${number == 6}">星期六</c:when>
<c:when test="${number == 7}">星期日</c:when>
<c:otherwise>数字不在范围内</c:otherwise>
</c:choose>
</body>
</html>
foreach for循环
完成重复的操作 普通for循环
属性
begin 开始值
end 结束值
var 临时变量
step 步长
varStatus 循环状态对象
index 容器中元素的索引 从0开始 在普通for循环中表示每个元素的值
count 第几次循环 从1开始
遍历容器 增强for循环
for(i : list) {
}
items 容器对象 相当于list
var 容器中元素的临时变量 相当于i
varStatus 循环状态对象
index 容器中元素的索引 从0开始 在普通for循环中表示每个元素的值
count 第几次循环 从1开始
<%@ page import="java.util.ArrayList" %>
<%@ page import="java.util.List" %><%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2020/12/30
Time: 19:29
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>jstl的foreach标签</title>
</head>
<body>
<%--普通for循环--%>
<c:forEach begin="1" end="10" var="i" step="1" varStatus="s">
${i}
${s.index}
${s.count}
</c:forEach>
<%--增强for循环--%>
<%
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
%>
<c:forEach items="${list}" var="i" varStatus="s">
${s.index}
${s.count}
${i}
<%--1 1 1 2 2 2 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7 8 8 8 9 9 9 10 10 10--%>
</c:forEach>
</body>
</html>
练习
需求
在request域中有一个存有User对象的List集合,需要使用jstl+el将list集合中的数据
展示到jsp页面的表格中
<%@ page import="practice.User" %>
<%@ page import="java.util.Date" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %><%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2020/12/30
Time: 19:59
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>jstl+el练习</title>
</head>
<body>
<%
List list = new ArrayList();
list.add(new User("张三", 18, new Date()));
list.add(new User("李四", 22, new Date()));
list.add(new User("王五", 23, new Date()));
request.setAttribute("list", list);
%>
<table border="1" cellpadding="0" cellspacing="0" width="500" align="center">
<tr>
<th>编号</th>
<th>姓名</th>
<th>年龄</th>
<th>生日</th>
</tr>
<c:forEach items="${list}" var="user" varStatus="s">
<c:if test="${s.count % 2 != 0}">
<%--奇数行为蓝--%>
<tr bgcolor="#add8e6" align="center">
<td>${s.count}</td>
<td>${user.name}</td>
<td>${user.age}</td>
<td>${user.birStr}</td>
</tr>
</c:if>
<c:if test="${s.count % 2 == 0}">
<%--偶数行为粉--%>
<tr bgcolor="#ffb6c1" align="center">
<td>${s.count}</td>
<td>${user.name}</td>
<td>${user.age}</td>
<td>${user.birStr}</td>
</tr>
</c:if>
</c:forEach>
</table>
</body>
</html>
10、三层架构
软件设计架构 在服务器中部署的web项目分为3层
界面层(表示层/web层) 用户看得到的界面,用户可以通过界面上的组件和服务器交互
接收用户参数,封装数据,调用业务逻辑层完成处理,转发jsp页面完成显示。
包名:公司名称.项目名称.web 如cn.itcast.项目名.web 放servlet等
业务逻辑层(service层) 处理业务逻辑,即功能的
组合数据访问层中的简单方法,形成复杂的功能(业务逻辑操作)。
如cn.itcast.项目名.service
数据访问层(dao层 data access object) 操作数据存储文件的
定义了对于数据库最基本的CRUD操作。
如cn.itcast.项目名.dao
原理
浏览器给服务器发送请求,服务器操作数据库,数据库返回数据给服务器,服务器将数据响应给浏览器,展示在页面上
浏览器访问界面层,界面层调用业务逻辑层,业务逻辑层访问数据访问层,数据访问层操作数据库,数据库返回数据给数据访问层,数据访问层将数据封装返回给业务逻辑层,业务逻辑层将数据返回给界面层,界面层将数据展示在浏览器中
mvc中,浏览器访问界面层中的控制器servlet,servlet接收用户请求,获取用户提交的参数,封装数据,然后调用业务逻辑层,数据访问层,数据库。最后业务逻辑层将数据返回给servlet,servlet存储数据,转发到视图jsp页面,由jsp页面给浏览器做出响应。
注意
数据访问层中定义了对于数据库最基本的CRUD操作,即增删改查等方法。这些方法可以在业务逻辑层中组合成比较复杂的功能(业务逻辑操作),大大增强了数据访问层中简单方法的复用性。
框架就是对三层结构的封装和简化
界面层对应SpringMVC(web层框架),简化servlet的编写
数据访问层对应MyBatis(持久层框架),简化对数据库的访问操作
业务逻辑层对应Spring,其也操作界面层和数据访问层,是JavaEE开发的灵魂框架