监视器与拦截器
十七、Servlet规范扩展----监听器接口
1.介绍:
1)一组来自于Servlet规范下接口,共有8个接口。在Tomcat存在servlet-api.jar包
2)监听器接口需要由开发人员亲自实现,Http服务器提供jar包并没有对应的实现类
3)监听器接口用于监控【作用域对象生命周期变化时刻】以及【作用域对象共享数据变化时 刻】
2.作用域对象:
1)在Servlet规范中,认为在服务端内存中可以在某些条件下为两个Servlet之间提供
数据共享方案的对象,被称为【作用域对象】
2)Servlet规范下作用域对象:
ServletContext: 全局作用域对象
HttpSession : 会话作用域对象
HttpServletRequest:请求作用域对象
3.监听器接口实现类开发规范:三步
1)根据监听的实际情况,选择对应监听器接口进行实现
2)重写监听器接口声明【监听事件处理方法】
3)在web.xml文件将监听器接口实现类注册到Http服务器
4.ServletContextListener接口:
1)作用:通过这个接口合法的检测全局作用域对象被初始化时刻以及被销毁时刻
2)监听事件处理方法:
public void contextInitlized() :在全局作用域对象被Http服务器初始化被调用
public void contextDestory(): 在全局作用域对象被Http服务器销毁时候触发调用
5.ServletContextAttributeListener接口:
1)作用:通过这个接口合法的检测全局作用域对象共享数据变化时刻
2)监听事件处理方法:
public void contextAdd():在全局作用域对象添加共享数据
public void contextReplaced():在全局作用域对象更新共享数据
public void contextRemove():在全局作用域对象删除共享数据
6.全局作用域对象共享数据变化时刻
ServletContext application = request.getServletContext();
application.setAttribute("key1",100); //新增共享数据
application.setAttribute("key1",200); //更新共享数据
application.removeAttribute("key1"); //删除共享数据
7.利用监听器接口来加快数据库的CURD
原理:普通的封装DAO类中,没做一次数据操作,都需要对封装的JDBC中的Connection进行一 次创建和销毁,这回及其浪费时间。
解决办法:利用监听器在服务器开始运行时,创建20个全局的Connection的对象,供程序中所有 的数据操作去使用,直到服务器关闭,统一创建统一销毁。
-
1.创建监听器类,实现ServletContextListener接口
/** * Created by IntelliJ IDEA. * User: LvHaoIT (asus) * Date: 2021/5/7 * Time: 15:00 */ public class OneListener implements ServletContextListener { //在tomcat启动时,创建20个connection,在useradd方法执行时候,将 //connection对象交给add方法使用 @Override public void contextInitialized(ServletContextEvent sce) { JdbcUtil util = new JdbcUtil(); Map map = new HashMap<>(); for (int i = 1; i <= 20; i++) { Connection con = util.createCon(); System.out.println("在服务器启动时,创建出Connection对象 " + con + "(" + i + ")"); map.put(con, true);//true表示这个通道处于空闲状态,false通道正在被使用 } //为了在http服务器运行期间,一直都可以使用20个Connection,所以将connection保存到全局作用域对象中 ServletContext application = sce.getServletContext();//获取全局作用域对象 application.setAttribute("key1", map);//放入全局变量中 } /** * 在http服务器关闭的时刻,我们需要将这20个connection进行销毁 * * @param sce */ @Override public void contextDestroyed(ServletContextEvent sce) { ServletContext application = sce.getServletContext(); Map map = (Map) application.getAttribute("key1"); Iterator it = map.keySet().iterator();//将map中数据放入set集合,然后利用迭代器进行一个排序 //遍历迭代器 /** * 1、hasNext() :此方法用来判断迭代器对象指向的索引位置有没有元素 * 2、next() :获取迭代器对象当前索引位置的元素并将索引下标移至下一个元素 * 3、remove() :删除参数中指定元素 */ while (it.hasNext()) { Connection con = (Connection) it.next(); if (con != null) { try { System.out.println("Connection对象" + con + "准备被销毁"); con.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } } }
-
2.为了执行程序开发的开闭原则,我们将重载其中的多个方法
JdbcUtil工具类中
//--------------------通过全局作用域对线得到Connetion---------------start public Connection createCon(HttpServletRequest request) { //1.通过请求对象来获得全局作用域对象 ServletContext application = request.getServletContext(); //2.获得对象集合 Map map = (Map) application.getAttribute("key1"); //3.从map中获得一个处于空闲状态的Connection Iterator it = map.keySet().iterator(); while (it.hasNext()) { conn = (Connection) it.next(); if ((boolean) map.get(conn)) { //判断是否可用 map.put(conn, false);//需要用的进行关闭 break; } } return conn; } //--------------------通过全局作用域对线得到Connetion---------------end //----------------====重载运行ps--------------------------------Statr public PreparedStatement createStatement(HttpServletRequest request, String sql) { try { ps = createCon(request).prepareStatement(sql); } catch (SQLException throwables) { throwables.printStackTrace(); } return ps; } //---------------------重载--------------------------- //------------------重载close方法----------------------Srart public void close(HttpServletRequest request) { try { if (ps != null) ps.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } ServletContext application = request.getServletContext();//获得全局 Map map = (Map) application.getAttribute("key1"); map.put(conn, true);//表明又可以使用了 } //------------------重载close方法----------------------end
-
3.进入UserDao类中,继续重载其中的add方法
//------------------------------重载add public int add(Users user, HttpServletRequest request) { int result = 0; String sql = "insert into users(userName,password,sex,email) values(?,?,?,?)"; //编译sql框架 PreparedStatement ps = this.util.createStatement(request, sql); //插入值 try { ps.setString(1, user.getUserName()); ps.setString(2, user.getPassword()); ps.setString(3, user.getSex()); ps.setString(4, user.getEmail()); //执行sql语句 result = ps.executeUpdate(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { this.util.close(request); } return result; } //-------------------------------------------
-
4.最后进入UserAddServlet中,修改对应代码,去调用重载后的方法
new UserDao().add(new Users(userName, password, sex, email), request); //将请求对象request传过去,用于获取全局作用域对象
这样就可以大幅减少CURD的操作时间。
十八、Servlet规范扩展----Filter接口(过滤器接口)
1.介绍:
1)来自于Servlet规范下接口,在Tomcat中存在于servlet-api.jar包
2)Filter接口实现类由开发人员负责提供,Http服务器不负责提供
3)Filter接口在Http服务器调用资源文件之前,对Http服务器进行拦截
2.具体作用:
1)拦截Http服务器,帮助Http服务器检测当前请求合法性
2)拦截Http服务器,对当前请求进行增强操作
3.Filter接口实现类开发步骤:三步
1)创建一个Java类实现Filter接口
2)重写Filter接口中doFilter方法
3)web.xml将过滤器接口实现类注册到Http服务器
4.Filter拦截地址格式
1) 命令格式:
<filter-mapping>
<filter-name>oneFilter</filter-name>
<url-pattern>拦截地址</url-pattern>
</filter-mapping>
2) 命令作用:
拦截地址通知Tomcat在调用何种资源文件之前需要调用OneFilter过滤进行拦截
3)要求Tomcat在调用某一个具体文件之前,来调用OneFilter拦截
<url-pattern>/img/mm.jpg</url-pattern>
4)要求Tomcat在调用某一个文件夹下所有的资源文件之前,来调用OneFilter拦截
<url-pattern>/img/*</url-pattern>
5)要求Tomcat在调用任意文件夹下某种类型文件之前,来调用OneFilter拦截
<url-pattern>*.jpg</url-pattern>
6)要求Tomcat在调用网站中任意文件时,来调用OneFilter拦截
<url-pattern>/*</url-pattern>
使用实例
1.利用拦截器加强方法,设置所有字符集为utf-8
拦截器TwoFilter
public class TwoFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
filterChain.doFilter(servletRequest, servletResponse);
}
}
web.xml
<filter>
<filter-name>twoFilter</filter-name>
<filter-class>com.filter.TwoFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>twoFilter</filter-name>
<url-pattern>/*</url-pattern><!--通知tomcat在调用所有资源文件之前都需要调用twoFilter-->
</filter-mapping>
这可以使浏览器请求每一个资源时,都把请求对象的字符集改为utf-8(不会乱码)
2.恶意登录(直接访问资源)
第一种方法:令牌方式
原理图:
-
通过在登录时为用户开辟一块session,后续访问资源时,判断这个用户是否存在sessio
-
//首先在loginServlet里面判断登录成功后纷发令牌 if (flag == 1) { //用户存在,合法用户,纷发令牌 HttpSession session = request.getSession(); response.sendRedirect("/demo1/index.html"); } else { response.sendRedirect("/demo1/login_error.html"); } //然后在后续需要验证的Servlet里利用 request.getSession(false) == null //判断是否拥有令牌,没有则不提供服务
这种方式的缺点:
1.增加开发难度(因为不止一个资源文件需要防止恶意访问)
2.不能保护静态资源文件
第二种方法:利用过滤器
原理图:
-
通过使用拦截器,对每一个请求资源都进行拦截判断,除去默认请求与登录请求和正常登录之外,所有的非法操作都无法通过。
-
OneFilter拦截器
-
package com.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * Created by IntelliJ IDEA. * User: LvHaoIT (asus) * Date: 2021/5/7 * Time: 22:59 */ public class OneFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //ServletRequest下没有getSession方法,这个方法在它的子接口中,(向下转型) HttpServletRequest request = (HttpServletRequest) servletRequest; //调用请求对象读取请求包中URI,了解用户访问的资源文件是谁 String URI = request.getRequestURI();//[/网站名称/资源名称] //如果本次请求文件与用户登录相关,或者是默认请求 则无条件放行 if (URI.indexOf("login") != -1 || "/demo1/".equals(URI)) { //indexof,查找字符串在其中出现的第一位置,未找到则返回-1 filterChain.doFilter(servletRequest, servletResponse); return; } // HttpServletResponse response = (HttpServletResponse) servletResponse; if (request.getSession(false) == null) { //用户不合法,直接送走(重定向) request.getRequestDispatcher("/login_error.html").forward(servletRequest, servletResponse); return; } else { filterChain.doFilter(servletRequest, servletResponse); return; } } }
-
web.xml中的配置
<!--登录拦截器--> <filter> <filter-name>oneFilter</filter-name> <filter-class>com.filter.OneFilter</filter-class> </filter> <filter-mapping> <filter-name>oneFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
这种方式解决了上一种令牌方式的所有缺点!
(Servlet完)