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

Servlet–Listener、Filter、Decorator

Listener-监听器Listener为在JavaWeb中进行事件驱动编程提供了一整套事件类和监听器接口.Listener监听的事件源分为ServletContextHttpSe

Listener-监听器
Listener为在Java Web中进行事件驱动编程提供了一整套事件类和监听器接口.Listener监听的事件源分为ServletContext/HttpSession/ServletRequest三个级别:

ServletContext级别
Servlet – Listener、Filter、Decorator
HttpSession级别
Servlet – Listener、Filter、Decorator
ServletRequest级别
Servlet – Listener、Filter、Decorator
注册
创建监听器只需实现相关接口即可,但只有将其注册到Servlet容器中,才会被容器发现,这样才能在发生事件时,驱动监听器执行.Listener的注册方法有注解和部署描述符两种:



  1. @WebListener
    在Servlet 3.0中, 提供了@WebListener注解:

@WebListener
public class ListenerClass implements ServletContextListener {
// ...
}


  1. 部署描述符


    com.fq.web.listener.ListenerClass

    注: 由于HttpSessionBindingListener/HttpSessionActivationListener是直接绑定在JavaBean上, 而并非绑定到Session等域对象, 因此可以不同注册.
    示例
    加载Spring容器
    ContextLoaderListener
    public class ContextLoaderListener extends ContextLoader implements ServletContextListener {


public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* Close the root web application context.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}

web.xml


org.springframework.web.context.ContextLoaderListener

统计HTTP请求耗时
监控ServletRequest的创建/销毁事件, 以计算HTTP处理耗时

/**
* @author jifang.
* @since 2016/5/4 15:17.
*/
@WebListener
public class PerforationStatListener implements ServletRequestListener {
private static final Logger LOGGER = Logger.getLogger("PerforationStatListener");
private static final String START = "Start";
public void requestInitialized(ServletRequestEvent sre) {
ServletRequest request = sre.getServletRequest();
request.setAttribute(START, System.nanoTime());
}
public void requestDestroyed(ServletRequestEvent sre) {
HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
long start = (Long)request.getAttribute(START);
long ms = (System.nanoTime() - start)/1000;
String uri = request.getRequestURI();
LOGGER.info(String.format("time token to execute %s : %s ms", uri, ms));
}
}

HttpSessionBindingListener
当JavaBean实现HttpSessionBindingListener接口后,就可以感知到本类对象被添加/移除Session事件:

Listener```

public class Product implements Serializable, HttpSessionBindingListener {

private int id;
private String name;
private String description;
private double price;
public Product(int id, String name, String description, double price) {
this.id = id;
this.name = name;
this.description = description;
this.price = price;
}
// ...
public void valueBound(HttpSessionBindingEvent event) {
System.out.println("bound...");
}
public void valueUnbound(HttpSessionBindingEvent event) {
System.out.println("un_bound...");
}

}


Servlet
> private static final String FLAG = "flag";
>
> @Override
> protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
> Boolean flag = (Boolean) getServletContext().getAttribute(FLAG);
> if (flag == null || !flag) {
> request.getSession().setAttribute("product", new Product(8, "水晶手链", "VunSun微色天然水晶手链女款", 278.00));
> getServletContext().setAttribute(FLAG, true);
> } else {
> request.getSession().removeAttribute("product");
> getServletContext().setAttribute(FLAG, !flag);
> }
> }
**HttpSessionActivationListener**
为节省内存, Servlet容器可以对Session属性进行迁移或序列化.一般当内存较低时,相对较少访问的对象可以序列化到备用存储设备中(钝化);当需要再使用该Session时,容器又会把对象从持久化存储设备中再反序列化到内存中(活化).HttpSessionActivationListener就用于感知对象钝化/活化事件:
对于钝化/活化,其实就是让对象序列化/反序列化穿梭于内存与持久化存储设备中.因此实现HttpSessionActivationListener接口的JavaBean也需要实现Serializable接口.
在conf/context.xml配置钝化时间
>
> WEB-INF/web.xml
>
>
>
>

>

JavaBean
> public class Product implements Serializable, HttpSessionActivationListener {
>
> private int id;
> private String name;
> private String description;
> private double price;
>
> // ...
>
> public void sessionWillPassivate(HttpSessionEvent se) {
> System.out.println("passivate...");
> }
>
> public void sessionDidActivate(HttpSessionEvent se) {
> System.out.println("Activate...");
> }
> }
将Product加入Session一分钟不访问后, 该对象即会序列化到磁盘, 并调用sessionWillPassivate()方法, 当再次使用该对象时, Servlet容器会自动活化该Session, 并调用sessionDidActivate()方法.
**Filter-过滤器**
Filter是指拦截请求,并可以对ServletRequest/ServletResponse进行处理的一个对象.由于其可配置为拦截一个或多个资源,因此可用于处理登录/加(解)密/会话检查/图片适配等问题.
Filter中常用的有Filter/FilterChain/FilterConfig三个接口:
![](http://i2.51cto.com/images/blog/201810/21/0a3a846aef609e074525eea0bbf4e57d.png?,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
过滤器必须实现Filter接口, 当应用程序启动时,Servlet容器自动调用过滤器init()方法;当服务终止时,自动调用destroy()方法.当每次请求与过滤器资源相关资源时,都会调用doFilter()方法;由于doFilter()可以访问ServletRequest/ServletResponse,因此可以在Request中添加属性,或在Response中添加一个响应头,甚至可以对Request/Response进行修饰/替换,改变他们的行为(详见下).
![](http://i2.51cto.com/images/blog/201810/21/18a6b0b34055be3d6eaeadf4bccc62c9.png?,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
FilterChain中只有一个doFilter()方法, 该方法可以引发调用链中下一过滤器或资源本身被调用.如果没有在Filter的doFilter()中调用FilterChain的doFilter()方法,那么程序的处理将会在此处停止,不会再继续请求.
示例: Filter解决GET/POST编码问题

/**



  • @author jifang.

  • @since 2016/5/2 11:55.
    */
    public class CharsetEncodingFilter implements Filter {

    private static final String IGNORE_URI = "ignore_uri";

    private static final String URI_SEPARATOR = ",";

    private Set ignoreUris = new HashSet();

    public void init(FilterConfig config) throws ServletException {
    String originalUris = config.getInitParameter(IGNORE_URI);
    if (originalUris != null) {
    String[] uris = originalUris.split(URI_SEPARATOR);
    for (String uri : uris) {
    this.ignoreUris.add(uri);
    }
    }
    }

    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    HttpServletRequest request = (HttpServletRequest) req;
    String uri = request.getRequestURI();
    if (!ignoreUris.contains(uri)) {
    if (request.getMethod().equals("GET")) {
    request = new EncodingRequest(request);
    } else {
    request.setCharacterEncoding("UTF-8");
    }
    }
    chain.doFilter(request, resp);
    }

    private static final class EncodingRequest extends HttpServletRequestWrapper {

    public EncodingRequest(HttpServletRequest request) {
    super(request);
    }
    @Override
    public String getParameter(String name) {
    String value = super.getParameter(name);
    if (value != null) {
    try {
    value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
    } catch (UnsupportedEncodingException e) {
    throw new RuntimeException(e);
    }
    }
    return value;
    }

    }
    }
    注: HttpServletRequestWrapper介绍见Decorator-装饰者部分.




注册/配置
编写好过滤器后, 还需对其进行注册配置,配置过滤器的目标如下:

确定过滤器要拦截的目标资源;
传递给init()方法的启动初始值;
为过滤器命名.
web.xml


CharsetEncodingFilter
com.fq.web.filter.CharsetEncodingFilter

ignore_uri

/new_servlet.do,/hello_http_servlet.do



CharsetEncodingFilter
/*

也可用@WebFilter注解,其配置方式简单且与部署描述符类似,因此在此就不再赘述
.
FilterConfig
前面介绍了Filter/FilterChain两个接口,下面介绍FilterConfig接口, 其最常用的方法是getInitParameter(), 获取过滤器的初始化参数, 以完成更精细化的过滤规则.不过他还提供了如下实用方法:

Servlet – Listener、Filter、Decorator
拦截方式
过滤器的拦截方式有四种: REQUEST / FORWARD / INCLUDE / ERROR

REQUEST : (默认)直接访问目标资源时执行(地址栏直接访问/表单提交/超链接/重定向等只要在地址栏中可看到目标资源路径,就是REQUEST)
FORWARD : 转发访问执行(RequestDispatcher中forward()方法)
INCLUDE : 包含访问执行(RequestDispatcher中include()方法)
ERROR : 当目标资源在web.xml中配置为中时,并且出现异常,转发到目标资源时, 执行该过滤器.


CharsetEncodingFilter
com.fq.web.filter.CharsetEncodingFilter

ignore_path

/new_servlet.do



CharsetEncodingFilter
/*
REQUEST
INCLUDE

Decorator-装饰者
Servlet中有4个包装类ServletRequestWrapper/ServletResponseWrapper/HttpServletRequestWrapper/HttpServletResponseWrapper,可用来改变Servlet请求/响应的行为, 这些包装类遵循装饰者模式(Decorator).

由于他们为所包装的Request/Response中的每一个对等方法都提供了默认实现,因此通过继承他们, 只需覆盖想要修改的方法即可.没必要实现原始ServletRequest/ServletResponse/…接口的每一个方法.

实例-页面静态化
HttpServletRequestWrapper在解决GET编码时已经用到, 下面我们用HttpServletResponseWrapper实现页面静态化.

页面静态化是在第一次访问时将动态生成的页面(JSP/Servlet/Velocity等)保存成HTML静态页面文件存放到服务器,再有相同请求时,不再执行动态页面,而是直接给用户响应已经生成的静态页面.

Filter & Decorator

/**
* @author jifang.
* @since 2016/5/7 9:40.
*/
public class PageStaticizeFilter implements Filter {
private static final String HTML_PATH_MAP = "html_path_map";
private static final String STATIC_PAGES = "/static_pages/";
private ServletContext context;
public void init(FilterConfig filterConfig) throws ServletException {
this.cOntext= filterConfig.getServletContext();
this.context.setAttribute(HTML_PATH_MAP, new HashMap());
}
public void destroy() {
}
@SuppressWarnings("All")
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse respOnse= (HttpServletResponse) resp;
Map htmlPathMap = (Map) context.getAttribute(HTML_PATH_MAP);
String htmlName = request.getServletPath().replace("/", "_") + ".html";
String htmlPath = htmlPathMap.get(htmlName);
// 尚未生成静态页面
if (htmlPath == null) {
htmlPath = context.getRealPath(STATIC_PAGES) + "/" + htmlName;
htmlPathMap.put(htmlName, htmlPath);
PageStaticizeResponse sRespOnse= new PageStaticizeResponse(response, htmlPath);
chain.doFilter(request, sResponse);
sResponse.close();
}
String redirectPath = context.getContextPath() + STATIC_PAGES + htmlName;
response.sendRedirect(redirectPath);
}
private static final class PageStaticizeResponse extends HttpServletResponseWrapper {
private PrintWriter writer;
public PageStaticizeResponse(HttpServletResponse response, String path) throws FileNotFoundException, UnsupportedEncodingException {
super(response);
writer = new PrintWriter(path, "UTF-8");
}
@Override
public PrintWriter getWriter() throws IOException {
return this.writer;
}
public void close() {
this.writer.close();
}
}
}

注册


PageStaticzeFilter
com.fq.web.filter.PageStaticizeFilter


PageStaticzeFilter
*.jsp

注: 在此只是提供一个页面静态化思路, 由于代码中是以Servlet-Path粒度来生成静态页面, 粒度较粗, 细节方面肯定会有所疏漏(但粒度过细又会导致生成HTML页面过多), 因此这份代码仅供参考, 不可用于实际项目(关于该Filter所拦截的jsp页面, 可参考上篇博客的购物车案例).


推荐阅读
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 标题: ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • position属性absolute与relative的区别和用法详解
    本文详细解读了CSS中的position属性absolute和relative的区别和用法。通过解释绝对定位和相对定位的含义,以及配合TOP、RIGHT、BOTTOM、LEFT进行定位的方式,说明了它们的特性和能够实现的效果。同时指出了在网页居中时使用Absolute可能会出错的原因,即以浏览器左上角为原始点进行定位,不会随着分辨率的变化而变化位置。最后总结了一些使用这两个属性的技巧。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 本文介绍了如何使用JSONObiect和Gson相关方法实现json数据与kotlin对象的相互转换。首先解释了JSON的概念和数据格式,然后详细介绍了相关API,包括JSONObject和Gson的使用方法。接着讲解了如何将json格式的字符串转换为kotlin对象或List,以及如何将kotlin对象转换为json字符串。最后提到了使用Map封装json对象的特殊情况。文章还对JSON和XML进行了比较,指出了JSON的优势和缺点。 ... [详细]
author-avatar
sdauilk_299
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有