Servlet

标签 : Java与Web


Listener-监听器

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

  • ServletContext级别
Listener 场景
ServletContextListener 响应ServletContext生命周期事件(创建/销毁),在ServletContext创建/销毁时分别调用其相应的方法.
ServletContextAttributeListener 响应ServletContext属性的添加/删除/替换事件.
  • HttpSession级别
Listener 场景
HttpSessionListener 响应Session生命周期事件(创建/销毁).
HttpSessionAttributeListener 响应Session**属性**的添加/删除/替换事件.
HttpSessionBindingListener 实现了该接口的JavaBean会在 Session添加/删除时做出响应.
HttpSessionActivationListener 实现了该接口的JavaBean会在被Session 钝化/活化时做出响应.
  • ServletRequest级别
Listener 场景
ServletRequestListener 响应ServletRequest的创建/删除事件.
ServletRequestAttributeListener 响应ServletRequest属性的添加/删除/替换事件.

注册

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

1. @WebListener

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

@WebListener
public class ListenerClass implements ServletContextListener {

    // ...
}

2. 部署描述符

<listener>
    <listener-class>com.fq.web.listener.ListenerClass</listener-class>
</listener>

注: 由于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
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

统计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配置钝化时间
<Context>
    <WatchedResource>WEB-INF/web.xml</WatchedResource>

    <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
        <Store className="org.apache.catalina.session.FileStore" directory="sessions"/>
    </Manager>
</Context>
  • 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三个接口:

Filter 描述
void init(FilterConfig filterConfig) Called by the web container to indicate to a filter that it is being placed into service.
void destroy() Called by the web container to indicate to a filter that it is being taken out of service.
void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) The doFilter method of the Filter is called by the container each time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain.

过滤器必须实现Filter接口, 当应用程序启动时,Servlet容器自动调用过滤器init()方法;当服务终止时,自动调用destroy()方法.当每次请求与过滤器资源相关资源时,都会调用doFilter()方法;由于doFilter()可以访问ServletRequest/ServletResponse,因此可以在Request中添加属性,或在Response中添加一个响应头,甚至可以对Request/Response进行修饰/替换,改变他们的行为(详见下).

FilterChain 描述
void doFilter(ServletRequest request, ServletResponse response) Causes the next filter in the chain to be invoked, or if the calling filter is the last filter in the chain, causes the resource at the end of the chain to be invoked.

FilterChain中只有一个doFilter()方法, 该方法可以引发调用链中下一过滤器资源本身被调用.如果没有在FilterdoFilter()中调用FilterChaindoFilter()方法,那么程序的处理将会在此处停止,不会再继续请求.

  • 示例: 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<String> ignoreUris = new HashSet<String>();

    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-装饰者部分.


注册/配置

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

  1. 确定过滤器要拦截的目标资源;
  2. 传递给init()方法的启动初始值;
  3. 为过滤器命名.
    • web.xml
<filter>
    <filter-name>CharsetEncodingFilter</filter-name>
    <filter-class>com.fq.web.filter.CharsetEncodingFilter</filter-class>
    <init-param>
        <param-name>ignore_uri</param-name>
        <param-value>/new_servlet.do,/hello_http_servlet.do</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharsetEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

也可用@WebFilter注解,其配置方式简单且与部署描述符类似,因此在此就不再赘述.


FilterConfig

前面介绍了Filter/FilterChain两个接口,下面介绍FilterConfig接口, 其最常用的方法是getInitParameter(), 获取过滤器的初始化参数, 以完成更精细化的过滤规则.不过他还提供了如下实用方法:

FilterConfig 描述
String getFilterName() Returns the filter-name of this filter as defined in the deployment descriptor.
String getInitParameter(String name) Returns a String containing the value of the named initialization parameter, or null if the initialization parameter does not exist.
Enumeration<String> getInitParameterNames() Returns the names of the filter’s initialization parameters as an Enumeration of String objects, or an empty Enumeration if the filter has no initialization parameters.
ServletContext getServletContext() Returns a reference to the ServletContext in which the caller is executing.

拦截方式

过滤器的拦截方式有四种: REQUEST / FORWARD / INCLUDE / ERROR

  • REQUEST : (默认)直接访问目标资源时执行(地址栏直接访问/表单提交/超链接/重定向等只要在地址栏中可看到目标资源路径,就是REQUEST)
  • FORWARD : 转发访问执行(RequestDispatcherforward()方法)
  • INCLUDE : 包含访问执行(RequestDispatcherinclude()方法)
  • ERROR : 当目标资源在web.xml中配置为中时,并且出现异常,转发到目标资源时, 执行该过滤器.
<filter>
    <filter-name>CharsetEncodingFilter</filter-name>
    <filter-class>com.fq.web.filter.CharsetEncodingFilter</filter-class>
    <init-param>
        <param-name>ignore_path</param-name>
        <param-value>/new_servlet.do</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CharsetEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
</filter-mapping>

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<String, String>());
    }

    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<String, String> htmlPathMap = (Map<String, String>) 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();
        }
    }
}
  • 注册
<filter>
    <filter-name>PageStaticzeFilter</filter-name>
    <filter-class>com.fq.web.filter.PageStaticizeFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>PageStaticzeFilter</filter-name>
    <url-pattern>*.jsp</url-pattern>
</filter-mapping>

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


Servlet - Listener、Filter、Decorator的更多相关文章

  1. Servlet、Filter、Listener、Interceptor

    首先,JSP/Servlet规范中定义了Servlet.Filter.Listener这三种角色,并没有定义Interceptor这个角 色,Interceptor是某些MVC框架中的角色,比如Str ...

  2. servlet、filter、listener、interceptor之间的区别和联系

    一.概念 1.servlet:servlet是一种运行服务器端的java应用程序,具有独立于平台和协议的特性,并且可以动态的生成web页面,它工作在客户端请求与服务器响应的中间层. 2.filter: ...

  3. j2ee Servlet、Filter、Listener

    首先,JSP/Servlet规范中定义了Servlet.Filter.Listener这三种角色,并没有定义Interceptor这个角色,Interceptor是某些MVC框架中的角色,比如Stru ...

  4. SpringBoot 配置 Servlet、Filter、Listener

    SpringBoot 配置 Servlet.Filter.Listener 在SpringBoot应用中,嵌入式的 Servlet 3.0+ 容器不会直接使用 ServletContainerInit ...

  5. SpringBoot注册Servlet、Filter、Listener

    SpringBoot默认是以jar包的方式启动嵌入式的Servlet容易来启动SpringBoot的Web应用,没有web.xml文件 因此我们可以使用以下方式来注册Servlet.Filter.Li ...

  6. spring boot(18)-servlet、filter、listener

    servlet.filter.listener的用法就不讲了,只讲如何在spring boot中配置它们.有两种方式,一种是从servlet3开始提供的注解方式,另一种是spring的注入方式 ser ...

  7. SpringBoot(四)-- 整合Servlet、Filter、Listener

    SpringBoot中有两种方式可以添加 Servlet.Filter.Listener. 1.代码注册 通过ServletRegistrationBean. FilterRegistrationBe ...

  8. Java的Servlet、Filter、Interceptor、Listener

    写在前面: 使用Spring-Boot时,嵌入式Servlet容器可以通过扫描注解(@ServletComponentScan)的方式注册Servlet.Filter和Servlet规范的所有监听器( ...

  9. SpringBoot之Servlet、Filter、Listener配置

    在SpringBoot中是不需要配置web.xml的,那么原来在web.xml中配置的Servlet.Filter.Listener现在怎么弄呢? SpringBoot提供了三种Bean Filter ...

  10. Spring Boot (19) servlet、filter、listener

    servlet.filter.listener,在spring boot中配置方式有两种:一种是以servlet3开始提供的注解方式,另一种是spring的注入方式. servlet注解方式 serv ...

随机推荐

  1. [LeetCode] Smallest Good Base 最小的好基数

    For an integer n, we call k>=2 a good base of n, if all digits of n base k are 1. Now given a str ...

  2. JS获取URL传的值与解决获取URL中的中文参数出现乱码

    大家好,我是小C, 我们在项目开发中有时需要页面与页面之间的传值,那我们可能会选择用地址栏传递参数,那另外的那个页面就需要获取地址栏里的参数,今天分享下关于地址栏怎么传递参数与获取中文参数出现乱码的解 ...

  3. python爬虫——爬取小说 | 探索白子画和花千骨的爱恨情仇(转载)

    转载出处:药少敏   ,感谢原作者清晰的讲解思路! 下述代码是我通过自己互联网搜索和拜读完此篇文章之后写出的具有同样效果的爬虫代码: from bs4 import BeautifulSoup imp ...

  4. Struts2--拦截器Interceptor

    拦截器是的我们可以在方法的执行前后定义执行的操作.可以作为一个非常有力的工具在数据验证,属性设置,安全,日志等等方面. 拦截器可以链接起来形成一个拦截器栈.框架会按照拦截器定义的顺序依次调用这些拦截器 ...

  5. 2-XOR-SAT

    [题目描述]SAT(Satisfiability,可满足性)问题是著名的 NP 完全问题,它的内容是:判断由有限个布尔变量及其“非”用“或”操作连接起来的表达式组是否可以都为 TRUE.2-SAT 问 ...

  6. hdu2669与hdu1576(扩展欧几里德)

    模板: int Extend_Euclid(int a, int b, int &x, int &y){         if(b == 0){             x = 1; ...

  7. bzoj 2594: [Wc2006]水管局长数据加强版

    Description SC省MY市有着庞大的地下水管网络,嘟嘟是MY市的水管局长(就是管水管的啦),嘟嘟作为水管局长的工作就是:每天供水公司可能要将一定量的水从x处送往y处,嘟嘟需要为供水公司找到一 ...

  8. [Helvetic Coding Contest 2017 online mirror]

    来自FallDream的博客,未经允许,请勿转载,谢谢, 第一次在cf上打acm...和同校大佬组队打 总共15题,比较鬼畜,最后勉强过了10题. AB一样的题目,不同数据范围,一起讲吧 你有一个背包 ...

  9. [BZOJ]1085 骑士精神(SCOI2005)

    这种鲜明的玄学风格很明显就是十几年前的题目. Description 在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空位.在任何时候一个骑士都能按照骑士的走法(它可以走到和它横坐 ...

  10. 使用Unity NGUI-InputField组件输入时发现显示为白色就是看不到字体

    今天在接入android支付宝 SDK时,打包运行时,发现使用Unity NGUI-InputField组件输入时发现显示为白色就是看不到字体,查找一下发现是与android交互存在的问题, 只需在A ...