目录
一、Listener监听器
监听器一般用于监听Web应用中的某些对象(例如:HttpServletRequest、HttpSession、ServletContext),比如对象的创建、销毁、属性变化、修改和删除等动作的发生,然后做出相应的响应处理。当范围对象状态发生变化的时候,服务器会自动调用监听器对象中的方法。
1.1监听机制
名称 | 备注 |
事件 | 对象发生了某些改变 |
事件源 | 发生事件的源头 |
监听器 | 用于监听指定时间的对象 |
注册监听 | 让监听器监听某事件,必须先注册 |
1.2JavaWeb开发中常见监听器
监听对象 | 监听器名称 |
ServletContext | 监听对象的创建与销毁:ServletContextListener |
监听对象的属性变化:ServletContextAttributeListener | |
HttpSession | 监听对象的创建与销毁:HttpSessionListener |
监听对象的属性变化:HttpSessionAttributeListener | |
HttpServletRequest | 监听对象的创建与销毁:ServletRequestListener |
监听对象的属性变化:ServletRequestAttributeListener | |
javaBean | 监听javaBean对象是否绑定到了session域中:HttpSessionBindingListener |
监听javaBean对象的活化与钝化:HttpSessionActivationListener |
1.3监听器练习
创建一个监听器的步骤如下:
- 创建一个类,实现指定的监听器接口
- 重写接口中的方法
- 在web.xml中对监听器进行注册
1.3.1监听域对象的创建与销毁
我们测试监听一下ServletContext对象的创建与销毁,首先新建一个类,继承ServletContextListener接口,并实现其中的方法,
package Listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ServletContextListenerDemo implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {//监听ServletContext对象创建的方法
System.out.println("ServletContext对象已初始化");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {//监听ServletContext对象销毁的方法
System.out.println("ServletContext对象已销毁");
}
}
然后我们需要在web.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>Listener.ServletContextListenerDemo</listener-class>
</listener>
</web-app>
启动服务器和关闭服务器看看输出,
同样的,HttpSession对象会在打开index.jsp时自动创建,Session对象销毁的话有四种方式,分别为①默认超时②关闭服务器③invalidate()方法④setMaxInactiveInterval(int interval)设置超时时间,当session被销毁时也会触发监听方法。
而ServletRequest对象是处理浏览器请求的,我们打开index.jsp时,其内置就有一个servlet,打开时会调用service()方法,从而创建ServletRequest对象,当执行完service()方法后,ServletRequest对象就会被销毁。
1.3.2监听域对象的属性变化
我们以ServletRequest对象为例,首先创建监听类,实现接口中的方法,
package Listener;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
public class ServletRequestAttListenerDemo implements ServletRequestAttributeListener {
@Override
public void attributeAdded(ServletRequestAttributeEvent servletRequestAttributeEvent) {//监听属性添加
System.out.println("ServletRequest已添加属性");
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent servletRequestAttributeEvent) {//监听属性删除
System.out.println("ServletRequest已删除属性");
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent servletRequestAttributeEvent) {//监听属性替换,参数为时间源对象
System.out.println("ServletRequest已替换属性");
System.out.println(servletRequestAttributeEvent.getName()+" "+servletRequestAttributeEvent.getValue());//输出被替换的属性名和属性值
}
}
在web.xml中注册监听,
<listener>
<listener-class>Listener.ServletRequestAttListenerDemo</listener-class>
</listener>
然后在index.jsp首页上添加一个属性,然后替换一下属性
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%
request.setAttribute("name","peru");
%>
</body>
</html>
启动服务器看看输出,
1.3.3监听session绑定javaBean对象
我们首先新建一个javaBean对象,实体类User,这里我们要在javaBean对象上实现接口中的方法,
package Util;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
public class User implements HttpSessionBindingListener {
private String name;
private int age;
//实现get和set方法,这里省略了
@Override
public void valueBound(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("User对象已被绑定");
}
@Override
public void valueUnbound(HttpSessionBindingEvent httpSessionBindingEvent) {
System.out.println("User对象已解除绑定");
}
}
然后在index.jsp中绑定该对象,
<%@ page import="Util.User" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%
session.setAttribute("user",new User());
%>
</body>
</html>
注意此时并不需要在web.xml中注册监听,直接启动服务器就可以监听绑定事件,
因为我们的session马上就被销毁了,所以也就迅速解除绑定了。
1.3.4定时销毁session
我们可以创建一个HttpSessionListener,当session对象创建时,就将这个session对象装入到一个集合中。将集合List<HttpSession>保存到ServletContext域中。
我们可以通过getLastAccessedTime()方法得到上依次使用session的时间,这样就可以完成定时的操作,然后利用invalidate()方法进行销毁。
我们新建一个MyServletContextListener类,用于创建集合,并存储session对象,然后定期对不活跃的session进行移除。
package Listener.test;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSession;
import java.util.*;
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
//通过事件源对象得到事件源ServletContext
ServletContext application = servletContextEvent.getServletContext();
//创建一个集合用于存储所有session对象
List<HttpSession> list= Collections.synchronizedList(new ArrayList<>());//并且让该集合处于线程安全状态
//把集合放在application域中
application.setAttribute("sessions",list);
//创建一个计数器对象
Timer t=new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("开始扫描session");
for (Iterator iterator=list.iterator();iterator.hasNext();){
HttpSession session=(HttpSession) iterator.next();
long time = System.currentTimeMillis() - session.getLastAccessedTime();//计算session对象多久没被使用
if (time>5000){//如果超过5s没被访问
System.out.println("session移除了"+session.getId());
session.invalidate();//将session销毁
iterator.remove();//在集合中移除session对象
}
}
}
}, 2000, 5000);//延迟2s后重复执行,间隔为5s
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
}
然后创建MySessionListener对象,监听session的创建过程,并将创建的session放置到集合中。
package Listener.test;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.List;
public class MySessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
//得到application对象,得到list集合
HttpSession session = httpSessionEvent.getSession();//首先获取session对象
ServletContext application = session.getServletContext();//获取servletContext对象
List<HttpSession> list = (List<HttpSession>)application.getAttribute("sessions");//获取域对象中的list集合
//得到session对象,并放入到list集合中
list.add(session);
System.out.println("添加了"+session.getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
}
}
二、Filter过滤器
Filter可以对客户端访问资源进行过滤,符合条件的就可以访问,不符合条件的就拒绝访问,并且可以对目标资源访问前后进行逻辑处理操作。
2.1Filter
创建过滤器Filter主要有以下几个步骤:
- 创建一个类实现Filter接口
- 重写接口中的方法,执行过滤方法的为doFilter()
- 在web.xml文件中配置
如果在doFilter方法中没有执行FilterChain对象的doFilter方法,那么Filter过滤访问请求后不会继续执行之前的访问请求。
我们新建一个类MyFilter实现Filter接口,进行过滤拦截,
package Filter;
import javax.servlet.*;
import java.io.IOException;
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter已执行");//开始拦截,执行输出语句
filterChain.doFilter(servletRequest, servletResponse);//执行完拦截语句后继续访问原来的网页,命令放行
System.out.println("结束拦截");
}
@Override
public void destroy() {
}
}
接着我们创建一个Servlet用于模拟需要访问的资源,然后在Servlet中输出一句话,
package Filter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "ServletFilterDemo")
public class ServletFilterDemo extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("ServletFilterDemo");
}
}
配置好web.xml配置文件,
<filter>
<filter-name>MyFilter</filter-name>
<filter-class>Filter.MyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>/*</url-pattern><!--拦截的地址,这里是所有地址都要过滤拦截-->
</filter-mapping>
<servlet>
<servlet-name>servletFilterDemo</servlet-name>
<servlet-class>Filter.ServletFilterDemo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>servletFilterDemo</servlet-name>
<url-pattern>/servlet/servletFilterDemo</url-pattern><!--访问资源的路径-->
</servlet-mapping>
启动服务器,访问servlet资源,
2.2FilterChain
FilterChain提供了一个Filter链,该链上的所有对象都是对同一个资源进行过滤的Filter。
所以如果有多个Filter同时对一个资源进行过滤拦截,那么该资源就会形成一个FilterChain,该链是由servlet提供的。
FilterChain会不断执行下一个Filter的过滤方法,如果FilterChain一直调用到了最后一个Filter,那么下一个访问的就是原先要访问资源的地址,即过滤结束了。
FilterChain中Filter的拦截顺序主要看Filter对象在web.xml文件中配置的<filter-mapping>排列的顺序。
不同的资源其FilterChain也就不同。
我们再添加一个Filter对上面的servelt资源进行拦截,看看输出结果。
package Filter;
import javax.servlet.*;
import java.io.IOException;
public class MyFilter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter2已执行");
filterChain.doFilter(servletRequest, servletResponse);//执行完拦截语句后继续访问原来的网页,命令放行
System.out.println("结束拦截2");
}
@Override
public void destroy() {
}
}
配置好web.xml启动服务器:
可以看到filter1先执行,然后执行filter2的拦截操作,然后通过filterchain对象的doFilter()访问原资源,再回退回来到filter2,再到filter1。
2.3Filter生命周期
- 当服务器启动时,会创建Filter对象并且调用对应的init初始化方法,只调用一次该方法
- 当访问资源时,访问路径与Filter的拦截路径相匹配,会执行Filter中的doFilter()方法进行拦截操作
- 当服务器关闭时,会调用Filter的destroy()方法来进行销毁操作
package Filter;
import javax.servlet.*;
import java.io.IOException;
public class MyFilterDemo implements Filter {
public MyFilterDemo(){
System.out.println("filter对象实例化");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("filter对象初始化");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("filter对象开始拦截");
}
@Override
public void destroy() {
System.out.println("filter对象销毁");
}
}
2.4FilterConfig
Filter的init初始化方法中有一个参数,类型为FilterConfig,FilterConfig是Filter的配置对象,有以下功能:
- 获取Filter名称
- 获取Filter初始化参数
- 获取ServletContext对象
我们创建一个MyFilterConfig对象,读取初始化的参数,
package Filter;
import javax.servlet.*;
import java.io.IOException;
public class MyFilterConfig implements Filter {
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig=filterConfig;//将配置赋值给私有对象
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String encoding = filterConfig.getInitParameter("encoding");//获取配置中encoding的值
System.out.println(encoding);//输出
servletRequest.setCharacterEncoding(encoding);//设置请求的编码格式为配置中的值
filterChain.doFilter(servletRequest, servletResponse);//结束拦截
}
@Override
public void destroy() {
}
}
配置好web.xml,设置初始化的参数以及对应的值,启动服务器
<filter>
<filter-name>myFilterConfig</filter-name>
<filter-class>Filter.MyFilterConfig</filter-class>
<init-param>
<param-name>encoding</param-name><!--设置初始化的参数名-->
<param-value>UTF-8</param-value><!--设置该参数的值-->
</init-param>
</filter>
<filter-mapping>
<filter-name>myFilterConfig</filter-name>
<url-pattern>/*</url-pattern><!--拦截的地址,这里是所有地址都要过滤拦截-->
</filter-mapping>
2.5Filter配置
这里的配置,主要说的是在web.xml文件中的配置
1、<url-pattern>
该配置项决定了Filter需要过滤的访问路径,主要有以下三种匹配:
- 完全匹配:不包含通配符*,只过滤某个特定的访问路径
- 目录匹配:过滤某个目录下的所有路径。以”/”开始,以*结束
- 扩展名匹配:过滤某个特殊的扩展名资源,形如*.xxx,不能写成/*.xxx
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/servlet/servletFilterDemo</url-pattern><!--拦截特定的访问地址-->
</filter-mapping>
<filter-mapping>
<filter-name>myFilter2</filter-name>
<url-pattern>/*</url-pattern><!--全过滤-->
</filter-mapping>
<filter-mapping>
<filter-name>myFilter3</filter-name>
<url-pattern>*.aaa</url-pattern><!--拦截访问资源以.aaa为后缀的路径-->
</filter-mapping>
2、<servlet-name>
对指定的servlet名称进行过滤拦截
<filter-mapping>
<filter-name>myFilterConfig</filter-name>
<servlet-name>servletFilterDemo</servlet-name><!--拦截该名字的servlet-->
</filter-mapping>
3、<dispatcher>
可以取的值有:REQUEST、FORWARD、ERROR、INCLUDE
它的作用是:当以什么方式去访问web资源时,进行拦截操作。
- REQUEST:当是从浏览器直接访问资源,或是重定向到某个资源时进行拦截方式配置的。它也是默认值
- FORWARD:它描述的是请求转发的拦截方式配置
- ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。
- INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用
三、自动登录
首先我们在数据库中创建一个user表,用来存储用户名和密码,
自动登录是当用户第一次访问资源时,需要输入用户名和密码,和后台数据库确认用户名和密码,无误后进入主页home.jsp。当第一次登录时,用户如果点击了自动登录,那么在一段时间内,用户重新访问资源时不需要再次登录,通过Filter过滤器检查是否有之前输入的用户名和密码,是否和数据库的对应,确认后可以直接进入主页访问资源。
首先我们做一个登录界面和一个主界面,
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${msg}
<form action="${pageContext.request.contextPath}/servlet/loginServlet" method="post">
用户名:<input type="text" name="userName"/></br>
密码:<input type="password" name="passWord"/></br>
<input type="checkbox"/>自动登录</br>
<input type="submit" value="登录"/>
</form>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>主页</title>
</head>
<body>
欢迎你:${user.username}
</body>
</html>
然后我们新建一个servlet用于连接数据库,并且判断用户名输入的username和password是否正确。
package Login;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "LoginServlet")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userName = request.getParameter("userName");//获取用户名
String passWord = request.getParameter("passWord");//获取密码
UserService userService=new UserService();
User user=userService.findUser(userName,passWord);
if (user!=null){//登录成功
request.getSession().setAttribute("user",user);//将用户名存到session中
request.getRequestDispatcher("../home.jsp").forward(request,response);//转发
}else{//登录失败
request.getSession().setAttribute("msg","用户名或密码错误!请重新登录");//提示用户
request.getRequestDispatcher("../login.jsp").forward(request,response);//转发
}
}
}
然后在数据访问层DAO和服务层Service实现该方法,
package Login;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import java.sql.SQLException;
public class UserDao {
public User findUser(String userName, String passWord) throws SQLException {
QueryRunner queryRunner=new QueryRunner(C3P0Util.getDataSource());//获取数据库连接池
return queryRunner.query("select * from user where username=? and password=?",new BeanHandler<User>(User.class),userName,passWord);//查询数据库,并将结果封装到user对象中
}
}
package Login;
import java.sql.SQLException;
public class UserService {
UserDao userDao=new UserDao();
public User findUser(String userName, String passWord) {
try {
return userDao.findUser(userName,passWord);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
可以看到登录成功和登录失败的界面:
然后我们实现一下自动登录的功能,当用户点击自动登录并且登录成功过一次后,用户关掉界面短时间内打开则会直接跳到主界面,实现自动登录功能。
首先我们要判断是否勾选了复选框[自动登录],勾选了的话则将用户名和密码保存到cookie中,下次登录的时候可以利用Filter过滤器,从cookie中取出用户名和密码,然后进行登录操作跳转到主界面。
我们在登陆的时候把用户名和密码记录到cookie中,
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userName = request.getParameter("userName");//获取用户名
String passWord = request.getParameter("passWord");//获取密码
UserService userService=new UserService();
User user=userService.findUser(userName,passWord);
if (user!=null){//登录成功
String autoLogin = request.getParameter("autoLogin");//获取是否勾选了自动登录框
Cookie cookie=new Cookie("user",user.getUsername()+"&"+user.getPassword());//将用户名和密码写到cookie中,中间用&隔开
cookie.setPath("/");
if (autoLogin!=null){//勾选了自动登录
cookie.setMaxAge(60*5);//cookie保存5min
}else{//未勾选
cookie.setMaxAge(0);//清除cookie
}
response.addCookie(cookie);//把cookie对象保存到客户端
request.getSession().setAttribute("user",user);//将用户名存到session中
request.getRequestDispatcher("../home.jsp").forward(request,response);//转发
}else{//登录失败
request.getSession().setAttribute("msg","用户名或密码错误!请重新登录");//提示用户
request.getRequestDispatcher("../login.jsp").forward(request,response);//转发
}
}
然后创建一个Filter,获取request中的cookie,得到用户名和密码进行登录。
package Login;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.*;
import java.io.IOException;
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//转换ServletRequest、ServletResponse为HttpServletRequest、HttpServletResponse
HttpServletRequest request= (HttpServletRequest) servletRequest;
HttpServletResponse response= (HttpServletResponse) servletResponse;
String uri = request.getRequestURI();//获取访问的地址,我们要过滤的地址为/home.jsp
if ("/home.jsp".equals(uri)){//对home.jsp进行过滤
User userS =(User) request.getSession().getAttribute("user");
if (userS==null){//如果用户之前没有执行登录,从session中获取数据进行登录
//得到cookie
Cookie[] cookies = request.getCookies();
String username="";
String password="";
for (int i = 0;cookies!=null && i < cookies.length; i++) {
if ("user".equals(cookies[i].getName())){//找到存放用户名和密码信息的cookie
String value = cookies[i].getValue();
String[] strs = value.split("&");//将用户名和密码信息分开
username=strs[0];
password=strs[1];
}
}
UserService userService=new UserService();
User user = userService.findUser(username, password);//查询用户名和密码是否正确
if (user!=null) {//登录成功
request.getSession().setAttribute("user", user);//将用户信息存放到session域中
}
}
}
filterChain.doFilter(servletRequest, servletResponse);//结束过滤
}
@Override
public void destroy() {
}
}
启动服务器,首先进入登录界面输入用户名和密码,选择自动登录,关掉浏览器后直接打开home.jsp页面,发现之前登陆的用户名显示出来了。
四、MD5加密
MD5是一种加密的算法,在mysql中可以对数据进行MD5加密比如密码等数据。
我们对user表中id为1的密码进行加密,
UPDATE USER SET PASSWORD=MD5(PASSWORD) WHERE id=1;
加密后的结果如下:
该过程是不可逆的,所以当用户在客户端输入密码后,我们会进行MD5加密,将结果和数据库中存放的数据进行对比,以此判断密码是否正确。
在java中,我们也可以进行md5进行加密:
package Login;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Utils {
/**
* 使用md5的算法进行加密
*/
public static String md5(String plainText) {
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(
plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("没有md5这个算法!");
}
String md5code = new BigInteger(1, secretBytes).toString(16);
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
}