欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

粤嵌打卡第46天(javaweb之过滤器(登陆验证)监听器(在线人数))

程序员文章站 2022-05-23 08:46:36
...

今天我们来写个平常在项目中常用的过滤器和监听器,方便大家平常在项目中使用。

1、过滤器

过滤器:实现了javax.servlet.Filter接口的类

原理:过滤器可以拦截客户端的请求、客户端的响应,可以对请求和响应进行处理。

执行流程:请求<->servlet容器<->过滤器1<->过滤器2<->过滤器n<->servlet容器<->servlet
粤嵌打卡第46天(javaweb之过滤器(登陆验证)监听器(在线人数))
Filter接口中重要的三个方法

粤嵌打卡第46天(javaweb之过滤器(登陆验证)监听器(在线人数))
执行过程:

web程序启动时,servlet会根据web.xml中的配置文件创建每个过滤器实例,将根据配置对URL中的请求进行过滤

配置过滤器时,可以将其映射到URL路径模式和```Servlet名称

web.xml中配置filter写法如下:

  <filter>
  	<filter-name>characterFilter</filter-name>
 	<filter-class>com.yueqian.store.filter.characterFilter</filter-class> 
  </filter>
  <!-- 过滤器执行的顺序和Filter-mapping顺序有关,可以设置多个dispatcher,表示哪些请求都需要过滤,默认为REQUEST为对客户端得请求进行过滤 -->
  <filter-mapping>
  	<filter-name>characterFilter</filter-name>
  	<url-pattern>/*</url-pattern>
  	<servlet-name>userServlet</servlet-name>
  	<dispatcher>REQUEST</dispatcher>
  </filter-mapping>
  • 注意:1、配置URL路径模式映射,取值通常有四种模式/**.do/xxx/*/xxx/*.do

    2、url映射的优先级比servlet名称映射的过滤器高,但两者只能出现一个。

过滤器的应用

  • 1、设置字符编码
package com.yueqian.store.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebServlet;

@WebFilter("/*")
public class characterFilter implements Filter{

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		System.out.println("Filter实例被过滤执行了。。。");
		//设置请求响应编码
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");
		chain.doFilter(request, response);//交给下面所有的过滤器链
	}

	@Override
	public void destroy() {
		System.out.println("Filter实例被销毁了。。。");
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		System.out.println("Filter实例被初始化了。。。");
	}
}
  • 2、记录日志和操作时耗
package com.yueqian.store.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

@WebFilter("/*")
public class LogFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		String remote = request.getRemoteAddr()+":"+request.getRemoteHost()+":"+request.getRemotePort();
		long start = System.currentTimeMillis();
		chain.doFilter(request, response);
		long end = System.currentTimeMillis();
		System.out.println(remote+"执行了"+(end-start)+"毫秒!");
	}

	@Override
	public void destroy() {
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}
}
  • 3、用户权限验证(重要)
package com.yueqian.store.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.yueqian.store.common.Constans;
@WebFilter("/*")  //使用注解的过滤器执行顺序是按照类名字典排序
/**
 * 对登录请求进行过滤
 * @author LinChi
 *
 */
public class LoginFilter implements Filter {

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest rep = (HttpServletRequest)request;
		HttpServletResponse resp = (HttpServletResponse)response;
		//放过所有不需要检查的资源文件(图片、样式、脚本、验证码、登陆注册界面)
		String uri = rep.getRequestURI();
		if(uri.contains("login.jsp") || uri.contains("register.jsp") || uri.contains("nologin.jsp")
				|| uri.endsWith(".jpg") || uri.endsWith(".css") || uri.endsWith(".jpeg") || uri.endsWith(".js")
				|| uri.contains("/user/login") || uri.contains("/user/logout") || uri.contains("/user/register")
				|| uri.contains("/verify")) {
			chain.doFilter(request, response);//放过这些不需要检查的资源文件
			return;
		}
		Object login = rep.getSession().getAttribute(Constans.SESSION_LOGINED_INFO);
		if(login == null) {
			resp.sendRedirect(rep.getContextPath()+"/login.jsp");
		}else {
			chain.doFilter(request, response);
		}
	}
}

2、监听器

常见的监听器有三种接口

  • 1、 ServletContextListener接口: 监听servletContext类的生命周期,当整个web应用程序开始启动到消亡。

实现对数据库连接池的开启和关闭

package com.yueqian.store.listener;

import java.util.LinkedList;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import com.yueqian.store.common.Constans;
import com.yueqian.store.common.DBUtils;
/**
 * 应用程序上下文监听器(监听Servletontext对象的创建与销毁)
 * 	监听整个服务器开启时的动作和关闭时的动作
 * @author LinChi
 *
 */
@WebListener
public class ApplicationListener implements ServletContextListener {

	@Override
	public void contextDestroyed(ServletContextEvent event) {
		System.out.println("数据库连接池关闭了。。。");
		//程序结束时关闭数据库连接池
		DBUtils.closeDataSourse();
	}

	@Override
	public void contextInitialized(ServletContextEvent event) {
		System.out.println("数据库连接池开启了。。。");
		//程序开启时初始化数据库连接池
		DBUtils.init();
		//给servletContext对象添加一个列表,用来存放登录系统用户的数量
		event.getServletContext().setAttribute(Constans.APPLICATION_LIST_ID, new LinkedList<>());
	}
	
}

数据库连接池类

package com.yueqian.store.common;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;

import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;

public class DBUtils {
	private static final String URL = "jdbc:mysql://127.0.0.1:3306/store?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false";
	private static final String USERNAME = "root";
	private static final String PASSWORD = "root";
	private static BasicDataSource ds = null;
	public static void init() {
		// 加载驱动
		try {
			// 方式一
			DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
			// 方式二 根据传入字符串形式的类名加载该类 (加载类时调用静态代码块加载驱动)
			// Class.forName("com.mysql.cj.jdbc.Driver()");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 创建数据库连接池
		ds = new BasicDataSource();
		// 设置连接信息
		ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
		ds.setUrl(URL);
		ds.setUsername(USERNAME);
		ds.setPassword(PASSWORD);

		// 设置连接池信息
		// 最大空闲连接数
		ds.setMaxIdle(30);
		// 最小空闲连接数
		ds.setMinIdle(2);
		// 设置初始连接数
		ds.setInitialSize(2);
		// 创建连接时最大等待时间
		ds.setMaxWaitMillis(4000);// 毫秒
		// 从数据源中拿到的连接,关闭其自动提交的事务
		ds.setDefaultAutoCommit(false);
	}

	// 定义连接保存在ThreadLocal类中,将当前线程对象作为key,保存连接conn对象,避免多线程访问业务层和dao层导致使用的conn连接不一致问题
	// service里绑定每个线程对象的连接,调用的dao层获取该处理线程对象的连接
	private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

	/**
	 * 连接数据库
	 * 
	 * @return
	 */
	public static Connection getConnection() {
		//先从threadLocal中获得连接,threadLocal类似map存放,ThreadLocal中是以键为当前线程对象,值为conn连接存放的。
		Connection conn = threadLocal.get();
		//如果连接为null,从连接池中获取连接
		if (conn == null) {
			try {
				conn = ds.getConnection();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			//将得到的连接设置到threadLocal中
			threadLocal.set(conn);
		}
		return conn;
	}

	/**
	 * 关闭连接
	 * 
	 * @param rs
	 * @param stmt
	 * @param conn
	 */
	public static void close(ResultSet rs, Statement stmt) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				if (stmt != null) {
					try {
						stmt.close();
					} catch (SQLException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}
	}

	// 提交事务
	public static void commit() {
		Connection conn = threadLocal.get();
		if(conn != null){
			try {
				conn.commit();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally {
				//关闭连接
				try {
					conn.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				//清理当前线程所绑定的连接
				threadLocal.remove();
			}
		}
	}

	// 回滚事务
	public static void rollback() {
		Connection conn = threadLocal.get();
		if(conn != null){
			try {
				conn.rollback();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}finally {
				//关闭连接
				try {
					conn.close();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				//清理当前线程所绑定的连接
				threadLocal.remove();
			}
		}
	}
	/**
	 * 关闭数据库连接池
	 */

	public static void closeDataSourse() {
		if(ds != null) {
			try {
				ds.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
  • 2、HttpSessionListener接口:实现监听对session对象的创建与销毁,通过session对象的创建监控访问网站的人数

实现游客访问网站人数

package com.yueqian.store.listener;

import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import com.yueqian.store.common.Constans;

/**
 * 监听session创建和销毁
 * 根据session创建的数目来统计访问系统人数的数目(不同浏览器、同一个用户登录也是在线两人,创建两个session,所以不太准确,游客)
 * 
 * @author LinChi
 *
 */
//@WebListener
public class OnLineListener implements HttpSessionListener {

	public void sessionCreated(HttpSessionEvent event) {
		// 获取上下文对象
		ServletContext context = event.getSession().getServletContext();
		// 获取上下文对象中存在的session数目
		Integer count = (Integer) context.getAttribute(Constans.APPLICATION_SESSION_ID);
		if (count == null) {
			count = 1;
		} else {
			count++;
		}
		context.setAttribute(Constans.APPLICATION_SESSION_ID, count);
	}

	/**
	 * @see HttpSessionListener#sessionDestroyed(HttpSessionEvent)
	 */
	public void sessionDestroyed(HttpSessionEvent event) {
		// 获取上下文对象
		ServletContext context = event.getSession().getServletContext();
		// 获取上下文对象中存在的session数目
		Integer count = (Integer) context.getAttribute(Constans.APPLICATION_SESSION_ID);
		if(count != null) {
			count--;
			context.setAttribute(Constans.APPLICATION_SESSION_ID, count);
		}
	}
}

前端页面监听游客访问人数:( 根据session创建的数目来统计访问系统人数的数目(不同浏览器、同一个用户登录也是在线两人,创建两个session,所以不太准确,游客)

 当前访问系统人数:<%
			Integer count = (Integer)application.getAttribute(Constans.APPLICATION_SESSION_ID);
			if(count != null){
				out.print(count);
			}
		%>人。
  • 3、HttpSessionBindingListener接口:被保存在session的实例对象实现此接口

实现不同用户在线登录人数的访问

实现原理:

1、我们把要存入session中的对象实例实现此接口。

2、用集合列表存放在线用户的数量(集合列表已经在上下文监听器中程序启动时创建)

3、判断用户是否为同一个用户,否则添加,当类对象在session中销毁时,从列表中移除该用户

package com.yueqian.store.domain;

import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

import com.yueqian.store.common.Constans;

/**
 * 实现精确实现在线人数的统计
 * 
 * @author LinChi
 *
 */
public class UserInfo implements HttpSessionBindingListener {
	private Integer userId;
	private String account;
	private String username;
	private String password;

	public UserInfo() {
		super();
		// TODO Auto-generated constructor stub
	}

	public UserInfo(Integer userId, String account, String username, String password) {
		super();
		this.userId = userId;
		this.account = account;
		this.username = username;
		this.password = password;
	}

	public Integer getUserId() {
		return userId;
	}

	public void setUserId(Integer userId) {
		this.userId = userId;
	}

	public String getAccount() {
		return account;
	}

	public void setAccount(String account) {
		this.account = account;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		return "UserInfo [userId=" + userId + ", account=" + account + ", username=" + username + ", password="
				+ password + "]";
	}

	// 当UserInfo对象被设置到session中,此方法被执行
	@Override
	public void valueBound(HttpSessionBindingEvent event) {
		// 将当前UserInfo对象的账号保存到列表中,通过列表中账号的数量来统计登录系统的人数
		ServletContext context = event.getSession().getServletContext();
		// 从应用程序监听器中获得访问用户列表
		List<String> list = (List<String>) context.getAttribute(Constans.APPLICATION_LIST_ID);
		// 当列表中不包含此用户,添加(防止同时添加同一个用户)
		if (!list.contains(this.account)) {
			list.add(this.account);
		}
	}

	// 当UserInfo对象从session对象中删除时,此方法被执行
	@Override
	public void valueUnbound(HttpSessionBindingEvent event) {
		// 将当前UserInfo对象的账号保存到列表中,通过列表中账号的数量来统计登录系统的人数
		ServletContext context = event.getSession().getServletContext();
		// 从应用程序监听器中获得访问用户列表
		List<String> list = (List<String>) context.getAttribute(Constans.APPLICATION_LIST_ID);
		//删除对象
		list.remove(this.account);
	}
}

前端页面接收统计在线人数:(不同浏览器、同一个用户登录在线一人,真正实现在线人数的监听。)

在线人数:<%
			List<String> list = (List<String>)application.getAttribute(Constans.APPLICATION_LIST_ID);
			if(list != null){
				out.print(list.size());
			}else{
				out.print("0");
			}
		%>人<br/>
		<% for(String account:list){ %>
		<%out.print(account+" ");} %>

好了,今天的内容就到此了,有了今天的整理,我们以后在项目中要使用过滤器和监听器,就可以cv操作了,期待下期的精彩展示吧!