开始时间: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