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

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

image-20220410135236582

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>&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 {
    @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文件

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

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

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

image-20220416215002605

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

image-20220416215036637

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

  • 监听的事件源分别为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 {
          @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的开始
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LRcoding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值