热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【JavaWeb】过滤器和监听器

【JavaWeb】过滤器和监听器-过滤器在日常生活中我相信大家多多少少应该都见过过滤器,比如滤网、滤纸等;这些都算是过滤器其作用就是过滤掉一些我们不需要的东西,而JavaWeb

过滤器

在日常生活中我相信大家多多少少应该都见过 过滤器,比如滤网、滤纸等;这些都算是过滤器

其作用就是 过滤掉一些我们不需要的东西,而 JavaWeb中的过滤器指的是实现了javax.servlet.Filter接口的类

我们可以通过 过滤器技术,对 Web服务器管理的所有 Web资源(例如 Jsp、Servlet、静态图片文件或静态 html 文件)等进行拦截

从而实现一些特殊的功能,例如实现 URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等...

其最简单的应用就是在我们访问所写的 Servlet类之前,预先处理乱码问题

在前面我们有提到过,如果我们直接使用 writer对象将中文字符串打印到前端,会出现乱码问题;也就是下面这种情况

这种小问题应该是大家学习 JavaWeb时会最先会碰到的,其解决起来也相当容易;只需要在我们的 servlet类中加上这么一行死代码即可

// 必须加在第一行
resp.setContentType("text/html;charset=UTF-8");



造成乱码问题的原因是,Tomcat所使用的编码默认为 ISO-8859-1,该编码常使用于欧洲国家;对中文的支持并不理想


如果想要正常显示中文,可以使用 UTF-8、GB2312、GBK等,其中 UTF-8是国际化的,所以大多数情况下都会使用 UTF-8来解决中文乱码的问题。


虽然一行死代码就可以解决响应到前端的字符乱码问题,但乱码的情况会有很多;不止是响应的时候可能会出现乱码问题,请求的时候也会

我们也不能或者说不应该在每一个 servlet类中都加上这些防止乱码的死代码,这样显得有些呆板了

那想要很好的解决该问题,过滤器就是一个不错的选择

我们只需要在每一次访问 Web资源时都经过一次过滤器(过滤器中预先处理乱码),就能够很好的避免乱码问题

说了这么多,还是没有真正的开始接触过滤器,我们先来看看 最简单的使用过滤器来处理乱码要如何操作




简单使用过滤器

过滤器的使用非常简单,我们只需要实现过滤器接口并将其注册到 web.xml中即可

可以看到,Filter接口非常的多,我们要实现的是 javax.servlet下的;如何你没有这个接口 检查你的依赖是否正常导入

<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
dependency>

实现该接口后,我们必须重写它的三个方法,其中init();destory();方法我们暂时不需要管;我们将处理乱码问题的死代码,写到 doFiter();中即可。

public class CharacterEncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 处理乱码
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");

// 让过滤器放行,这一步是必须的;你也可以试试不写这个会怎样
chain.doFilter(request,response);
}
@Override
public void destroy() {
// 销毁
}
}

将写好的过滤器注册到 web.xml中,并配置该过滤器所需要过滤的路径

<filter>
<filter-name>characterEncodingfilter-name>
<filter-class>com.molu.demo.CharacterEncodingFilterfilter-class>
filter>
<filter-mapping>
<filter-name>characterEncodingfilter-name>

<url-pattern>/*url-pattern>
filter-mapping>

操作无误后,我们可以删掉原本 Servlet类中处理乱码的死代码进行测试

你会发现,你的乱码问题基本上已经被解决了这就是过滤器最简单的应用——处理乱码




过滤器链

过滤器的存在可以是单一的,也可以是有多个;如果在一个 web服务中存在多个过滤器且可以相互组合 我们就称其为过滤器链

比如登录操作时,我们不仅要校验用户输入的账号密码是否正确,还需要校验所登录用户的权限是否足够等

这种情况下我们就可以编写两个或多个过滤器,分别处理不同的过滤逻辑。

Web服务器会根据过滤器在 web.xml文件中的注册顺序,决定先调用哪个过滤器

当第一个过滤器的 doFilter():方法被调用时,Web服务器会创建一个代表 过滤器链的 FilterChain对象传递给该方法。

在 过滤器的doFilter();方法中,我们如果调用了 FilterChain对象的 doFilter();方法,则 Web服务器会检查 FilterChain对象中是否还有过滤器存在,如果有 则调用第二个过滤器,如果没有,则返回目标资源。

这也就是为什么我在最后写上了 让过滤器放行,这一步是必须的这么一行注释,如果你最后不调用 FilterChain对象的 doFilter();方法,那么你就会被过滤器拦住,没办法访问到对应的资源。




过滤器的生命周期

过滤器的创建和销毁由 Web服务器负责,过滤器从创建到销毁的这段时间我们称之为——过滤器的生命周期


过滤器的创建

Web应用程序启动时,Web 服务器将创建过滤器的实例对象,并调用其init();方法,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作。

过滤器对象只会创建一次,init();方法也只会执行一次,通过init();方法的参数,可获得代表当前过滤器配置信息的 FilterConfig对象。


过滤器的销毁

当我们关闭 Web服务器时过滤器的实例对象会被销毁,并调用其 destory();方法

destroy();方法在过滤器的生命周期中 仅执行一次,我们可以在destroy();方法中,写一些释放资源的代码




监听器

先理解什么是监听器,可能某些初学者 听到监听器这个词汇会感到有些不明觉厉,实际上监听器这种东西没各位想象的那么复杂;

监听器就是一个专门用于:对其它对象产生的特定事件,或状态改变后进行监听和相应处理的对象

监听器其实就是一个实现特定接口的普通 Java程序,这个程序专门用于监听另一个 Java对象的方法调用或属性改变。

当被监听对象发生上述事件后,监听器某个方法立即被执行。

听起来可能会有些云里雾里,尤其是对初学者来说


当然 如果你对 GUI编程有一定了解的话,对监听器应该就不会陌生了


如何最简单的理解监听器呢,打个比方 你打开浏览器,浏览器的右上方应该都会有一个用来关闭浏览器的按钮

当我们点下这个按钮时浏览器就会被关闭,这是因为这个按钮绑定了一个用于关闭浏览器的监听事件;当我们点击该按钮时监听事件会被触发,浏览器也就被关闭了。

在 JavaWeb中,监听器也是越用越少了,这东西就和 GUI一样 慢慢的被人们所淡忘,很多课程里面对这个东西都是一笔带过

正是因为这些东西学的人越来越少了,初学者学起来也就更加困难,搜文章博客的话 都很难找到有所帮助的,所以我这边打算不敷衍的好好写写这类东西。




JavaWeb中的监听器

JavaWeb中的监听器是 Servlet规范中定义的一种特殊类,它用于监听 Web应用程序中的 ServletContext、 HttpSession和 ServletRequest等域对象的创建与销毁事件,以及监听这些域对象中的属性发生修改的事件。


统计在线人数Demo

好 理论说的够多了,我们上代码 写一个能够统计当前在线人数的简单 Demo,认识一下监听器。


重写方法

// 实现一个用于监听当前 Web服务中 Session数量的监听器;可以简单的理解为统计在线人数
// 首先我们需要实现 Servlet规定的监听器接口
public class OnlineCountListener implements HttpSessionListener {
// 实现该接口后会必须重写下面这两个方法
@Override
public void sessionCreated(HttpSessionEvent se) {
// 该方法是会在 Session创建时被调用,也就是 Session创建的监听事件
// 拿到上下文对象
ServletContext cOntext= se.getSession().getServletContext();
Integer OnlineCount= (Integer) context.getAttribute("onlineCount");
// 在触发 Session创建监听事件时,如果 onlineCount变量为 0我们将其设置为 1,表示第一个用户在线
if (OnlineCount==null){
OnlineCount= new Integer(1);
// 如果不为 0表示之前有用户在线,我们将在线人数 +1
}else {
int count = onlineCount.intValue();
OnlineCount= new Integer(count+1);
}
// 打印输出 方便测试,可以去掉
System.out.println(onlineCount);
// 将在线人数的变量赋值添加到上下文对象中,方便前端取值
context.setAttribute("onlineCount",onlineCount);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
// 这个方法则相反,会在Session被销毁时调用
// 销毁部分则逻辑相反
ServletContext cOntext= se.getSession().getServletContext();
Integer OnlineCount= (Integer) context.getAttribute("onlineCount");
if (OnlineCount==null){
OnlineCount= new Integer(0);
}else {
int count = onlineCount.intValue();
OnlineCount= new Integer(count-1);
}
context.setAttribute("onlineCount",onlineCount);
}
}

注册监听器

监听器写完之后我们需要和过滤器一样,将其注册到 web.xml中才能生效

<listener>

<listener-class>com.molu.listener.OnlineCountListenerlistener-class>
listener>

我们再写一个 jsp,让它能够拿到并展示在线人数



当前网站在线人数为:
"background-color: aquamarine">
${applicationScope.get("onlineCount")}





测试

成功通过监听器实现了简单网站在线人数统计

这是通过实现 HttpSessionListener接口,用于监听Session创建和销毁的监听器;

除此之外我们还可以实现 ServletContextListener接口,写出一个用于监听 ServletContext创建与销毁的监听器(上下文对象的创建与销毁是服务器级别的,这一点和过滤器的生命周期类似);

实现 ServletRequestListener接口,写出一个用于监听 ServletRequest 创建与销毁的监听器(ServletRequest对象会在访问时创建,访问结束后销毁)。

这些都基本类似,就不每个都写了;感兴趣可以自己测试一下




监听域对象属性的变更

域对象中属性变更的事件监听器,指的就是用来监听 ServletContext、HttpSession、HttpServletRequest 这三个对象中的属性变更信息事件的监听器。
这三个监听器接口分别是 ServletContextAttributeListener、HttpSessionAttributeListener 和 ServletRequestAttributeListener

这三个接口中都定义了三个方法来处理被监听对象中的属性的增加,删除和替换的事件;同一个事件在这三个接口中对应的方法名称完全相同,只是接受的参数类型不同。




attributeAdded

实现任意一个 AttributeListener接口,都需要你重写类似的 attributeAdded();方法,该方法会在你为所对应的监听域对象新增属性时触发。

例如,你实现了 ServletContextAttributeListener接口,那么你就需要重写其对应的attributeAdded();方法,该方法会在上下文对象中新增属性时被触发。

无论是 attributeAdded();方法亦或是其他的监听域对象属性变化的方法,都会存在一个 XXXXXAttributeEvent对象,我们通过这个对象可以拿到监听的域对象本身,也可以拿到被监听域对象中有所变化的属性。

以下为示例代码

// 实现 ServletContextAttributeListener接口,这里的三个监听器接口是类似的就不一一实现了
public class ContextAttributeListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent scab) {
// 我们可以通过 ServletContextAttributeEvent对象取到上下文对象
ServletContext cOntext= scab.getServletContext();
// 也可以通过它取到我们新增属性的 name和 value
context.setAttribute("attributeName",scab.getName());
context.setAttribute("attributeValue",scab.getValue());
}
@Override
public void attributeRemoved(ServletContextAttributeEvent scab) {
}
@Override
public void attributeReplaced(ServletContextAttributeEvent scab) {
}

实现接口后,我们重写对应的三个监听属性变化的方法,我们先来看看 attributeAdded();

监听器写完后一定要记得注册,不然等于没写是不会生效的

<listener>
<listener-class>com.molu.listener.ContextAttributeListenerlistener-class>
listener>

在测试之前,我们再写一些用于测试的 jsp,方便测试





新增的属性为: ${applicationScope.get("attributeName")}



值为: ${applicationScope.get("attributeValue")}







<%
application.setAttribute("nickname","陌路");
%>


添加成功




测试

没问题,我们成功通过 attributeAdded();监听事件拿到了我们新增属性的 name及value。




attributeRemoved

这三个方法实际上是类似的,我们将attributeAdded的方法拷到这里面也是可以直接使用的甚至不需要修改

只要我们删除了 上下文对象中的某个属性,该方法就能够拿到该属性对应的 name和 value;然后展示到前端

@Override
public void attributeRemoved(ServletContextAttributeEvent scab) {
// 我们可以通过 ServletContextAttributeEvent对象取到上下文对象
ServletContext cOntext= scab.getServletContext();
// 也可以通过它取到我们删除属性的 name和 value
context.setAttribute("attributeName",scab.getName());
context.setAttribute("attributeValue",scab.getValue());
}

对应的我们需要修改一下之前的 jsp文件,方便测试


<%--index--%>



删除的属性为: ${applicationScope.get("attributeName")}



值为: ${applicationScope.get("attributeValue")}






<%--update--%>
<%
/*application.setAttribute("onlineCount",999);*/
application.removeAttribute("onlineCount");
%>

删除成功




测试

没有问题,我们只要删除掉上下文对象中的属性,就会触发 attributeRemoved();方法

那么剩下的 attributeReplaced(); 我就不再重复演示一遍了;没有任何意义,感兴趣可以自行测试




感知Session绑定的事件监听器

保存在 Session域中的对象可以有多种状态



  • 绑定到Session中 (session.setAttribute("bean",Object));

  • 从 Session域中解除绑定 (session.removeAttribute("bean"));

  • 随 Session对象持久化到一个存储设备中

  • 随 Session对象从一个存储设备中恢复

Servlet 规范中定义了两个特殊的监听器接口 HttpSessionBindingListener 和HttpSessionActivationListener 来帮助 JavaBean对象了解自己在 Session域中的这些状态

实现这两个接口的类不需要 web.xml 文件中进行注册




HttpSessionBindingListener

实现了 HttpSessionBindingListener接口的 JavaBean对象需要重写两个方法:valueBound();valueUnbound();
当这个 JavaBean被绑定到 Session对象中时,Web服务器将调用该 JavaBean重写的valueBound();方法
反之(指从 Session中移除)则会调用valueUnbound();方法

我们来写一下代码确认一下:

// 实现 HttpSessionBindingListener接口
public class User implements HttpSessionBindingListener {
private int age;
private String name;
// 以下省略了 getter、setter、构造器等方法

......
// 必须重写的两个方法
@Override
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("User被绑定到了 Session中");
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("User从 Session中移除了");
}
}

好,现在我们再写几个简单的 jsp


<%-- add --%>
<%
// 将对应的 JavaBean添加到 Session对象中
session.setAttribute("User",new User(18,"陌路"));
%>


添加成功





<%-- remove --%>
<%
session.removeAttribute("User");
%>

移除成功




这两个 jsp非常简单,做的事情无非是添加和移除 JavaBean到 Session对象中。

测试

没有问题,通过观察控制台我们确定 JavaBean User从 Session对象中添加或移除都会触发其对应的监听事件。




HttpSessionActivationListener

实现了 HttpSessionActivationListener接口的 JavaBean可以感知到自己被序列化或反序列化

同样的 实现它我们需要重写两个方法sessionWillPassivate();sessionDidActivate();

当绑定到 Session对象中的 JavaBean对象将要随 Session对象被钝化 (序列化) 之前,Web服务器将调用该 JavaBean对象的sessionWillPassivate(); 方法;

这样,JavaBean对象就可以知道自己将要和 Session对象一起被序列化(钝化)到硬盘中。
当绑定到 Session对象中的JavaBean对象将要随 Session对象被活化 (反序列化) 之后,Web服务器调用该 JavaBean对象的sessionDidActive();方法;

这样 JavaBean对象就可以知道自己将要和 Session对象一起被反序列化 (活化) 回到内存中。

我相信认真看到这里,对监听事件应该不需要再多赘述了(可能唯一有所困惑的是序列化这一操作,但这篇文章不打算写序列化相关的东西,感兴趣可以找找其他人写的读读看),我们直接通过代码来进行一波收尾测试。

// 实现 HttpSessionActivationListener接口 监听序列化(和反序列化)操作
public class User implements HttpSessionActivationListener, Serializable {
private int age;
private String name;
// 以下省略了 getter、setter、构造器等方法

......

// 必须重写的两个方法
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
System.out.println(name + " 和 Session一起被序列化到硬盘中了" + "Session的 id是 : " + se.getSession().getId());
}
@Override
public void sessionDidActivate(HttpSessionEvent se) {
System.out.println(name + " 和 Session一起被反序列化到内存中了" + "Session的 id是 : " + se.getSession().getId());
}
}

为了很好的观察绑定到 Session对象中的 JavaBean对象随 Session对象一起被序列化到硬盘上和反序列化回到内存中的的过程,我们需要借助 tomcat服务器帮助我们完成 Session对象的序列化和反序列化过程,具体做法如下:

在 web项目的根路径下新建 META-INF目录,在该目录下新建 context.xml文件,并将下方代码烤入

<Context>

<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="test"/>
Manager>
Context>

在 index.jsp中书写一些简单的代码,并将我们的 JavaBean绑到 Session中


<%--index--%>



Session创建完成 id为: ${pageContext.session.id}



该 Session将在一分钟后被序列化到本地硬盘




<%
session.setAttribute("User",new User(18,"moluu"));
%>

启动服务器进行测试,我们只需要访问 localhost:8080即可,正确的显示了你在 index.jsp中书写的代码后 清空 IDE的控制台

稍微等待一分钟

一分钟后你会发现你写在sessionWillPassivate(); 方法中的输出语句被执行了

此时找到你 IDE 配置的 tomcat运行后产生的文件所在的目录,在其中你会发现生成的 test目录,和序列化到本地存储的 session

此时你再次访问 localhost:8080,就会执行反序列化操作,序列化到本地的 session将会回到内存中(一分钟后又会执行序列化操作)




放松一下眼睛


原图P站地址


画师主页






推荐阅读
author-avatar
宝蓝小礼服
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有