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 {
@Overrideprotected 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天免登录功能 ====================================// 创建一个 cookieCookie 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 {
// 去掉 cookieusernameCookie.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. 拿出所有的 cookieCookie[] cookies = request.getCookies();// 2. 找到需要的 cookieString 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 {
@Overrideprotected 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 {
@Overrideprotected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 立即结束当前的 sessionreq.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 {
@Overrideprotected 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 {
@Overridepublic 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;}@Overridepublic 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 {
@Overrideprotected 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 {
@Overrideprotected 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;}@Overridepublic 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 {
@Overridepublic 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*/@Overrideprotected 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 { @Overrideprotected 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页面中的小脚本,声明,表达式等,全部是在服务器端执行的。
JSTL的简单使用
声明变量 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的有无,动态拼接 sqlStringBuilder sql = new StringBuilder("select * from user where 1=1"); // 使用 1=1,后面统一跟 andif (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*/@Overridepublic 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*/@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 请求到达之前的【预处理操作】servletRequest.setCharacterEncoding(encoding);// 将请求传递给下一个过滤器,或者目标资源filterChain.doFilter(servletRequest, servletResponse);// 响应离开目标资源之后的后处理操作}/*** 销毁操作,只执行一次*/@Overridepublic 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 {
@Overridepublic 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 { @Overridepublic void valueBound(HttpSessionBindingEvent event) { System.out.println("----------- 绑定时 setAttribute ---------------");}@Overridepublic void valueUnbound(HttpSessionBindingEvent event) { System.out.println("----------- 解绑时 ---------------");}@Overridepublic void sessionWillPassivate(HttpSessionEvent se) { System.out.println("------------ 钝化:序列化到硬盘上 ------------");}@Overridepublic void sessionDidActivate(HttpSessionEvent se) { System.out.println("------------- 活化:反序列化到内存中 ------------");} }
-
8.2 监听用户请求
记录每个请求的时间、客户端IP、URL地址
LogListener.java
public class LogListener implements ServletRequestListener {
@Overridepublic void requestDestroyed(ServletRequestEvent sre) {
}@Overridepublic void requestInitialized(ServletRequestEvent sre) {
PrintWriter printWriter = null;try {
printWriter = new PrintWriter(new FileWriter("e:/sys.log"), true);// 时间Date date = new Date();// 客户端IPHttpServletRequest 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的开始