- 什么是过滤器Filter?有什么用?
Filter是过滤器,就像泡茶叶时用的过滤网一样,具有过滤功能。再说详细一点,就是拦截+放行功能。过滤网拦截了茶叶,放行了茶水。而过滤器拦截的是请求,放行则表示继续执行接下来的代码。
在JavaWeb中,过滤器可以在Servlet这个目标程序执行之前添加代码,也可以在其之后添加代码。
一般情况下,在过滤器中编写的代码都是公共代码,[eg]每个Servlet执行之前都要判断一下用户是否已经登陆,那么就可以写一个过滤器,过滤器的内容就是判断用户是否登陆。接下来就是如何将过滤器放在Servlet之前了。
如何将过滤器放在目标Servlet之前呢?
很简单,只需保证过滤器的路径包含Servlet的路径即可。假设路径为"/oa/dept"的时候走的是ServletDept,那么过滤器的路径是"/oa/dept"也行,"/oa/*"也行,总之只要是包含目标Servlet的路径即可。这是因为Filter的优先级天生就比Servlet高,所以当访问路径以后,优先走到Filter中,这便是本文第一段所说的“拦截”。在拦截以后,是否放行则取决于一行代码:Chain.doFilter( request , response ); 这行代码的含义为“放行”:继续进入下一个Filter,如果没有Filter了则进入Servlet。
多个Filter如何确定执行顺序?
上一段提到了Chain对象的doFilter方法具有“放行”功能,然后提到了这个“放行”有可能会进入到下一个Filter,那么就有一个新的问题:既然有多个Filter,如何判断他们的执行顺序呢?有两种情况如下:
第一种情况:Filter是在web.xml文件中配置的。那么此时filter-mapping越靠前的,对应的filter优先级越高。
第二种情况:使用@WebFilter注解的,则比较的是类名。类名逐个比对,在字典中排序靠前的则优先级高。例如Filter1优先级高于Filter2,这是因为逐个字母比较的时候,前面都一样,后面“1”在“2”前面,所以先走Filter1.然后FilterA优先级高于FilterB,理由同上。
- Filter怎么写?
说了这么多,Filter究竟该怎么写呢?其实也就两步。
第一步:编写一个类实现一个接口:jakarta.servlet.Filter。并且实现所有方法。这里面有三个方法。
init方法在Filter对象第一次被创建之后调用,并且只调用一次
doFilter方法 只要用户发送一次请求,则执行一次,发送N次请求,则执行N次。在这个方法中添加过滤规则
destroy方法 在Filter对象被释放/销毁之前调用,并且只调用一次
第二步:在web.xml中配置这个Filter,这个配置和Servlet的配置很像。或者用注解@WebFilter代替配置。
- Filter生命周期
和Servlet一样。唯一的区别在于Filter在服务器启动阶段就实例化,但是Servlet不会。
- 责任链设计模式
Filter过滤器这里有一个设计模式:责任链设计模式
核心思想:在程序运行阶段,动态的组合程序的调用顺序
过滤器最大的优点:在编程阶段不会确定调用顺序。因为Filter的调用顺序是配置到web.xml中的,只需要修改配置文件的filter-mapping的顺序就可以调整Filter的执行顺序。显然Filter的执行顺序是在程序运行阶段动态组合的。那么这种设计模式被称为责任链设计模式
- Filter小结
简单小结一下Filter就是过滤器,包含拦截和放行功能。拦截是拦截请求,只要访问路径符合我这个过滤器的路径,我都会拦截住。拦截过后便会按照顺序执行我的三个方法------init方法、doFilter方法、destroy方法。在doFilter方法中如果写Chain.doFilter(request , response); 则表示“放行”。“放行”时候如果有过滤器会先进过滤器,没有过滤器则会进入路径对应的Servlet。
过滤器怎么写呢?先写一个Filter类(需要实现Filter接口),然后重写其中的三个方法,伪代码如下:
@Override
init(){ //初始化方法
Filter初始化
}
doFilter(这里面有个Chain对象){
//doFilter开始执行
这里可以写希望在进入Servlet之前就进行的处理
Chain.doFilter(request , response); 这行代码表示“放行”。如果不写这行代码,就拦截住了,流程不往下走了
这里可以写希望在进入Servlet之后就进行的处理
//doFilter执行结束
}
destroy(){ //销毁方法
Filter销毁
}
由上面的伪代码,我们就可以比较清晰的知道我们的处理代码应该写在什么地方了。同时我们也会发现如果有Filter1、Filter2代码都如上所示,然后他们会经过ServletA的话,执行顺序应该是与栈类似:即先进后出
Filter1的doFilter开始执行--->Filter2的doFilter开始执行--->Servlet开始执行-->Servlet执行结束--->Filter2的doFilter执行结束--->Filter1的doFilter执行结束
- 什么是监听器Listener?有什么用?
监听器也是Servlet规范中的一员,这和Filter一样。
监听器可以理解为是触发器。生活中有烟雾触发器,它一旦检测到烟雾的存在,便会发出警报。监听器便是JavaWeb中的“触发器”。监听器会在特殊的时机(烟雾突发)做出行为(报警)。因此程序员不用去手动调用它,因为程序员根本不知道什么时候会是特殊时机,所以只能是等特殊时机发生时,由服务器自动调用相关方法。
所以监听器实际上就是Servlet规范留给程序员的特殊时机。特殊的时机想执行某段代码,就需要能想到对应的监听器。
有哪些监听器呢?
1 ServletContextListener:与下文request类似
2 ServletContextAttributeListener
3 ServletRequestListener:这个里面有两个方法,一个是request对象创建时调用的,一个是request对象销毁时调用的。这两个方法都是有默认值的,所以可以不重写,但是如果想使用这个“特殊时机”来完成特定功能,则可以选择重写
4 ServletRequestAttributeListener:里面有三个方法,分别对应request对象的三个动作:往域中存add、删remove、改replace。当这三个动作发生的时候,监听器里的对应方法就会执行。(有点像触发器,例如烟雾触发器,触发器检测到烟雾的存在,就报警。当request对象发生了这三个动作,对应的方法就被执行。)
5 HttpSessionListener:与上文request类似
6 HttpSessionAttributeListener:该监听器需要使用@WebListener注解进行标注
该监听器监听的是什么?是session域中数据的变化。只要数据变化,则执行相应的方法。主要监测点在session域对象上
7 HttpSessionBindingListener:该监听器不需要使用@WebListener进行标注。因为他是监听类的,不是监听网站的
假设User类实现了该监听器,那么User对象在被放入session的时候就会触发bind事件,User对象从session中被删除的时候就会触发unbind事件
假设Customer类没有实现该监听器,那么Customer对象放入session或者从session删除的时候,不会触发bind和unbind事件
8 HttpSessionIdListener:session的id发生改变的时候,监听器中唯一一个方法就会被调用
9 HttpSessionActivationListener:监听session对象的活化与钝化的
钝化:session对象从内存存储到硬盘文件;活化:session对象从硬盘文件存储到内存
- Listener怎么写?
前六种Listener实现一个监听器的步骤:以ServletContextListener为例。(ServletRequestListener和ServletSessionListener相同)
第一步:编写一个类实现ServletContextListener接口。并且实现里面的两个方法
void contextInitialized ( ServletContextEvent event );
void contextDestroyed ( ServletContextEvent event );
(如果是有Attribute的话,则需实现setAttribute、removeAttribute、replaceAttribute三个方法)
第二步:在web.xml文件中对ServletContextListener进行配置,如下:
<listener>
<listener-class>com.bjpowernode.javaweb.listener.MyServletContextListener</listener-class>
</listener>
当然,可以用@WebListener代替配置文件
HttpSessionBindingListener:这个是在普通Java实例类中实现的,实现HttpSessionBindingListener接口后需要重写两个方法:valueBound(数据绑定)和valueUnbound(数据解绑)。在之后调用session域对象使用到该Java实例类的时候,就会触发绑定事件。(这里讲的有点抽象,在后边小结内会有例子说明)
- Listener小结
以上的列举看起来比较复杂,但其实是有规律可循的:前面六种监听器,实际上是对应三个域。request域、session域、context域。因此这六种监听器中不带Attribute的三种监听器分别监听的是三个域的状态,带Attribute的三种监听器分别监听的是三个域中所存取的属性的状态。以context举例:
不带Attribute:监听的是context对象的状态
contextInitialized ( ServletContextEvent event ){
context初始化的时候则执行这里的代码。换句话说就是想让context初始化的时候执行的代码就放在这里
}
contextDestroyed ( ServletContextEvent event ){
context销毁的时候则执行这里的代码。换句话说就是想让context销毁的时候执行的代码就放在这里
}
带Attribute:监听的是context对象域中存取数据的状态
attributeAdded (){
往域中添加的时候执行这里的代码。换句话说就是想在域添加东西这个时刻执行的代码就往这里写
}
attributeRemoved (){
往域中删除的时候执行这里的代码。换句话说就是想在域删除东西这个时刻执行的代码就往这里写
}
attributeReplaced (){
往域中替换的时候执行这里的代码。换句话说就是想在域替换东西这个时刻执行的代码就往这里写
}
HttpSessionBindingListener:
某类中{
valveBound(){
绑定 的时候执行这里
}
valueUnbound(){
解绑 的时候执行这里
}
}
HttpSessionBindingListener与HttpSessionAttributeListener的区别:前者是使用在类当中,每当与这个类有关的时候触发。后者是不使用在普通实例类当中,只在session域中加减东西的时候触发。就好比session域是一个篮子🧺,然后现在有个苹果类实现了HttpSessionBindingListener接口。那么往篮子🧺中加一个梨🍐的时候就会触发后者,往篮子🧺里加苹果🍎则两者都会触发。所以两者关注的点不一样,前者关注苹果🍎,后者关注篮子🧺。注意:前者(苹果🍎)是只有往session域当中放才会触发奥。