当前位置: 代码迷 >> 综合 >> 【JavaLearn】#(24)Session、Cookie、ServletContext、MVC开发模式、JSP九大内建对象及四个作用域、JSTL及EL表达式、过滤器、监听器
  详细解决方案

【JavaLearn】#(24)Session、Cookie、ServletContext、MVC开发模式、JSP九大内建对象及四个作用域、JSTL及EL表达式、过滤器、监听器

热度:85   发布时间:2023-12-11 16:29:11.0

1. session和cookie

1.1 session和cookie原理

HTTP协议是无状态的协议,客户每次读取 web 页面,服务器都会打开新的连接,而且服务器也不会自动维护客户的上下文信息。

如何在多次请求之间共享信息呢? 服务器端如何判断一个分时段的连接是不是属于同一个客户呢?

Session 和 Cookie 就是为解决 HTTP协议的无状态采用的两种解决方案

  • Cookie:将信息保存在客户端解决
  • Session:将信息保存在服务器端解决

image-20220410113540525

1.2 cookie—实现10天免登录

重定向:可以拿到 cookie(返回到页面再跳转,所以可以拿到)

转发:拿不到 cookie(服务器内部转发,页面拿不到)

image-20220410133234206

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判断属于哪个会话

image-20220410135236582

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>&nbsp;</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文件

image-20220416151022567

tomcat 的配置文件 web.xml

image-20220416152049058

上图中的 JspServlet文件中的 service方法,最重要的两句话(在地址栏输入 xxxx.jsp后怎么转译、编译、解释执行的过程)

image-20220416152500681

5.2 九大内建对象

image-20220416152909231

  • response

    当服务器创建request对象时会同时创建用于响应这个客户端的response对象,程序员可往里放东西

  • out

    JspWriter 类的实例,不是PrinterWriter(在servlet.java中的out是)的实例

    JspWriter 增加了一些处理缓存的方法,并且会抛出 IOException 异常

  • pageContext

    用来代表整个JSP页面

    image-20220416154732284

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组件,它可以截取客户端和服务器目标资源之间的请求和响应信息,并对这些信息进行处理

过滤器是请求到达一个目标资源前的预处理程序,和/或响应离开目标资源后的后处理程序

应用:编码转化、数据过滤和替换、身份验证、数据加密、数据压缩、日志记录等

image-20220416215002605

过滤器不仅要定义,也要在 web.xml 中配置,使用的是 职责链模式

image-20220416215036637

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容器中的事件。事件发生后,容器激活监听器,执行预定的操作

  • 监听的事件源分别为SerlvetConextHttpSessionServletRequest这三个域对象

    对应了JSP内建对象applicationsessionrequest对象上发生的事件

  • 可以在不修改现有系统基础上,增加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 接口

    • 对象序列化到硬盘,或者反序列化到内存时,触发事件

      需要实现 HttpSessionActivationListenerSerializable 接口

      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的开始
  相关解决方案