Javaweb122-132:JavaWeb(五)

开始时间:2021-07-12

监听器接口

  • 一组来自于servlet规范下接口,共有8个接口。在Tomcat存在servlet-api.jar包
  • 监听器接口需要由开发人员亲自实现,Ettp服务器提供jar包并没有对应的实现类
  • 监听器接口用于监控【作用域对象生命周期变化时刻】以及【作用域对象共享数据变化时刻】

在servlet规范中,认为在服务端内存中可以在某些条件下为两个servlet之间提供数据共享方案的对象,被称为【作用域对象】

servletcontext全局作用域对象
Httpsession会话作用域对象
HttpservletRequest请求作用域对象

注意 Cookie不属于作用域对象。Cookie存在浏览器内存或者是硬盘中。

监听器接口实现类开发规范

  • 根据监听的实际情况,选择对应监听器接口进行实现
  • 重写监听器接口声明【监听事件处理方法】
  • 在web.xml文件将监听器接口实现类注册到Http服务器

ServletContextListener接口

检测初始化和结束

配置监听器

package com.example.ServletContextListener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class OneListener implements ServletContextListener {
    @Override
    //初始化时运行
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("context has been initialized");
    }

    @Override
    //关闭服务器时运行
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("context has been destroyed");
    }
}

在web.xml配置listener

    <listener>
        <listener-class>com.example.ServletContextListener.OneListener</listener-class>
    </listener>

Tomcat服务器加载和结束时,运行两个方法

[2021-07-14 10:17:05,444] Artifact Gradle : com.example : ServletContextListener-1.0-SNAPSHOT.war: Artifact is being deployed, please wait...
context has been initialized

context has been destroyed

Disconnected from server

ServletContextAttributeListener接口

合法检测全局作用变量域共享数据变化时刻

写一个Servlet用来初始化共享对象及对对象进行处理

package com.example.MyServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class OneServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //新建一个ServletContext对象
        ServletContext application = req.getServletContext();
        //新增共享数据
        application.setAttribute("key1", 100);
        //更新共享数据
        application.setAttribute("key1", 200);
        //删除共享数据
        application.removeAttribute("key1");
    }
}

监听器用来监听共享数据的情况

package com.example.ServletContextAttributeListener;

import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;

public class OneListener implements ServletContextAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent event) {
        System.out.println("Data Add Success");
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent event) {
        System.out.println("Data Remove Success");
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent event) {
        System.out.println("Data Update Success");
    }
}

xml写好配置文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <listener>
        <listener-class>com.example.ServletContextAttributeListener.OneListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>OneServlet</servlet-name>
        <servlet-class>com.example.MyServlet.OneServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>OneServlet</servlet-name>
        <url-pattern>/one</url-pattern>
    </servlet-mapping>
</web-app>

通过debug逐步执行,可以观测到数据被监听的几个过程

Data Add Success
Data Update Success
Data Remove Success

测试了一下之前考试系统中代码useradd的速度

Date start = new Date();
        result = dao.add(user);
        Date end = new Date();
        System.out.println("It takes " + (end.getTime() - start.getTime()) + "ms");

平均下来5ms

时间主要是耗在了DAO上面

尤其是JDBC中Connection的创建和销毁最花时间

但不去创建Connection,PreparedStatement又没有办法做了

那既然主要时间是创建销毁上,不如一下创建很多个,最后统一销毁

创建一个监听器
然后重写监听器里面的两个方法,在服务器的开始和关闭时调用

package listener;

import Util.JDBCUtil;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class OneListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        JDBCUtil jdbcUtil = new JDBCUtil();
        Map map = new HashMap();
        for (int i = 0; i < 20; i++) {
            Connection con = jdbcUtil.createCon();
            System.out.println("Http Server Starts,create Connection:" + con);
            //value值设置为true,表示通道是空闲状态,false为使用状态
            map.put(con, true);
        }
        ServletContext application = sce.getServletContext();
        //存数据,设置key值为key1
        application.setAttribute("key1", map);
    }


    @Override
    //关闭服务器,销毁20个Connection
    public void contextDestroyed(ServletContextEvent sce) {
        ServletContext application = sce.getServletContext();
        //取数据,通过key来得到value值
        Map map = (Map) application.getAttribute("key1");
        //加一个迭代器用于处理map中的对象
        //map.keySet()获取Map集合的所有键名
        Iterator it = map.keySet().iterator();
        while (it.hasNext()) {
            Connection con = (Connection) it.next();
            if (con != null) {
                System.out.println("Destroy Connection:" + con);
            }
        }
    }
}

配置文件中配置一下

    <listener>
        <listener-class>listener.OneListener</listener-class>
    </listener>

启动

Http Server Starts,create Connection:com.mysql.cj.jdbc.ConnectionImpl@511a4d41

关闭

Destroy Connection:com.mysql.cj.jdbc.ConnectionImpl@100aaef4

重载UserDAO里面的方法,增加相应部分的request

package dao;

import Entity.Users;
import Util.JDBCUtil;

import javax.servlet.http.HttpServletRequest;
import javax.xml.registry.infomodel.User;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class UserDao {
    private JDBCUtil jdbcUtil = new JDBCUtil();

    //用户注册
    public int add(Users user) {
        String sql = "insert into myusers(UserName,Password,sex,email)" + "values(?,?,?,?)";
        PreparedStatement ps = jdbcUtil.createPs(sql);
        int result = 0;
        try {
            ps.setString(1, user.getUserName());
            ps.setString(2, user.getPassword());
            ps.setString(3, user.getSex());
            ps.setString(4, user.getEmail());
            result = ps.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            jdbcUtil.close();
        }
        return result;
    }

    //添加request参数
    public int add(Users user, HttpServletRequest request) {
        String sql = "insert into myusers(UserName,Password,sex,email)" + "values(?,?,?,?)";
        PreparedStatement ps = jdbcUtil.createPs(sql, request);
        int result = 0;
        try {
            ps.setString(1, user.getUserName());
            ps.setString(2, user.getPassword());
            ps.setString(3, user.getSex());
            ps.setString(4, user.getEmail());
            result = ps.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            jdbcUtil.close(request);
        }
        return result;
    }

重载util里面的内容

package Util;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.sql.*;
import java.util.Iterator;
import java.util.Map;

public class JDBCUtil {
    final String url = "jdbc:mysql://localhost:3306/mysql";
    final String user = "root";
    final String password = "333";
    private Connection con = null;
    private PreparedStatement ps = null;

    //注册Driver,在第一次调用工具类时就加载
    static {
        Driver driver = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //创建connection
    public Connection createCon() {
        try {
            con = DriverManager.getConnection(url, user, password);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return con;
    }

    //重载一个创建connection
    public Connection createCon(HttpServletRequest request) {
        //通过请求对象,得到全局作用域对象
        ServletContext application = request.getServletContext();
        //从全局作用域对象中得到map
        Map map = (Map) application.getAttribute("key1");
        //拿到处于空闲状态的Connection
        Iterator it = map.keySet().iterator();
        while (it.hasNext()) {
            con = (Connection) it.next();
            boolean flag = (boolean) map.get(con);
            if (flag == true) {
                map.put(con, false);
                break;
            }
        }
        return con;
    }

    //2.获取连接
    //封装PreparedStatement对象
    public PreparedStatement createPs(String sql) {
        try {
            Connection con = createCon();
            ps = con.prepareStatement(sql);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return ps;
    }

    //继续重载createPS方法
    public PreparedStatement createPs(String sql, HttpServletRequest request) {
        try {
            //此时调用创建con需要调用有参的createCon
            Connection con = createCon(request);
            ps = con.prepareStatement(sql);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return ps;
    }

    public void close() {

        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    //对close再进行重载
    public void close(HttpServletRequest request) {

        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        ServletContext application = request.getServletContext();
        Map map = (Map) application.getAttribute("key1");
        map.put(con, true);
    }

    public void close(Connection con) {

        close();

        if (con != null) {
            try {
                con.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

    public void close(ResultSet rs) {

        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        close();
    }
}

修改一下UserAddServlet里的内容

    Date start = new Date();
        result = dao.add(user, request);
        Date end = new Date();

此时运行,观察结果

It takes 3ms
It takes 3ms
It takes 2ms
It takes 2ms

加载速度有了提升

相当于自己写了一个连接池

过滤器接口

Python中也有一个filter

Filter接口

介绍:

1)来自于servlet规范下接口,在Tomcat中存在于servlet-api.jar包(因为叫Filter的很多)
2) Filter接口实现类由开发人员负责提供,Http服务器不负责提供
3)Filter接口在Http服务器调用资源文件之前,对Http服务器进行拦截

具体作用:

1)拦截Http服务器,帮助Http服务器检测当前请求合法性
2)拦截Http服务器,对当前请求进行增强操作

Filter接口实现类开发步骤:三步

1)创建一个Java类实现Filter接口
2)重写Filter接口中doFilter方法
3)在web.xml中注册

首先看看测试合法性

package filter;

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

public class OneFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String age = request.getParameter("age");
        if (Integer.valueOf(age) < 40) {
            //判断正确,允许通过,将得到的信息归还,
            chain.doFilter(request, response);
        } else {
            response.setContentType("text/html;charset=utf-8");
            PrintWriter out = response.getWriter();
            out.print("<center><font style='color:red;font-size=40px'>this request is refused</font></center>");

        }
    }
}

    <filter>
        <filter-name>OneFilter</filter-name>
        <filter-class>filter.OneFilter</filter-class>
    </filter>
    <!--调用图片的时候开始拦截-->
    <filter-mapping>
        <filter-name>OneFilter</filter-name>
        <url-pattern>/Vae.jpg</url-pattern>
    </filter-mapping>

通过在地址栏中加上age参数,来测试过滤器的作用
在这里插入图片描述

在这里插入图片描述

再来看看对拦截的请求进行增强处理

package com.example.FilterDemo;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class OneServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //读取请求参数
        String userName = req.getParameter("userName");
        System.out.println("OneServlet gets Parameter userName:" + userName);
    }
}

package filter;

import javax.servlet.*;
import java.io.IOException;

public class TwoFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //通知请求对象,实现增强UTF-8的功能
        request.setCharacterEncoding("utf-8");
        //把请求对象和响应对象放行
        chain.doFilter(request, response);
    }
}

    <filter>
        <filter-name>TwoFilter</filter-name>
        <filter-class>filter.TwoFilter</filter-class>
    </filter>
    <!--调用所有文件的时候都拦截-->
    <filter-mapping>
        <filter-name>TwoFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

其中

       <url-pattern>/*</url-pattern>

控制拦截地址
在这里插入图片描述

在这里插入图片描述

调用任意文件夹下的jpg格式

       <url-pattern>*.jpg</url-pattern>

注意这里没有斜线打头了

防止用户恶意登录

我们网站的内部逻辑,是通过输入正确的账号密码,跳转到对应的界面。但如果直接从网址里面输入想要进入的界面,也一样可以进入。这是很危险的,所以要考虑到防止用户恶意登录

我们以查看 用户信息查询 为例
如图所示,当加了防止恶意登录的代码后,即使跳过登录验证进入了主界面,在查询信息时依然会再次让你登录。
当然,如果之前是正常登录进来的,那么不需要再次验证了
在这里插入图片描述

本质上是在进入页面时,判断是否携带了HttpSession信息,如果有,则正常进入,如果没有则不行
登录成功时携带上HttpSession

package Controller;

public class UserLoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//占空
        if (result == 1) {
            HttpSession session = req.getSession();
            resp.sendRedirect("/TestSystem/Navicat.html");
        } else {
            resp.sendRedirect("/TestSystem/user_loginError.html");
        }
    }
}

查询时判断Session

public class UserFindServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        UserDao userDao = new UserDao();
        PrintWriter out = resp.getWriter();
        //如果当前没有Session,则 getSession(false)返回null
        HttpSession session = req.getSession(false);
        if (session == null) {
            //拒绝服务
            System.out.println("Login fails, please use account and password");
            resp.sendRedirect("/TestSystem/user_login.html");
            return;
        } else {
            //占空
        }
    }
}

但该种方法有缺陷
每个Servlet都加上Session,那么会增大工作量
并且新增一个Servlet,都会再重复这段代码进行判断。
同时,只能对动态文件进行保护,而不能对静态文件进行保护

解决方案就是使用过滤器
在doFilter中进行增强

package filter;

import Entity.Users;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.List;

public class OneFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //向下转型,父类接口的转为子类的
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        HttpSession session = req.getSession(false);
        if (session == null) {
            //拒绝服务
            System.out.println("Login fails, please use account and password");
            req.getRequestDispatcher("/TestSystem/user_login.html").forward(request, response);
            return;
        } else {
            //提供服务
            chain.doFilter(request, response);
        }
    }
}

问题依然来了,像我直接想进入登录页面,同样会被拦截。

那么需要进行修改
将所有名称带有Login/login直接通过,其他的进行拦截判断

package filter;

public class OneFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //向下转型,父类接口的转为子类的
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        String uri = req.getRequestURI();
        //判断"login"或者是"Login"是否是uri的子字符串;默认界面也要考虑其中,因为会跳转到login界面
        if (uri.indexOf("login") != -1 ||uri.indexOf("Login") != -1|| "/TestSystem/".equals(uri)) {
            chain.doFilter(request, response);
            return;
        }
        //得到其他文件,则需要参与判断
        HttpSession session = req.getSession(false);
        if (session != null) {
            //提供服务
            chain.doFilter(request, response);
            return;
        }
        //拒绝服务
        System.out.println("Login fails, please use account and password");
        req.getRequestDispatcher("/user_loginError.html").forward(request, response);
        return;
    }
}

互联网通信流程图更新

浏览器端控制请求三要素
请求地址/请求方式/请求参数(超链接/表单域)

请求协议包中包含
请求行/请求头[get]/空白行/请求体[post]
生成请求对象和响应对象

传送到Http服务器操作静态资源文件(内容固定/只能在浏览器执行)
动态资源文件(Servlet接口实现类)[创建实例对象,调用重写的方法,将响应对象结果写入响应体中]

多个Servlet调用规则
重定向/请求转发

多个Servlet数据共享
ServletContext/Cookie/HttpSession/HttpServletRequest

响应协议包
状态行/响应头/空白行/响应体
结束时间:2021-07-16

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值