一、Filter简介
ServletAPI中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。
通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截。简单说,就是可以实现web容器对某资源的访问前截获进行相关的处理,还可以在某资源向web容器返回响应前进行截获进行处理。
如图,浏览器发出的请求先递交给第一个filter进行过滤,符合规则则放行,递交给filter链中的下一个过滤器进行过滤。过滤器在链中的顺序与它在web.xml中配置的顺序有关,配置在前的则位于链的前端。当请求通过了链中所有过滤器后就可以访问资源文件了,如果不能通过,则可能在中间某个过滤器中被处理掉。
在doFilter()方法中,chain.doFilter()前的一般是对request执行的过滤操作,chain.doFilter后面的代码一般是对response执行的操作。
二、Filter实现拦截的原理
Filter接口中有一个doFilter方法,当开发人员编写好Filter类实现doFilter方法,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前(服务器内部对资源的访问机制决定的),都会先调用一下filter的doFilter方法。
应用举例:
1、批量设置请求编码
public class EncodingFilter implements Filter {
private String encoding = null;
public void destroy() {
encoding = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
String encoding = getEncoding();
if (encoding == null){
encoding = "gb2312";
}
request.setCharacterEncoding(encoding);// 在请求里设置上指定的编码
chain.doFilter(request, response); //通过控制对chain.doFilter的方法的调用,来决定是否需要访问目标资源
}
public void init(FilterConfig filterConfig) throws ServletException {
this.encoding = filterConfig.getInitParameter("encoding");
}
private String getEncoding() {
return this.encoding;
}
}
xml配置代码
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.logcd.filter.EncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>gb2312</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
配置规则:
在配置中需要注意的有两处:一是<filter-class>指明过滤器类所在的包路径。二是<url-pattren>处定义过滤器作用的对象。一般有以下规则:
1:作用与所有web资源:<url—pattern>/*</url-pattern>。则客户端请求访问任意资源文件时都要经过过滤器过滤,通过则访问文件,否则拦截。
2:作用于某一文件夹下所有文件:<url—pattern>/dir/*</url-pattern>
3:作用于某一种类型的文件:<url—pattern>*.扩展名</url-pattern>。比如<url—pattern>*.jsp</url-pattern>过滤所有对jsp文件的访问请求。
4:作用于某一文件夹下某一类型文件:<url—pattern>/dir/*.扩展名</url-pattern>
如果一个过滤器需要过滤多种文件,则可以配置多个<filter-mapping>,一个mapping定义一个url-pattern来定义过滤规则。
如上的代码完成的功能为,无论进入那个页面,都要先执行EncodingFilter类的dofilter方法设置字符集
其中,doFilter()方法类似于Servlet接口的service()方法。当客户端请求目标资源的时候,容器就会调用与这个目标资源相关联的过滤器的doFilter()方法。
参数 request, response 为web 容器或 Filter 链的上一个 Filter 传递过来的请求和相应对象;参数 chain 代表当前 Filter 链的对象。
对于FilterChain接口,代表当前Filter链的对象。由容器实现,容器将其实例作为参数传入过滤器对象的doFilter()方法中。
过滤器对象使用FilterChain对象调用过滤器链中的下一个过滤器,或者目标Servlet 程序去处理,也可以直接向客户端返回响应信息,或者利用RequestDispatcher的forward()和include()方法,以及HttpServletResponse的sendRedirect()方法将请求转向到其他资源。
这个方法的请求和响应参数的类型是 ServletRequest和ServletResponse,也就是说,过滤器的使用并不依赖于具体的协议。
三、Filter生命周期
和Servlet一样,Filter的创建和销毁也是由WEB服务器负责。
与Servlet区别的是
1、在应用启动的时候就进行装载Filter类而servlet是在请求时才创建(但filter与Servlet的load-on-startup配置效果相同)。
2、容器创建好Filter对象实例后,调用init()方法。接着被Web容器保存进应用级的集合容器中去了等待着,用户访问资源。
3、当用户访问的资源正好被Filter的url-pattern拦截时,容器会取出Filter类调用doFilter方法,下次或多次访问被拦截的资源时,Web容器会直接取出指定Filter对象实例调用doFilter方法(Filter对象常驻留Web容器了)。
4、当应用服务被停止或重新装载了,则会执行Filter的destroy方法,Filter对象销毁。
四、应用
1.登录验证
package Filter;
import Model.User;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @author zyh
* date 2021-08-16
*/
public class LoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("登录过滤器初始化成功");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("loginFilter调用");
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
User user = null;
HttpSession session = request.getSession();
user = (User)session.getAttribute("user");
if (user == null){
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("<script> alert('您还没登录') </script>");
response.setHeader("Refresh","0;url='/login.html'");
}else {
filterChain.doFilter(request,response);
}
}
@Override
public void destroy() {
System.out.println("登录过滤器销毁");
}
}
2、设置字符编码
* Filter:过滤器 过白了就是在请求到达servlet之前,response到达servlet容器之前,拦截请求和响应,并且做一些操作
*
* Filter的作用:
* 1.设置编码集 ps:一般来说只会在filter里面设置编码,不会改response的contentType
* 2.做权限控制 -> 即没有登录的人,滚回去登录
* 3.做一些业务逻辑 ->针对不同业务场景来做响应的业务逻辑
* ...
*
* Filter做过滤时:
* 1.当servlet容器解析完请求报文以后,会将request对象发送给filter
* 2.当filter做完过滤操作以后,会把request对象发送给对应的servlet
* 3.servlet处理完请求以后,返回一个响应对象,这个response对象也是先发送给filter进行处理
* 4.filter处理完请求以后,把response对象交给servlet容器解析为报文
*
* 怎么实现一个Filter:
* 1.编写一个类 实现Filter接口
* 2.重写init doFilter destory方法
* 3.在doFilter方法中,将servletRequest和servletResponse强制转换成HttpServletRequest和HttpServletResponse
* 4.在web.xml中配置相关的Filter,filter的url-partten指明是拦截哪一个servlet *表明所有的servlet
*
* FilterChain对象:
* 如果程序中有很多的过滤器的话,可以调用filterChain的doFilter方法来交给下一个filter进行处理(即调用下一个filter的doFilter()方法)
Filter的调用顺序是谁在web.xml之前定义谁先调用
如果filterChain里面没有了filter的话,最后一个filter会把请求和响应转发给servlet
filterChian.dofilter -> 就是方法调用 调用下一个filt
er的doFilter()方法
五、Listener
监听器:顾名思义,就是在一些事件发生的时候,对这些事情进行一些响应
* 监听context,session和request对象创建和销毁事件以及属性改变事件(说白了就是设置在这些作用域内部的对象发生了改变)
监听器用于监听web应用中某些对象、信息的创建、销毁、增加,修改,删除等动作的发生,然后作出相应的响应处理。当范围对象的状态发生变化的时候,服务器自动调用监听器对象中的方法。常用于统计在线人数和在线用户,系统加载时进行信息初始化,统计网站的访问量等等。
分类:
按监听的对象划分,可以分为
- ServletContext对象监听器
- HttpSession对象监听器
- ServletRequest对象监听器
按监听的事件划分
- 对象自身的创建和销毁的监听器
- 对象中属性的创建和消除的监听器
- session中的某个对象的状态变化的监听器
在Servlet规范中定义了多种类型的监听器(一共8个监听器),它们用于监听的事件源分别为ServletContext,HttpSession和ServletRequest这三个域对象。Servlet规范针对这三个对象上的操作,又把多种类型的监听器划分为三种类型:
1.域对象的生命周期监听:监听域对象自身的创建和销毁。这个监听器需要实现相应的监听器接口:ServletContextListener、HttpSessionListener、ServletRequestListener。
2.域对象的属性监听:监听域对象中属性的增加和删除。这个监听器需要实现的监听器接口为:ServletContextAttributeListener、HttpSessionAttributeListener、ServletRequestAttributeListener
3.感知监听(都与HttpSession域对象有关):监听绑定到HttpSession域中的某个JavaBean对象的状态的监听器。这个监听器需要实现的监听器接口:HttpSessionBindingListener、HttpSessionActiveationListener.
示例:用监听器统计网站在线人数
原理:每当有一个访问连接到服务器时,服务器就会创建一个session来管理会话。那么我们就可以通过统计session的数量来获得当前在线人数。
所以这里用到的是HttpSessionListener
package listener;
/**
* @author zyh
* @version 1.0
* @items
* @Date:on 2021/8/17 at 10:29
*/
import Model.User;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.*;
/** 统计网站登录的在线人数:
* session每创建一次,证明有一个用户访问了网站(统计网站访问的在线人数)
* session中每有一个属性增加的时候,证明有一个用户登录了网站(统计网站登录的在线用户)
*
* 思路:
* 1.在servletContext创建的时候,可以在servletContxt中设置一个count属性用来统计网站当前登录的用户数量
* 2.设置一个session属性添加监听器,当每次session中有属性新增的时候,就从servletContext中将count取出来,+1再放回去
* 3.在session销毁的时候,把session中设置的user属性移除
* 4.设置一个session属性移除监听器,监听到属性移除的时候,将count从context中拿出来并-1放回去
*/
public class OnlineUserListener implements HttpSessionAttributeListener, ServletContextListener , HttpSessionListener {
@Override
public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
/**
* 通过HttpSessionBindingEvent.getSession()获取session对象
*
*/
HttpSession httpSession=httpSessionBindingEvent.getSession();
/**
* 通过session获得当前的ServletContext对像
*/
ServletContext servletContext = httpSession.getServletContext();
/**
* 设置一个count属性用来统计网站当前登录的用户数量
*/
int count =(int) servletContext.getAttribute("count");
/**
* 设置一个session属性添加监听器,当每次session中有属性新增的时候,
* 就从servletContext中将count取出来,+1再放回去
*/
servletContext.setAttribute("count",count+1);
}
@Override
public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {
HttpSession httpSession = httpSessionBindingEvent.getSession();
ServletContext servletContext = httpSession.getServletContext();
/**
* 在session销毁的时候,把session中设置的user属性移除
*/
int count = (int)servletContext.getAttribute("count");
servletContext.setAttribute("count",count-1);
}
@Override
public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {
}
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("初始化");
/**
* 通过servletContextEvent.getServletContext()获得servletContext
*/
ServletContext servletContext = servletContextEvent.getServletContext();
/**
* 在服务器启动的时候,在context中设置一个count属性来统计人数,初始值为0
*/
servletContext.setAttribute("count",0);
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
HttpSession httpSession = httpSessionEvent.getSession();
/**
* 从session中将user拿出来,如果user为空,证明当前用户没有登陆,
* 否则,证明当前的这个session是登陆的
*
*/
User user =(User) httpSession.getAttribute("user");
if (user!=null){
httpSession.removeAttribute("user");
}
}
}
public class onLineCount implements HttpSessionListener {
public int count=0;//记录session的数量
public void sessionCreated(HttpSessionEvent arg0) {//监听session的创建
count++;
arg0.getSession().getServletContext().setAttribute("Count", count);
}
@Override
public void sessionDestroyed(HttpSessionEvent arg0) {//监听session的撤销
count--;
arg0.getSession().getServletContext().setAttribute("Count", count);
}
}
在显示在线人数处通过session.getAttribute("Count")即可获取在线人数值。
HttpSessionListener用于监听用户session的创建和销毁,实现该接口的监听器需要实现如下两个方法: 1.sessionCreated(HttpSessionEvent se):用户与服务器的会话开始,创建时触发该方法。 2.sessionDestroyed(HttpSessionEvent se):用户与服务器的会话断开,销毁时触发该方法。 HttpSessionAttributeListener则用于监听HttpSession(session)范围内属性的变化,实现该接口的监听器需要实现attributeAdded(),attributeRemoved() 由此可见,HttpSessionAttributeListener监听session范围内属性的改变, ServletContextAttributeListener监听的是application范围内属性的改变。 实HttpSessionListener接口的监听器可以监听每个用户会话的开始断开,因此应用可以通过该监听器监听系统的在线用户。 属性范围 age:只在一个页面中保存属性,跳转之后无效 Page范围内的对象在客户端每次请求JSP时被创建,在页面向客户端发送回应或者被forward转发时被删除。 request:只在一次请求中保存,服务器跳转之后依然有效 常用的两个方法签名如下: ublic String getParameter(String name) ublic String[] getParameterValues(String name) 通过forward跳转后由于相当于在一次请求中,所以信息不会被删除,但是如果通过redirect方式跳转,则相当于一个新的请求,会被删除重新创建请求。 session:在一次回话范围中,无论何种跳转都可以使用,但是新开的浏览器不能使用 作用范围是在一次用户与服务器的链接时间内,如果与服务器断开链接则删除 application:在整个服务器上保存,所有的用户都可以使用