目录
1 分析使用存粹的Servlet开发web应用的缺陷
这就需要再Servlet当中编写html/css/JavaScript等前端代码,在java中编写前端代码:难度大、麻烦、耦合度高、不美观、维护成本高等等问题。
思考一下,在Servlet中写前端代码最麻烦的是,前端代码在Servlet中是放在out.print("");中的,是以字符串的形式写进去的,然后out.print()输出就会以前端的形式输出。这样的字符串是很难写的,所以是否存在这样一种工具,不需要再写Servlet了,我们只需要写这个Servlet程序中的前端代码,然后这个工具会将我们写的前端代码自动翻译成Servlet这种java程序,然后在自动将java程序编译成class文件,最后再使用jvm调用这个class中的方法。确实存在,这就是JSP。
2 JSP原理解析
我的第一个JSP程序:
在WEB-INF目录之外创建一个index.jsp文件,这个文件中没有任何内容。
启动服务器,访问这个jsp,地城执行的是index_jsp.class这个java程序。实际上,当我们执行这个jsp文件时,这个文件会被tomcat翻译生成index_jsp.java文件,然后会将这个Java文件编译成index_jsp.class文件。访问index.jsp实际上就是执行index_jsp.class。那么实质上index_jsp就是一个类,index_jsp类继承HttpJspBase,而HttpJspBase类继承HttpServlet,所以index_jsp类就是一个Servlet,并且jsp的声明周期和Servlet生命周期完全相同。
那我到底是怎么得出这些呢,可以打开文件夹查看一下:在idea输出端找到CATALINA_BASE这个目录,在电脑中打开,当执行jsp文件的时候就会生成下面两个文件:
我们打开index_jsp.java查看:发现这个类继承HttpJspBase
我们再在tomcat源码中找到这个类查看:发现它确实继承的HttpServlet。
jsp文件第一次访问的时候比较慢
大部分的运维人员在给客户演示项目的时候,都要提前把所有的jsp文件夹先访问一遍。因为第一次访问要先把jsp文件翻译成java源文件,然后再把java源文件编译成class文件,然后通过class创建servlet对象,然后调用servlet对象的init方法,最后调用service方法。
第二次就比较快了,因为第二次直接调用单例servlet对象的service方法即可.
JSP是什么
- JSP本质上就是一个servlet,一个Java程序。Servlet是javaEE的13个子规范之一,JSP也是。
- 开发JSP的最高境界:眼前事JSP代码,脑袋呈现的是Java代码。
- 对JSP进行错误调试的时候,还是要打开JSP文件对应的java文件,检查java代码
JSP既然本质上是Servlet,那么JSP和Servlet到底有什么区别呢?
职责不同:Servlet的职责是什么:收集数据(逻辑处理,业务处理,连接数据库,收集数据)
JSP的职责:展示数据(做数据展示,也就是更擅长写前端代码)
3 JSP基本语法
在jsp编写文字,都会被翻译到哪里?会被翻译到Servlet类的service方法的out.write("翻译到这里"),直接翻译到双引号里,被java程序当做普通字符串打印输出到浏览器。其实这操作和我们之前的操作差不多,我们之前用到的是out.print,不过这里它可以自动帮我们翻译,不需要我们一点点写了。
下面随便在jsp文件写点东西:
找到jsp文件翻译成的java文件,查看信息,果然被自动加了out.write
JSP的page指令解决响应时中文乱码问题:<%@ page contentType="text/html;charset=UTF-8" %>
怎么在JSP中编写Java代码:
- <% java语句; %>
- 在这个符号中编写java代码,被翻译到Servlet类的service方法内部,在写代码时要思考在这个符号里面写代码就是在“方法体”里面写代码,方法体可以写什么不可以写什么,心里是否明白。
- 方法体中编写代码要遵循自上而下的顺序
- service方法当中不能写静态代码块,不能写方法,不能定义成员变量等等
- 在同一个JSP当中<%%>这个符号可以多次出现
<%!%>:这个符号当中编写的java程序会自动翻译到service方法之外。这个方法很少用,因为在service方法外面编写静态变量和实例变量都会存在线程安全问题。
在JSP中如何编写JSP专业注释: <%--这是JSP专业注释--%>,这样的注释不会翻译到java源代码中。
怎么向浏览器输出一个java变量:
- 我们发现在jsp自动生成的Servlet文件中,输出信息使用的是out.write,因此我们可以在<%%>里面使用这个语句来向浏览器输出java信息。
- 注意:这里的out就是jsp的九大内置对象之一,可以直接拿来用,但必须只能在service方法内部使用。
- 如果向浏览器输出的是固定的字符串,可以直接写,不需要写在<%%>中
- 如果输出的内容有java代码可以使用这个语法格式:<%= %> 在等号后编写输出的内容,这个符号最终会被翻译成out.print();
- 什么时候使用<%= %>呢,输出的内容有java变量,是一个动态的内容时。
4 Servlet和JSP改造oa项目
首先创建一个新的模块,将之前写好的html文件改成jsp文件,并加上page信息避免出现乱码,复制到web目录下。然后我们开始改造。首先打开首页index.jsp,我们发现这里用的路径是list.html,肯定是不对的,改成jsp,但好像仍然不够完美。因为前端发送请求时,我们最好加上项目名,那我们加上“/oa3”,但好像还是不完美。因为我们不喜欢将项目名写死,动态获取是最好的,使用<%=request.getContextPath()%>获取项目名。由此我们也发现了jsp的好处,可以写java代码,让项目更完美。
接下来将其它jsp文件中的路径都改一下,完成页面的正常流转。
创建一个Servlet,和上一个oa项目一样,只创建一个Servlet,然后不同的功能通过调用方法来实现。
现在有个问题是,如何将Servlet中得到的信息传到jsp???可以先将数据放到请求域,然后转发到jsp文件 。在咋混发数据之前先创建一个javabean用来封装数据并且使用集合来装更多的数据。传到jsp文件时就可以得到请求域中的数据然后输出,不过发现这样仍然很麻烦,后续还会学更方便的方法。
其实解决了将Servlet中得到的信息传到jsp的问题,这个项目就差不多可以完成了。
实现登录功能
- 步骤一:数据库中添加一个用户表:t_user。里面存储用户登录信息(用户名和密码),密码一般在数据库表当中存储的是密文,不是明文,因为以防被管理员做不好的事情(本次先用明文)
- 步骤二:实现登录界面,一个登录的表单,用于输入用户名和密码
- 步骤三:后台要有一个Servlet来处理登录的请求,成功跳转到部门列表页面,失败跳转到失败的页面。
- 步骤四:提供一个登陆失败页面。
5 Servlet和JSP结合改造oa项目后的代码
首先项目目录结构:
add.html
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>新增页面</title>
</head>
<body>
<h1>添加信息</h1>
<hr >
<form action="<%=request.getContextPath()%>/dept/save" method="post">
部门编号<input type="text" name="dno" /><br>
部门名称<input type="text" name="dname" /><br>
部门位置<input type="text" name="loc" /><br>
<input type="submit" value="添加" />
</form>
</body>
</html>
detail.html
<%@ page import="com.itzw.oa.bean.Dept" %>
<%@ page contentType="text/html;charset=UTF-8" %>
<%
//获取请求域数据
Dept d = (Dept)request.getAttribute("dept");
String deptno = d.getDeptno();
String dname = d.getDname();
String loc = d.getLoc();
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>详情信息</title>
</head>
<body>
<h1>详情信息</h1>
<hr >
部门编号:<%=deptno%><br>
部门名称:<%=dname%><br>
部门位置:<%=loc%><br>
<input type="button" value="后退" onclick="window.history.back()" />
</body>
</html>
edit.html
<%@ page import="com.itzw.oa.bean.Dept" %>
<%@ page contentType="text/html;charset=UTF-8" %>
<%
//拿到请求域数据
Dept dept = (Dept)request.getAttribute("dept");
String deptno = dept.getDeptno();
String dname = dept.getDname();
String loc = dept.getLoc();
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>修改信息</title>
</head>
<body>
<h1>修改信息</h1>
<hr >
<form action="<%=request.getContextPath()%>/dept/modify" method="post">
部门编号<input type="text" name="dno" value="<%=deptno%>" readonly /><br>
部门名称<input type="text" name="dname" value="<%=dname%>"/><br>
部门位置<input type="text" name="loc" value="<%=loc%>"/><br>
<input type="submit" value="修改" />
</form>
</body>
</html>
error.html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录失败</title>
</head>
<body>
登录失败,请<a href="index.jsp">重新登录</a>
</body>
</html>
index.html
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>欢迎页面</title>
</head>
<body>
<%--<a href="<%=request.getContextPath()%>/dept/list">查看部门列表</a>--%>
<%--<%=request.getContextPath()%>--%>
<h1>用户登录</h1>
<hr>
<form action="<%=request.getContextPath()%>/user/login" method="post">
用户名:<input type="text" name="username"><br>
密 码:<input type="password" name="password"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
list.html
<%@ page import="java.util.List" %>
<%@ page import="com.itzw.oa.bean.Dept" %>
<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>部门列表</title>
<script type="text/javascript">
function del(dno){
var ok = window.confirm("亲,删除了不可恢复哦");
if (ok){
document.location.href="<%=request.getContextPath()%>/dept/delete?deptno="+dno;
}
}
</script>
</head>
<body>
<h1>部门信息</h1>
<hr >
<table border="1px" align="center" width="50%">
<tr>
<td>序号</td>
<td>部门编号</td>
<td>部门名称</td>
<td>操作</td>
</tr>
<%
int i = 0;
//从request中获取集合
List<Dept> deptList = (List<Dept>) request.getAttribute("deptList");
//循环遍历
if ((deptList != null)&&(deptList.size() != 0) ){
for (Dept dept : deptList) {
//在后台输出看看对不对
//System.out.println(dept.getDname());
%>
<tr>
<td><%=++i%></td>
<td><%=dept.getDeptno()%></td>
<td><%=dept.getDname()%></td>
<td><a href="javascript:void(0)" onclick="del(<%=dept.getDeptno()%>)">删除</a>
<a href="<%=request.getContextPath()%>/dept/detail?f=m&deptno=<%=dept.getDeptno()%>">修改</a>
<a href="<%=request.getContextPath()%>/dept/detail?f=d&deptno=<%=dept.getDeptno()%>">详情</a>
</td>
</tr>
<%
}
}
%>
</table>
<hr >
<a href="<%=request.getContextPath()%>/add.jsp">添加信息</a>
</body>
</html>
DeptServlet
package com.itzw.oa.web.action;
import com.itzw.oa.bean.Dept;
import com.itzw.oa.util.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
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;
import java.util.ArrayList;
import java.util.List;
@WebServlet({"/dept/list","/dept/detail","/dept/delete","/dept/save","/dept/modify"})
public class DeptServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String servletPath = request.getServletPath();
if ("/dept/list".equals(servletPath)){
doList(request,response);
}else if ("/dept/detail".equals(servletPath)){
doDetail(request,response);
}else if ("/dept/delete".equals(servletPath)){
doDel(request,response);
}else if ("/dept/save".equals(servletPath)){
doSave(request,response);
}else if ("/dept/modify".equals(servletPath)){
doModify(request,response);
}
}
/**
* 根据前端提交数据修改信息
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void doModify(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
//拿到前端提交的数据
String dno = request.getParameter("dno");
String dname = request.getParameter("dname");
String loc = request.getParameter("loc");
//连接数据库修改数据
Connection conn = null;
PreparedStatement ps = null;
int i = 0;
//连接数据库
try {
conn = DBUtil.getConnection();
String sql = "update dept set dname = ?,loc = ? where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,dname);
ps.setString(2,loc);
ps.setString(3,dno);
i = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(conn,ps,null);
}
if (i == 1){
//修改成功,重定向到list页面
String contextPath = request.getContextPath();
//重定向一定要记得加项目名
response.sendRedirect(contextPath+"/dept/list");
}
}
/**
* 新增部门信息
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void doSave(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取前端提交的信息
String dno = request.getParameter("dno");
String dname = request.getParameter("dname");
String loc = request.getParameter("loc");
//连接数据库,将获取到的信息存入数据库
Connection conn = null;
PreparedStatement ps = null;
int i = 0;
//连接数据库
try {
conn = DBUtil.getConnection();
String sql = "insert into dept(deptno,dname,loc) values (?,?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1,dno);
ps.setString(2,dname);
ps.setString(3,loc);
i = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(conn,ps,null);
}
if (i == 1){
//新增成功,重定向到list页面
String contextPath = request.getContextPath();
//重定向一定要记得加项目名
response.sendRedirect(contextPath+"/dept/list");
}
}
/**
* 根据部门编号删除部门信息
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void doDel(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
//获取编号
String deptno = request.getParameter("deptno");
//连接数据库
Connection conn = null;
PreparedStatement ps = null;
int i = 0;
//连接数据库
try {
conn = DBUtil.getConnection();
String sql = "delete from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,deptno);
i = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(conn,ps,null);
}
if (i == 1){
//删除成功,重定向到list页面
String contextPath = request.getContextPath();
//重定向一定要记得加项目名
response.sendRedirect(contextPath+"/dept/list");
}
}
/**
* 根据编号获取部门信息
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void doDetail(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
//获取编号
String deptno = request.getParameter("deptno");
//创建对象
Dept dept = new Dept();
//连接数据库获取部门信息
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
//连接数据库
try {
conn = DBUtil.getConnection();
String sql = "select dname,loc from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,deptno);
rs = ps.executeQuery();
if (rs.next()){
String dname = rs.getString("dname");
String loc = rs.getString("loc");
dept.setDeptno(deptno);
dept.setDname(dname);
dept.setLoc(loc);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(conn,ps,rs);
}
//将数据放入请求域
request.setAttribute("dept",dept);
//转发
String f = request.getParameter("f");
if ("m".equals(f)){
//跳转到修改页面
request.getRequestDispatcher("/edit.jsp").forward(request,response);
}else {
//跳转到详情页面
request.getRequestDispatcher("/detail.jsp").forward(request,response);
}
}
/**
* 部门列表
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
private void doList(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
//创建一个集合
List<Dept> depts = new ArrayList<>();
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
//连接数据库
try {
conn = DBUtil.getConnection();
String sql = "select deptno,dname,loc from dept";
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()){
String deptno = rs.getString("deptno");
String dname = rs.getString("dname");
String loc = rs.getString("loc");
//怎么将这个数据拿到jsp文件中呢
//可以先将数据放到请求域,然后转发到jsp文件
//先将数据装到对象中
Dept dept = new Dept();
dept.setDeptno(deptno);
dept.setDname(dname);
dept.setLoc(loc);
//因为不止一个对象,所以要装到集合中
depts.add(dept);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(conn,ps,rs);
}
//将集合放到请求域
request.setAttribute("deptList",depts);
//转发
request.getRequestDispatcher("/list.jsp").forward(request,response);
}
}
UserServlet
package com.itzw.oa.web.action;
import com.itzw.oa.util.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
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;
@WebServlet("/user/login")
public class UserServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//先获取到前端提交的用户名和密码username,password:
String username = request.getParameter("username");
String password = request.getParameter("password");
boolean success = false;
//连接数据库
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
String sql = "select * from t_user where username = ? and password = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,username);
ps.setString(2,password);
rs = ps.executeQuery();
if (rs.next()){
//查到数据就是登录成功
success = true;
}
} catch (SQLException e) {
e.printStackTrace();
}
//判断是否登录成功
if (success){
//登录成功
response.sendRedirect(request.getContextPath()+"/dept/list");
}else {
//登录失败
response.sendRedirect(request.getContextPath()+"/error.jsp");
}
}
}
bean目录下的Dept
package com.itzw.oa.bean;
public class Dept {
private String deptno;
private String dname;
private String loc;
public Dept() {
}
public Dept(String deptno, String dname, String loc) {
this.deptno = deptno;
this.dname = dname;
this.loc = loc;
}
public String getDeptno() {
return deptno;
}
public void setDeptno(String deptno) {
this.deptno = deptno;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
@Override
public String toString() {
return "Dept{" +
"deptno='" + deptno + '\'' +
", dname='" + dname + '\'' +
", loc='" + loc + '\'' +
'}';
}
}
util目录里的内容、resources里的内容之前有展示过,都是一样的,这就不展示了。
但我们发现了一个问题,这里的登录功能好像只是个摆设,不管有没有这个登录功能我都可以根据URL访问资源。这个问题就需要后面的学习来解决了。
6 session机制
6.1 session机制概述
什么是会话?
- 会话对应的英语单词:session
- 用户打开浏览器,进行一系列操作,关闭浏览器,整个过程叫一次会话。会话对应的服务器端的java对象是:session
- 回顾:什么是一次请求:用户在浏览器上点击了一下然后页面停下来,可以粗略认为是一次请求,请求对应的服务器端的java对象是:request
- 一次会话包含多次请求
- 在java的Servlet规范中,session对应的类名:HttpSession
- session机制属于B/S结构的一部分
- session对象的主要作用是:保持会话状态。用户登录成功了,这是一种登录成功的状态,使用session对象可以保持这个登录状态。
为什么需要session对象来保存会话状态呢?
- 因为Http协议是一种无状态协议
- 什么是无状态协议:请求的时候,B和S是连接的,但是请求结束之后,连接就断了。为什么要这么做?因为这样可以降低服务器的压力。请求的瞬间是连接的,结束之后就会断开,这样服务器压力小。
- 只要B和S断开了,那么关闭浏览器这个动作,服务器知道吗?不知道。
为什么不使用request对象或者ServletContext对象保存会话状态?
- request是一次请求一个对象,request请求域太小
- ServletContext对象是服务器启动的时候创建的,服务器关闭的时候销毁,这个ServletContext对象只有一个。ServletContext对象的域太大。
- session对象是界于这两者中间的。
思考一下:Session对象的实现原理
- HttpSession session = request.getSession();
- 这行代码很神奇,张三访问的时候获取的session对象就是张三,李四访问的时候获取的session对象就是李四。这是怎么做到的呢,在张三访问的时候,张三和session对象都做了一个标记,这个标记就是他们能识别他们自己的session对象的原因,这就是cookie,这个知识后面再学。
总结一下session的存在原因
因为Http协议是无状态协议,请求结束连接就断了。但我们有时候需要让服务不断,比如登录状态,我们不想每次打开这个网页都要登录一次。所以就有了session,它可以让本次会话中的登录状态一直保存。
6.2 session机制原理
说这么废话先写个session对象看看啥样子:
HttpSession session = request.getSession();
就这一行代码就可以获取session对象,我们打开浏览器,不管访问几次都是同一个session对象。需要注意的是:session对象是存储在服务器端的。如果没有获取到任何session对象则新建。
session实现原理:
- 在web服务器中有一个session列表,类似于map集合,key存储的是sessionid,value存储的是session对象
- 用户第一次发送请求时,服务器会新建一个新的session对象,同时给session对象生成一个id,然后会将这个id发送给浏览器,浏览器会将这个id保存在缓存中。
- 用户第二次发送请求时,会将缓存中的sessionid发送给服务器,服务器获取到id然后在session列表找这个id对应的session对象。
为什么关闭浏览器会话结束
因为sessionid存储在缓存中,关闭浏览器缓存会清除,当再次向服务器发送请求的时候并没有id可以发送,找不到服务器的session对象,自然就会话结束。但服务器中依然存在这个session对象,只是浏览器找不到了。
session对象什么时候被销毁
- 一种销毁:超时销毁,可以设置超时时间,到了那个时间如果还没有对这个session对象访问就自动销毁,和浏览器关不关没关系。
- 一种销毁:手动销毁
- 那么如何设置超时时长?可以在web.xml文件设置,如下:
-
<!--这里表示40分钟--> <session-config> <session-timeout>40</session-timeout> </session-config>
cookie被禁用了,session还能找到吗?
- 前面说id是被存在缓存也就是cookie中,那么cookie被禁用如何呢?
- 找不到了。每一次请求都是新的对象。
- cookie被禁用,session机制还能实现吗?可以实现,需要使用URL重写机制,在URL后面加上;jsessionid=id全名
- URL重写会大大提高开发成本,所以大部分网站的设计是:如果你禁用cookie,你就别用了,老子不伺候你。
总结一下目前学到的域对象:
- request(对应的类名:HttpServletRequest):请求域(请求级别的)
- session(对应的类名:HttpSession):会话域(用户级别的)
- application(对应的类名:ServletContext):应用域(项目级别的,所有用户共享)
- 它们三个域都有以下三个公共的方法:setAttribute(向域中绑定数据)、getAttribute(从域中获取数据)、removeAttribute(删除域当中的数据)
- 使用原则:尽量使用小的域
7 使用session解决oa项目的登录问题
登录成功后,可以将用户信息存储到session当中,也就是说session当中有用户的信息就代表登录成功了,没有则表示没登录过,则跳转到登录界面
在实现登录功能的Servlet中,在成功登录后加将用户名存入session
不管访问哪个资源都要先判断session中有没有登录的用户名,有才能访问。
可以使用下面代码做个简单的欢迎语句,不过不能直接使用session.getAttribute,不知道为啥
<h3>欢迎[<%=request.getSession().getAttribute("username")%>]</h3>
8 退出系统
这里就是手动销毁session对象
list界面加上超链接
<a href="<%=request.getContextPath()%>/user/exit">[退出系统]</a>
Servlet实现退出操作
private void doExit(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
//获取session
HttpSession session = request.getSession(false);
if (session != null){
//手动销毁session对象
session.invalidate();
//跳转到登录界面
response.sendRedirect(request.getContextPath());
}
}
9 Cookie原理
session的实现原理中,每一个session对象都会关联一个sessionid
- JSESSIONID=41SHG372864246HD3219748NDJS4
- 以上这个键值对就是cookie对象
- 对于session关联的cookie来说,这个cookie被保存在浏览器的运行内存当中,只要浏览器不关闭,用户再次发送请求的时候,会自动运行内存中的cookie发送给服务器。服务器根据这个id值找到对应的session对象。
cookie怎么生成?cookie保存在什么地方?cookie有啥用?浏览器什么时候发送cookie,发送哪些cookie给服务器?
cookie最终是保存在浏览器客户端上的。
- 可以保存在运行内存中(浏览器关闭cookie就消失了)
- 也可以保存在硬盘中(永久保存)
cookie有啥用?
- cookie和session机制都是为了保存会话状态
- cookie是将会话的状态保存在浏览器客户端上。(cookie数据存储在浏览器客户端上)
- session是将会话状态保存在服务器上(session对象是存储在服务器上的)
- 为什么要有cookie和session机制呢?因为HTTP协议是无状态无连接协议。
cookie经典案例
- 京东商城,在很久之前,未登录的情况下,可以向购物车加物品,然后关闭商城,再次打开浏览器购物车的商品还在,这是怎么做到的呢?购物车的商品编号放入cookie中,cookie保存在硬盘中,这样就可以了。注意:cookie如果清除掉,购物车的商品就消失了。
- 126邮箱有个功能:十天免登录。这是怎么实现的呢?用户输入正确的用户名和密码选择十天免登录后,浏览器客户端会保存一个cookie,这个cookie保存用户名密码等信息,保存在硬盘当中,十天有效。
- 怎么让cookie失效:十天自动失效;或者改密码;或者在客户端清除cookie。
cookie机制和session机制都不属于java中的机制,都是HTTP协议的一部分。
HTTP协议规定任何一个cookie都是由name和value组成的。
在java的servlet中,对cookie提供了哪些技术支持?
- 提供一个Cookie类专门表示cookie数据。jakarta.servlet.http.Cookie。
- java程序怎么把cookie数据发送给浏览器?response.addCookie(cookie)。
在HTTP中这样规定:当浏览器发送请求的时候,会自动携带该path下的cookie数据给服务器。
接下来我们在idea使用cookie感受一下:
//new一个cookie
Cookie cookie = new Cookie("productid","122498164512645");
//设置cookie存活时间(单位是s),本次设置一小时
cookie.setMaxAge(60*60);
//响应给浏览器
response.addCookie(cookie);
可以用Cookie对象的setMaxAge方法设置存活时间,打开浏览器可以查看:
值得注意的是:当cookie的有效时间设置为0时表示该cookie被删除,有效时间设置为负数的时候表示这个cookie不被存储到硬盘文件当中,等于没有使用这个方法,好像没啥卵用啊。
关于cookie的path,cookie的关联路径:
- 假设现在发送的请求路径为“localhost:8080/servlet11/cookie/generate”生成的cookie,如果cookie没有设置path,默认path是什么?
- 默认的path是:“localhost:8080/servlet11/cookie”以及它的子路径,也就是说以后只要浏览器发送的请求是这个路径或者它的子路径,cookie都会发送到服务器。
- 手动设置cookie的path:cookie.setPath("servlet11");表示只要是这个servlet11项目的请求路径,都会提交这个cookie给服务器。
那么浏览器发送cookie给服务器后,java程序怎么接收呢:
//接收cookie
Cookie[] cookies = request.getCookies();
//遍历cookie
if (cookies != null){
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
System.out.println(name + " = " + value);
}
}
10 使用cookie实现十天内免登录功能
同样是改造之前的oa项目
先实现登录功能:
- 登录成功跳转到部门列表页面
- 登录失败跳转到登录失败页面
修改前端页面:
- 在登录界面给个复选框(十天内免登录)
- 用户选择复选框表示支持十天内免登录,没有选择就不支持
修改servlet中的login方法
- 如果用户登录成功了,并且用户登录时选择了十天内免登录功能,这个时候应该在Servlet的login方法中创建cookie,用来存储用户名和密码,并且重置路径,设置有效期,将cookie响应给浏览器
- 用户再次访问网站的时候,有两个走向:要么跳转到部门列表,要么跳转到登录界面,这是由java程序来进行判断的
- 也就是说我们还要再写一个Servlet来判断
本次新加了一个Servlet,写了一个welcome配置信息。因为,当我成功使用cookie后,再次访问这个网页的任何一个资源的时候都是可以直接访问的,并且直接访问oa项目不指定具体路径的时候是直接可以调到列表页面的,所以之前的默认页面(登录页面)就不能继续使用了。
web.xml配置信息:
<welcome-file-list>
<welcome-file>welcome</welcome-file>
</welcome-file-list>
welcome:
package com.itzw.oa.web.action;
import com.itzw.oa.util.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@WebServlet("/welcome")
public class Welcome extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Cookie[] cookies = request.getCookies();
String username = null;
String password = null;
if (cookies != null) {
//cookie有内容
for (Cookie cookie : cookies) {
//取出cookie
String name = cookie.getName();
//判断是否是登录的用户名
if ("username".equals(name)) {
//取出用户名
username = cookie.getValue();
} else if ("password".equals(name)) {
//取出密码
password = cookie.getValue();
}
}
}
//先判断取到的用户名和密码是否为空
if (username != null && password != null){
//连接数据库查看cookie获取到的用户名和密码是否和数据库一样
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
boolean success = false;
try {
conn = DBUtil.getConnection();
String sql = "select * from t_user where username = ? and password = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,username);
ps.setString(2,password);
rs = ps.executeQuery();
if (rs.next()){
//查到数据就是登录成功
success = true;
}
} catch (SQLException e) {
e.printStackTrace();
}
if (success){
//cookie里的数据和数据库对应上了,跳转到列表页面
//获取Session
HttpSession session = request.getSession();
//将username放入session对象中
session.setAttribute("username",username);
response.sendRedirect(request.getContextPath()+"/dept/list");
}else {
//错误,跳转到登录界面
response.sendRedirect(request.getContextPath()+"index.jsp");
}
}else{
//cookie为空,跳转到登录界面
response.sendRedirect(request.getContextPath()+"/index.jsp");
}
}
}
UserServlet新增:
//登录成功创建cookie
String f = request.getParameter("f");
//选择了十天内免登录开始创建cookie
if ("1".equals(f)){
//存储登录的用户名和密码
Cookie cookie1 = new Cookie("username",username);
Cookie cookie2 = new Cookie("password",password);
//设置有效时间为10天
cookie1.setMaxAge(60*60*24*10);
cookie2.setMaxAge(60*60*24*10);
//设置path(只要访问这个应用,浏览器就一定携带这两个path)
cookie1.setPath(request.getContextPath());
cookie2.setPath(request.getContextPath());
//响应给浏览器
response.addCookie(cookie1);
response.addCookie(cookie2);
}
注意上面的代码放在重定向前面
之前有些路径是直接使用的项目名,经过本次修改之前的那些路径不能用了,需要加上具体路径。
11 JSP指令
指令的作用:指导JSP的翻译引擎如何工作
指令包括哪些呢?
- include指令:包含指令,很少用了
- taglib指令:引入标签库的指令,JSTL再学这个
- page指令:目前重点学习
指令的使用语法是什么?<%@指令名 属性名=属性值 属性名=属性值%>
关于page指令当中都有哪些常用的属性?
- <%@page session="false" %>,其中false表示不启动内置对象session,当前JSP页面无法使用session对象;如果是true表示JSP的内置对象session一定被启动,并且没有session对象会创建。如果没有设置默认为true
- <%@page contentType="text/html" %> <%@page contentType="text/json;charset=UTF-8" %>,contenType属性用来设置响应的内容类型,同时还可以设置字符集。
- <%@page pageEncoding="UTF-8" %>,用来设置响应时采用的字符集
- <%@page import="com.sun.java_cup" %>,用来导包
- <%@page errorPage="/error.jsp" %>,当页面出现异常后跳转到error.jsp页面,不管什么错误都会跳转到这个页面。
- <%@page isErrorPage="true" %>,但是经过上面的操作我们发现这样就无法发现错误了。可以在错误页面加上这个属性,改为true可以在jsp使用九大内置对象之一exception,它可以打印异常信息。<%exception.printStackTrace();%>
JSP九大内置对象
- pageContext:页面作用域
- request:请求作用域
- session:会话作用域
- application:应用作用域
- exception:
- config
- page:其实是this,当前的Servlet对象
- out:负责输出
- response:负责响应