1. session和cookie
1.1 session和cookie原理
HTTP协议是无状态的协议,客户每次读取 web 页面,服务器都会打开新的连接,而且服务器也不会自动维护客户的上下文信息。
如何在多次请求之间共享信息呢? 服务器端如何判断一个分时段的连接是不是属于同一个客户呢?
Session 和 Cookie 就是为解决 HTTP协议的无状态采用的两种解决方案
Cookie
:将信息保存在客户端解决Session
:将信息保存在服务器端解决
1.2 cookie—实现10天免登录
重定向:可以拿到 cookie(返回到页面再跳转,所以可以拿到)
转发:拿不到 cookie(服务器内部转发,页面拿不到)
LoginServlet.java
- 创建一个 cookie
- 指定 cookie 作用范围(默认范围是当前目录)
- 指定 cookie 有效时间(默认有效时间是当前浏览器打开时)
- 把 cookie 给客户端
public class LoginServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
String username = req.getParameter("username");
String pwd = req.getParameter("pwd");
String remember = req.getParameter("remember"); // 根据勾选判断是否记住
// 增加一个服务器端格式验证,验证数据合法性
// TODO
// 应该调用后台(JDBC)判断登录是否成功;此处直接处理
boolean flag = false;
if (username.contains("lwclick") && pwd.contains("click")) {
flag = true;
}
if (flag) {
// ==================================== 完成 10天免登录功能 ====================================
// 创建一个 cookie
Cookie usernameCookie = new Cookie("uname", username);
Cookie pwdCookie = new Cookie("upwd", pwd);
// 指定 cookie 作用范围(默认范围是当前目录)
usernameCookie.setPath(req.getContextPath()); // 作用范围当前项目
pwdCookie.setPath(req.getContextPath());
if ("yes".equals(remember)) {
// 指定 cookie 有效时间(默认有效时间是当前浏览器打开时)
usernameCookie.setMaxAge(60 * 60 * 24 * 10);
pwdCookie.setMaxAge(60 * 60 * 24 * 10);
} else {
// 去掉 cookie
usernameCookie.setMaxAge(0);
pwdCookie.setMaxAge(0);
}
// 把 cookie 给客户端
resp.addCookie(usernameCookie);
resp.addCookie(pwdCookie);
// ==================================== 10天免登录功能 end ====================================
// 跳转到成功页面 【重定向 redirect】
req.setAttribute("username", username);
resp.sendRedirect(req.getContextPath() + "/login/success.jsp");
} else {
// 跳回登录页 【转发 dispatcher】
req.setAttribute("error", "用户名或密码错误");
req.getRequestDispatcher("/login/login.jsp").forward(req, resp);
}
}
}
login.jsp页面
- 拿出所有的 cookie
- 找到需要的 cookie
- 使用 cookie
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<script type="text/javascript" src="../js/jquery-1.9.1.js"></script>
<script type="text/javascript">
<!-- 客户端验证 省略 -->
</script>
</head>
<body>
<%
// 1. 拿出所有的 cookie
Cookie[] cookies = request.getCookies();
// 2. 找到需要的 cookie
String username = "";
String password = "";
String isChecked = "";
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if ("uname".equals(cookies[i].getName())) {
username = cookies[i].getValue();
isChecked = "checked";
}
if ("upwd".equals(cookies[i].getName())) {
password = cookies[i].getValue();
}
}
// 使用 cookie
// 在用户名和密码的输入框中,使用 value 字段绑定值
}
%>
<form action="../servlet/LoginServlet" method="post" onsubmit="return checkForm()">
用户名:<input type="text" id="username" name="username" value="<%=username%>" onblur="checkUser()"/>
<!-- 错误提示,省略 --><br>
密码: <input type="text" id="password" name="pwd" value="<%=password%>" onblur="checkPwd()"/>
<!-- 错误提示,省略 --><br>
<input name="remember" type="checkbox" value="yes" <%=isChecked%>/>十天免登录<br> <!-- 复选框,勾中才记住 -->
<input type="submit" value="登录">
</form>
</body>
</html>
1.3 session—实现记住用户名
- 首次访问服务器上的一个JSP页面时,JSP引擎产生一个session对象
- 每个session都有一个sessionid
- 将数据保存在服务器端, 将sessionid保存在客户端的Cookie中(服务器端也有sessionId,用来对照)
- 后续每次请求request都携带cookie到服务器端
- 服务器端根据客户端的sessionid判断属于哪个会话
LoginServlet.java
public class LoginServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
// 接收用户编号和密码
String username = req.getParameter("username");
String pwd = req.getParameter("pwd");
String remember = req.getParameter("remember"); // 根据勾选判断是否记住
// 增加一个服务器端格式验证,验证数据合法性
// TODO
// 应该调用后台(JDBC)判断登录是否成功;此处直接处理
boolean flag = false;
if (username.contains("lwclick") && pwd.contains("click")) {
flag = true;
}
if (flag) {
// ==================================== 记住用户名 ====================================
HttpSession session = req.getSession();
session.setAttribute("username", username);
// ==================================== 记住用户名 end ====================================
// 跳转到成功页面 【重定向 redirect】
resp.sendRedirect(req.getContextPath() + "/login/success.jsp");
} else {
// 跳回登录页 【转发 dispatcher】
req.setAttribute("error", "用户名或密码错误");
req.getRequestDispatcher("/login/login.jsp").forward(req, resp);
}
}
}
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%
String basePath = request.getScheme() + "://" + request.getServerName() +
":" + request.getServerPort() + request.getContextPath() + "/";
%>
<base href="<%=basePath%>">
</head>
<body>
<!-- session 代表当前会话,一个用户一定时间内的多个请求 -->
登录成功!欢迎<%=session.getAttribute("username")%> <!-- 使用session可以在【当前项目】的【所有页面】中访问到 -->
<!-- 可以用 sessionId 判断是否为同一个session -->
<%=session.getId()%>
<!-- 设置 session过期时间,过期后结束 -->
<% session.setMaxInactiveInterval(10) %>
<!-- 注销操作 -->
<a href="servlet/LogoutServlet">注销</a>
</body>
</html>
LogoutServlet.java
public class LogoutServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 立即结束当前的 session
req.getSession().invalidate();
// 跳回登录页面(注销操作建议使用重定向)
resp.sendRedirect(req.getContextPath() + "/login/login.jsp");
}
}
注意:有的浏览器不支持 cookie时,可以通过 URL重写(http://localhost:8080/sevlet/LoginServelt?sessionId=354nlehtbbew54,每次都传sessionId)的方式,来实现 session对象的唯一性
2. servletContext
2.1 统计网站访问次数
ServletContext
-
代表 web应用本身,相当于 web应用的全局变量,整个 web应用共享一个 servletContext对象
-
关闭服务器,servletContext数据清空
session的数据还在(tomcat会在服务器关闭时,自动保存session信息到硬盘)
LoginServlet.java
public class LoginServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
// 接收用户编号和密码
String username = req.getParameter("username");
String pwd = req.getParameter("pwd");
String remember = req.getParameter("remember"); // 根据勾选判断是否记住
// 增加一个服务器端格式验证,验证数据合法性
// TODO
// 应该调用后台(JDBC)判断登录是否成功;此处直接处理
boolean flag = false;
if (username.contains("lwclick") && pwd.contains("click")) {
flag = true;
}
if (flag) {
// ==================================== 统计网站的访问人数 ====================================
// 获取之前的人数
ServletContext servletContext = req.getServletContext(); // this.getServletContext();
Integer count = (Integer) servletContext.getAttribute("count");
if (count == null) {
count = 1;
} else {
count++;
}
servletContext.setAttribute("count", count);
// ==================================== 统计网站的访问人数 end ====================================
// 跳转到成功页面 【重定向 redirect】
resp.sendRedirect(req.getContextPath() + "/login/success.jsp");
} else {
// 跳回登录页 【转发 dispatcher】
req.setAttribute("error", "用户名或密码错误");
req.getRequestDispatcher("/login/login.jsp").forward(req, resp);
}
}
}
2.2 ServletAPI 总结
-
Servlet
- Servlet、 GenericServlet、 HttpServlet
-
请求和响应
- ServletRequest、 ServletResponse
- HttpServletRequest、 HttpServletResponse
-
会话和上下文
- HttpSession、 Cookie、 ServletContext
-
配置
- ServletConfig
-
请求转发
- RequestDispatcher
-
异常
- ServletException
-
Cookie类
- Cookie cookie = new Cookie(name,value);
-
HttpSession接口
- HttpSession session=request.getSession();
-
ServletContext接口
- 上下文表示每个Web应用程序的环境,且被当作是一个应用程序中所有servlet可访问的共享库。
- ServletContext context=this.getServletContext();
-
ServletConfig接口
- ServletConfig是表示单独的Servlet的配置和参数,只是适用于特定的Servlet。
- 从servlet访问一个servlet被实例化后,对任何客户端在任何时候访问有效,但仅对本servlet有效,一个servlet的ServletConfig对象不能被另一个
- ServletConfig config = this.getServletConfig();
3. 访问数据库完善登录功能
3.1 创建数据表
CREATE TABLE sys_user (
userid VARCHAR(10) PRIMARY KEY,
realname VARCHAR(20) NOT NULL,
pwd VARCHAR(10),
age INT(3)
);
INSERT INTO sys_user VALUES ("zhangsan", "张三", "zhangsan123", 23);
3.2 创建实体类
public class SysUser {
private String userId;
private String realName;
private String pwd;
private Integer age;
public SysUser(String userId, String realName, String pwd, Integer age) {
this.userId = userId;
this.realName = realName;
this.pwd = pwd;
this.age = age;
}
public SysUser() {
}
// getter、 setter、 toString
}
3.3 DBUtil类
简单封装的
public class DBUtil {
private DBUtil(){
}
/**
* 获取数据库连接
* @return
*/
public static Connection getConnection(){
String driver ="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai";
String user = "root";
String password = "root";
Connection conn = null;
try {
//1.加载驱动
Class.forName(driver);
//2.建立和数据库的连接
conn = DriverManager.getConnection(url, user, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 关闭各种资源
* @param rs
* @param stmt
* @param conn
*/
public static void closeAll(ResultSet rs, Statement stmt, Connection conn){
try {
if(rs!=null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(stmt != null){
stmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 完成DML操作:insert、update和delete
* @param sql
* @param params
* @return
*/
public static int executeUpdate(String sql, Object [] params) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
int n = 0;//添加失败
try {
//2.建立和数据库的连接
conn = DBUtil.getConnection();
//3.创建一个SQL命令发送器
pstmt = conn.prepareStatement(sql);
//4.准备好SQL语句,通过SQL命令发送器发送给数据库,并得到结果
for (int i = 0; i <params.length ; i++) {
pstmt.setObject(i+1, params[i]);
}
n = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//6.关闭资源
DBUtil.closeAll(rs, pstmt, conn);
}
return n;
}
}
3.4 创建 Dao层
DAO=Data Access Object 数据访问(存取)对象
DAO层=数据访问层
DAO模式包括以下元素:
- UserDao --> DAO接口
- UserDaoImpl --> DAO实现类
- BaseDao或者DBUtil --> 数据库操作工具类
- User实体类
/**
* Dao 面对的是数据库,只有增删改查操作
*/
public interface UserDao {
/**
* 查询用户
* @param userId
* @param password
* @return
*/
SysUser find(String userId, String password);
/**
* 保存用户
* @param user
* @return
*/
int save(SysUser user);
}
实现类:
public class UserDaoImpl implements UserDao {
@Override
public SysUser find(String userId, String password) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
SysUser user = null;
try {
conn = DBUtil.getConnection();
String sql = "select * from user where userid = ? and pwd = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userId);
pstmt.setString(2, password);
rs = pstmt.executeQuery();
if (rs.next()) {
String realName = rs.getString("realname");
int age = rs.getInt("age");
user = new SysUser(userId, realName, password, age);
}
}catch (SQLException e) {
e.printStackTrace();
} finally {
//6.关闭资源
DBUtil.closeAll(rs, pstmt, conn);
}
return user;
}
@Override
public int save(SysUser user) {
String sql = "insert into sys_user values(?, ?, ?, ?)";
Object[] params = { user.getUserId(), user.getRealName(), user.getPwd(), user.getAge() };
return DBUtil.executeUpdate(sql, params);
}
}
3.5 在 servlet中使用
public class LoginServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
// 接收用户编号和密码
String username = req.getParameter("username");
String pwd = req.getParameter("pwd");
// 增加一个服务器端格式验证,验证数据合法性
// ==================================== 访问数据库 ====================================
UserDao userDao = new UserDaoImpl();
SysUser sysUser = userDao.find(username, pwd);
if (sysUser != null) {
HttpSession session = req.getSession();
session.setAttribute("user", sysUser);
// 跳转到成功页面 【重定向 redirect】
resp.sendRedirect(req.getContextPath() + "/login/success.jsp");
} else {
// 跳回登录页 【转发 dispatcher】
req.setAttribute("error", "用户名或密码错误");
req.getRequestDispatcher("/login/login.jsp").forward(req, resp);
}
}
}
3.6 在 JSP中处理
<%@ page import="com.lwclick.entity.SysUser" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<%
String basePath = request.getScheme() + "://" + request.getServerName() +
":" + request.getServerPort() + request.getContextPath() + "/";
%>
<base href="<%=basePath%>">
</head>
<body>
欢迎您,
<%
SysUser user = (SysUser) session.getAttribute("user");
out.println(user.getRealName());
%>
</body>
</html>
4. MVC模式
分层思想:
- 视图层view:Login.jsp success.jsp
- 控制层control:LoginServlet
- 数据访问层DAO:UserDao UserDaoImpl
4.1 提取业务层
为了条理清晰,减少 LoginServlet(control层)的操作,增加一个 业务层service,将所有涉及到业务的操作,都放在service中
4.2 JavaBean
符合特定规范的Java类,是一种可重用的组件
特定规范:
- public class, 并且提供无参数构造方法
- 属性private
- 提供public的setter和getter方法
功能分类:
- 封装数据:数据Bean --> 实体类
- 封装业务:业务Bean --> service Dao
4.3 用户注册案例
register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户注册</title>
<%
String basePath = request.getScheme()+"://"+request.getServerName()+
":"+request.getServerPort()+request.getContextPath()+"/";
%>
<base href="<%=basePath%>">
</head>
<body>
<h3>注册用户</h3>
<form action="servlet/RegisterServlet" method="post">
<table border="0" width="40%">
<tr>
<td>用户编号</td>
<td><input type="text" name="userId" id="userId" /></td>
</tr>
<tr>
<td>真实姓名</td>
<td><input type="text" name="realName" id="realName"/></td>
</tr>
<tr>
<td>密 码</td>
<td><input type="password" name="pwd" id="pwd" /></td>
</tr>
<tr>
<td>确认密码</td>
<td><input type="password" name="repwd" id="repwd"/></td>
</tr>
<tr>
<td>年龄</td>
<td><input type="text" name="age" id="age"/></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" value="提交"/>
<input type="reset" value="重置"/>
</td>
</tr>
</table>
${error}
</form>
</body>
</html>
RegisterServlet.java
public class RegisterServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
//1.接收视图层的表单数据
String userId = request.getParameter("userId");
String userName = request.getParameter("realName");
String pwd = request.getParameter("pwd");
String repwd = request.getParameter("repwd");
//java.lang.NumberFormatException: For input string: "adfadf"
int age = Integer.parseInt(request.getParameter("age")); //!!! "23" "adsfadf"
// String [] hobbyArr = request.getParameterValues("hobby");///!!!
//两次密码必须相同
if(pwd == null || pwd.length()<=6 || !pwd.equals(repwd)){
request.setAttribute("error", "密码长度必须大于6,两次密码必须相同");
request.getRequestDispatcher("/register.jsp").forward(request, response);
return;
}
//2.调用业务层完成注册操作
User user = new User(userId, userName, pwd, age);
UserService userService = new UserServiceImpl();
int n = userService.register(user);
//3.根据注册结果进行页面跳转
if(n > 0){
//跳到登录页面(因为注册是添加操作,采用转发会导致表单重复提交)
response.sendRedirect(request.getContextPath()+"/login/login.jsp");
}else{
// 因为要提示错误信息,所以需要使用 转发
request.setAttribute("error", "注册失败");
request.getRequestDispatcher("/register.jsp").forward(request, response);
}
}
}
UserService.java
public interface UserService {
public int register(User user);
}
UserServiceImpl.java
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public int register(User user) {
// UserDao userDao = new UserDaoImpl();
int n = userDao.save(user);
return n;
}
}
UserDao.java
/**
* Dao面对的是数据库,只有增删改查操作
*/
public interface UserDao {
public int save(User user);
}
UserDaoImpl.java
public class UserDaoImpl implements UserDao {
@Override
public int save(User user) {
String sql = "insert into user values(?,?,?,?)";
Object params [] = {user.getUserId(), user.getUserName(), user.getPassword(), user.getAge()};
return DBUtil.executeUpdate(sql, params); // DBUtil 工具类是相同的
}
}
4.4 合并Servlet
目前是以功能划分 servlet的,登录一个,注销一个,注册一个,现在将这 3个整合到一个 servlet中
第一种方式(不推荐):
-
UserServlet.java
public class UserServlet extends HttpServlet { /** * 如果采用该种方法,表单必须是 post提交 * 必须先解决 post表单的乱码问题,再接收表单数据 * @param req * @param resp * @throws ServletException * @throws IOException */ @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("utf-8"); // 获取请求中 method的值 String method = req.getParameter("method"); // 根据 method的值进行判断 if ("login".equals(method)) { this.login(req, resp); } else if ("logout".equals(method)) { this.logout(req, resp); } else { this.register(req, resp); } } /** * RegisterServlet 注册的方法 * @param req * @param resp */ public void register(HttpServletRequest req, HttpServletResponse resp) { // TODO } /** * LoginServlet 登录的 * @param req * @param resp */ public void login(HttpServletRequest req, HttpServletResponse resp) { // TODO } /** * LogoutServlet 注销的 * @param req * @param resp */ public void logout(HttpServletRequest req, HttpServletResponse resp) { // TODO } }
-
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <% String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/"; %> <base href="<%=basePath%>"> </head> <body> <!-- 修改action的路径, action处有 ?,一定是 post提交方式 --> <form action="servlet/UserServlet?method=login" method="post"> </form> </body> </html>
-
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> <% String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/"; %> <base href="<%=basePath%>"> </head> <body> <a href="servlet/UserServlet?method=logout">注销</a> </body> </html>
第二种方式
:
-
BaseServlet.java
/** * 不需要在 web.xml中进行配置 */ public abstract class BaseServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 解决中文乱码 req.setCharacterEncoding("utf-8"); // 获取 method的值 String methodName = req.getParameter("method"); // 使用反射 !!! Class aClass = this.getClass(); try { // Object obj = aClass.newInstance(); // 不能 new!!!不然违反 servlet的单实例 Method aClassMethod = aClass.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); // 使用反射执行方法 aClassMethod.invoke(this, req, resp); } catch (Exception e) { e.printStackTrace(); } } }
-
UserServlet.java
public class UserServlet extends BaseServlet { // 不再需要 service方法 // 请求来了之后,去找父类的 service方法,通过反射,执行本类的方法 // 页面处还是需要使用 ?method=XXX 去传递参数 }
5. JSP高级内容
5.1 JSP执行原理
jsp --- 转译/翻译 --- Servlet --- 编译 --- class --- 执行
查看 JSP 转译之后的 Java文件
tomcat 的配置文件 web.xml
上图中的 JspServlet文件中的 service方法,最重要的两句话(在地址栏输入 xxxx.jsp后怎么转译、编译、解释执行的过程)
5.2 九大内建对象
-
response
当服务器创建request对象时会
同时
创建用于响应这个客户端的response对象,程序员可往里放东西 -
out
是 JspWriter 类的实例,不是PrinterWriter(在servlet.java中的out是)的实例
JspWriter 增加了一些处理缓存的方法,并且会抛出 IOException 异常
-
pageContext
用来代表整个JSP页面
Servlet 和 JSP 的联系和区别:
JSP本质上是一个 Servlet
- 联系
- 都是动态网页技术
- Servlet开发页面繁琐,推出JSP来简化页面开发
- JSP本质上是一个Servlet,会翻译成一个Servlet
- 区别
- JSP使人们把显示和逻辑分隔开,意味着两者的开发可并行进行
- Servlet需要在web.xml中配置,而JSP无需配置
- JSP主要用在视图层负责显示,而Servlet主要用在控制层负责调度
5.3 四个作用域
在 jsp页面中使用,常用的方法都是 setAttribute()
,getAttribute()
-
page:当前页面 (一个页面)
- 动态包含得不到
<jsp:include page=""></jsp:include>
- 静态包含可以得到
<%@include file=""%>
- 动态包含得不到
-
request
:当前请求 (一个请求的多个页面(多次转发) )- request = page + dipatcher转发 + include包含
- 服务器端给了响应之后,客户端再发就是一个新的 request
-
session
:当前会话 (一个用户的多个请求)结束条件:
- Session.invalidate()
- 超过 MaxInactiveInterva l时间
- 关闭浏览器(关闭后其实session还存在)
-
application
:当前应用程序(一个应用的多个用户)结束条件:
- 重启服务器
6. JSTL/EL表达式
6.1 EL表达式
使用 ${}
在 JSP 页面中代替 getAttribute() 相关操作
${requestScope.error}
: request 请求中的 error 字段 ====> 相当于 request.getAttribute(“error”)
EL的四个范围和JSP的四个范围对应,分别为pageContextScope、requestScope、sessionScope,applicationScope
如果未指定范围,则先从 pageContext范围开始找、然后找 request。。。
底层使用的是反射的机制,针对于对象的属性,使用的是 getter方法
6.2 JSTL表达式
使用 JSTL代替 JSP页面中的小脚本,声明,表达式等,全部是在服务器端执行的。
声明变量 c:set :
<c:set var="sum" value="0"></c:set>
<!-- 相当于 -->
<%
pageContext.setAttribute("sum", 0);
%>
判断是否为空值 empty :
<c:if test="${empty userList}"></c:if>
循环 map:
<c:forEach items="${map}" var="entry">
${entry.key} ---> ${entry.value}
</c:forEach>
6.3 有条件的查询用户信息
JSP页面:
<%@ page import="com.bjsxt.entity.User" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>Title</title>
<%
String basePath = request.getScheme()+"://"+request.getServerName()+
":"+request.getServerPort()+request.getContextPath()+"/";
%>
<base href="<%=basePath%>">
</head>
<body>
登录成功!欢迎您:
${sessionScope.user.userName}
当前应用的历史访问人数:${applicationScope.count}
<a href="servlet/UserServlet?method=logout">注销</a>
<hr>
<h3>用户列表</h3>
<hr>
<!-- 查询表单, 此处请求 find方法 -->
<form action="servlet/UserServlet?method=find" method="post">
<table border="0" width="40%">
<tr>
<td>用户编号</td>
<!-- ${userId} 用来获取页面转发后,request设置的数据, 数据回显 -->
<td><input type="text" name="userId" id="userId" value="${userId}" /></td>
<td>年龄</td>
<td><input type="text" name="age" id="age" value="${age}"/></td>
<td><input type="submit" value="提交"/></td>
</tr>
</table>
</form>
<hr>
<table border="1" width="60%">
<tr>
<th>用户账号</th>
<th>真实姓名</th>
<th>年龄</th>
<th>vs.index</th>
<th>vs.count</th>
<th>入学时间</th>
<th>爱好</th>
<th>操作</th>
</tr>
<c:if test="${empty requestScope.userList}">
<tr>
<td colspan="10">一个学生也没有</td>
</tr>
</c:if>
<c:if test="${not empty requestScope.userList}">
<!--1.循环之前定义sum、count,表示总年龄和人数 -->
<c:set var="sum" value="0" ></c:set>
<c:set var="count" value="0"></c:set>
<c:forEach items="${userList}" var="user" varStatus="vs">
<!--2.循环过程中,实现年龄、人数的增加 -->
<c:set var="sum" value="${sum+user.age}"></c:set>
<c:set var="count" value="${count+1}"></c:set>
<tr <c:if test="${vs.index%2==0}">bgcolor="yellow"</c:if> >
<td>${user.userId}</td>
<td>${user.userName}</td>
<td>${user.age}</td>
<td>${vs.index}</td>
<td>${vs.count}</td>
<td>${user.enterDate} || <fmt:formatDate value="${user.enterDate}" pattern="yyyy年MM月dd日"></fmt:formatDate> </td>
<td>${user.hobby}</td>
<td><a href="#">修改</a> <a href="#">删除</a></td>
</tr>
</c:forEach>
<!--3.循环之后,输出平均年龄、人数 -->
<tr>
<td colspan="10">
总人数:${count} 平均年龄:${sum/count} ||
<fmt:formatNumber value="${sum/count}" pattern="####.##" ></fmt:formatNumber>||
<fmt:formatNumber value="${sum/count}" pattern="0000.00" ></fmt:formatNumber>||
<fmt:formatNumber value="${sum/count}" pattern="¤0,000.00" ></fmt:formatNumber>
</td>
</tr>
</c:if>
</table>
</body>
</html>
Servlet.java
// 由 ` <a href="servlet/UserServlet?method=findAll">显示所有用户</a> ` 发送请求
public void findAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//接收来自视图层的数据(查询所有没有条件可以接收)
//调用业务层查询所有的用户信息
UserService userService = new UserServiceImpl();
List<User> userList = userService.findAll(); // 业务层就是查询所有的操作,此处省略
// 页面跳转
request.setAttribute("userList", userList);
request.getRequestDispatcher("/login/select.jsp").forward(request, response);
}
// =================== 查询表单请求的 find 方法 ========================
public void find(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//接收来自视图层的数据
String userId = request.getParameter("userId");
String sage = request.getParameter("age");
int age = 0;
try {
age = Integer.parseInt(sage);
} catch (NumberFormatException e){
e.printStackTrace();
}
//调用业务层查询所有的用户信息
UserService userService = new UserServiceImpl();
List<User> userList = userService.find(userId, age); // 业务层方法省略
/** 业务层,根据 userId 和 age的有无,动态拼接 sql
StringBuilder sql = new StringBuilder("select * from user where 1=1"); // 使用 1=1,后面统一跟 and
if (cuserId != null && !"".equals(cuserId)) {
sql.append(" and userid like '%" + cuserId + "%'");
}
if (cage > 0) {
sql.append(" and age > " + cage);
}
sql.append(" order by age");
*/
// 页面跳转
request.setAttribute("userList", userList);
request.setAttribute("userId", userId); // 回显输入的条件
request.setAttribute("age", sage);
request.getRequestDispatcher("/login/select.jsp").forward(request, response);
}
7. 过滤器
过滤器是驻留在服务器端的Web组件,它可以截取客户端和服务器目标资源之间的请求和响应信息,并对这些信息进行处理
过滤器是请求到达一个目标资源前的预处理程序,和/或响应离开目标资源后的后处理程序
应用:编码转化、数据过滤和替换、身份验证、数据加密、数据压缩、日志记录等
过滤器不仅要定义,也要在 web.xml 中配置,使用的是 职责链模式
7.1 解决中文乱码
EncodingFilter.java
public class EncodingFilter implements Filter {
private String encoding;
/**
* 初始化操作,只执行一次
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String encoding = filterConfig.getInitParameter("encoding"); // 从 web.xml中读取该文件的初始化参数
if (encoding == null) {
encoding = "utf-8";
}
}
/**
* 完成过滤操作,(过滤范围内的)每次请求响应都执行
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 请求到达之前的【预处理操作】
servletRequest.setCharacterEncoding(encoding);
// 将请求传递给下一个过滤器,或者目标资源
filterChain.doFilter(servletRequest, servletResponse);
// 响应离开目标资源之后的后处理操作
}
/**
* 销毁操作,只执行一次
*/
@Override
public void destroy() {
}
}
web.xml文件
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.lwclick.filter.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/servlet/*</url-pattern>
</filter-mapping>
7.2 权限验证
AuthFilter.java
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
String queryString = request.getQueryString();
boolean flag1 = false;
boolean flag2 = false;
if (queryString != null) {
flag1 = queryString.contains("method=login");
flag2 = queryString.contains("method=register");
}
// 如果不是要排除在外的资源,进行验证(login.jsp、register.jsp、servlet/UserServlet?method=login、验证码等)
if (!requestURI.contains("login.jsp") || !requestURI.contains("register.jsp")
|| !flag1 || !flag2) {
// 1. 预处理程序
User user = (User) request.getSession().getAttribute("user");
if (user == null) {
// 未登录,转到登录页面
response.sendRedirect(request.getContextPath() + "/login/login.jsp");
return;
}
}
// 2. 转到下一个过滤器或者目标资源
filterChain.doFilter(request, response);
}
}
web.xml
<filter>
<filter-name>AuthFilter</filter-name>
<filter-class>com.lwclick.filter.AuthFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern>/servlet/*</url-pattern>
<url-pattern>*.jsp</url-pattern> <!-- 过滤jsp -->
</filter-mapping>
7.3 更多原理
多个过滤器,执行顺序怎么确定呢?
- 最直观的就是在 web.xml中,谁的 filter-mapping 在前,谁先执行
每个请求和响应都要经过过滤器嘛?
- 不是;只有请求路径和 filter-mapping 相匹配的请求
请求和响应时是不是分别将过滤器代码从头到尾执行一遍?
- 不是;请求时执行预处理操作,响应时执行后处理操作
在过滤器中能否跳转到项目的其他任意资源?
- 可以;如果一个过滤器是进行权限验证,没有登录,就不让访问目标资源,直接跳转到login.jsp
重定向和转发是否经过过滤器?
-
重定向经过
-
转发默认不经过,因为是服务器端跳转。可以通过配置解决(在web.xml的 filter-mapping 中配置)
<filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/servlet/*</url-pattern> <url-pattern>*.jsp</url-pattern> <dispatcher>REQUEST</dispatcher> <!-- 请求经过过滤器(默认) --> <dispatcher>FORWARD</dispatcher> <!-- 配置【转发】也经过过滤器 --> </filter-mapping>
8. 监听器
8.1 作用及分类
作用:
-
监听Servlet容器中的事件。事件发生后,容器激活监听器,执行预定的操作
-
监听的事件源分别为SerlvetConext、HttpSession和ServletRequest这三个域对象
对应了JSP内建对象application、session、request对象上发生的事件
-
可以在不修改现有系统基础上,增加web应用程序生命周期事件的跟踪
分类:
- 按监听的对象划分:servlet2.4规范定义的事件有三种
- 监听应用程序环境对象 ServletContext 的(2个)
- 监听用户会话对象 HttpSession 的(4个)
- 监听请求消息对象(ServletRequest)的(2个)
按监听的 *事件类项* 划分
-
监听域对象自身的创建和销毁的(3个) ---- 使用时必须在web.xm中注册
-
ServletRequestListener
-
HttpSessionListener
-
ServletContextListener
-
均有两个方法(以ServletRequestListener举例)
requestDestroyed(ServletRequestEvent sre)
对象【销毁】时requestInitialized(ServletRequestEvent sre)
对象【初始化】时<listener> <listener-class>com.lwclick.listener.MyListener</listener-class> </listener>
-
-
监听域对象中的属性的增加和删除的(3个) ---- 使用时必须在web.xm中注册
-
ServletRequestAttributeListener
-
HttpSessionAttributeListener
-
ServletContextAttributeListener
-
均有三个方法(以ServletRequestListener举例)
attributeAdded(ServletRequestAttributeEvent srae)
setAttribute(‘error’, msg) 时attributeReplaced(ServletRequestAttributeEvent srae)
setAttribute(‘error’, msg12) 同一个key,不同value时attributeRemoved(ServletRequestAttributeEvent srae)
removeAttribute(‘error’) 时
-
-
监听绑定到HttpSession域中的某个对象的状态的(2个) ---- 不需要在web.xml中注册,但是需要相应的类实现对应接口
-
添加固定内容时(比如添加 setAttribute(“user”, user) 时 ),才触发事件
需要 User 类实现 HttpSessionBindingListener 接口
-
对象序列化到硬盘,或者反序列化到内存时,触发事件
需要实现 HttpSessionActivationListener 及 Serializable 接口
public class SysUser implements Serializable, HttpSessionBindingListener, HttpSessionActivationListener { @Override public void valueBound(HttpSessionBindingEvent event) { System.out.println("----------- 绑定时 setAttribute ---------------"); } @Override public void valueUnbound(HttpSessionBindingEvent event) { System.out.println("----------- 解绑时 ---------------"); } @Override public void sessionWillPassivate(HttpSessionEvent se) { System.out.println("------------ 钝化:序列化到硬盘上 ------------"); } @Override public void sessionDidActivate(HttpSessionEvent se) { System.out.println("------------- 活化:反序列化到内存中 ------------"); } }
-
8.2 监听用户请求
记录每个请求的时间、客户端IP、URL地址
LogListener.java
public class LogListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
PrintWriter printWriter = null;
try {
printWriter = new PrintWriter(new FileWriter("e:/sys.log"), true);
// 时间
Date date = new Date();
// 客户端IP
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
String ip = request.getRemoteAddr();
// URL地址
String url = request.getRequestURL().toString();
String queryString = request.getQueryString();
if (queryString != null) {
printWriter.println(date + "\t" + ip + "\t" + url + "?" + queryString);
} else {
printWriter.println(date + "\t" + ip + "\t" + url);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (printWriter != null) {
printWriter.close();
}
}
}
}
web.xml
<listener>
<listener-class>com.lwclick.listener.LogListener</listener-class>
</listener>
8.3 使用场合举例
- 统计网站访问人数:只监听session开始
- 在线用户检测:监听session开始和session结束
- 网站登录用户人数:使用感知型监听器HttpSessionBindingListener监听用户对象放入session
- 访问次数计数器:监听用户请求(因为图片等也进行请求,需要排除)
- Web服务器的启动和停止:监听SerlvetConext的开始和结束
- 会话结束后,下次打开购物车信息还在:使用感知型监听器监听session开始和结束
- 项目启动时执行某些初始化操作(创建工厂和创建连接池):监听SerlvetConext的开始