上篇文章我们介绍了Servlet和Jsp以及一些Java Web开发的基础概念,本篇文章我们来介绍另一Java Web开发中的重要概念——Java Web三大组件,即Servlet、Filter和Listener。

1. Servlet

由于我们上篇文章已经介绍过Servlet了,这里就不再详细介绍了,透过现象看本质——什么是servlet

2. Filter

Filter(过滤器)用于拦截用户请求,在服务器作出响应前,可以在拦截后修改request和response。可以实现一次编码,多处应用。完整的流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。

2.1 作用

  • 拦截修改请求:在HttpServletRequest到达Servlet之前,拦截客户的HttpServletRequest。根据需要检查HttpServletRequest,也可以修改HttpServletRequest头和数据。
  • 拦截修改响应:在HttpServletResponse到达客户端之前,拦截HttpServletResponse。根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

2.2 原理

Filter也是一个接口,实现了该接口并进行相应配置,就可以使Filter生效,进而实现:对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后再对服务器响应进行后处理。Filter的底层原理是基于函数回调的,这里用到了责任链模式。

  • init(FilterConfig filterConfig)
    • 初始化方法,只会在web应用程序启动时调用一次
    • 和Servlet一样,Filter的创建和销毁由WEB服务器负责
    • web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象
    • Filter对象创建之后会驻留在内存,一直服务
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    • 完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器
  • destroy();
    • 销毁方法,只会在当web应用移除或服务器停止时才调用一次来卸载Filter对象
    • 通常在这个方法中,可以释放过滤器使用的资源

所以我们自定义实现Filter的核心逻辑就在doFilter方法中,一般我们自定义实现Filter都会按照以下的模式:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
	//对用户请求执行预处理的代码逻辑
	precode... 
	//交给FilterChain的下一个对象处理
	//如果还有filter则调用下一个filter
	//如果没有,则调用目标资源。
	chain.doFilter(req, resp);
	//对服务器响应执行后处理
	postcode...
}

2.2.2 FilterConfig

与Servlet一样,Filter也很可能需要访问 Servlet 容器。Servlet 规范将代表 ServletContext对象和Filter的配置参数信息都封装到一个称为FilterConfig的对象中。

  • getInitParameter():获取初始化参数
  • getInitParameterNames():获取所有初始化参数的名称
  • getFilterName():获取过滤器的配置名称
  • getServletContext():获取ServletContext

2.2.3 FilterChain

在一个web应用当中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链,其中每个过滤器(Filter)都可以决定是否执行下一步。

doFilter():该方法被FilterChain对象调用,表示对Filter过滤器过滤范围下的资源进行放行,让链中下一个对象进行处理

FilterChain在tomcat中的实现类是ApplicationFilterChain。

  • n:filter个数
  • pos:下一个要执行的filter的位置
  • Servlet:当pos >= n,即过滤完成时,调用Servlet的service方法,把请求交给Servlet
  • filters:Filter的相关配置信息

FilterChain持有所有Filter的配置信息,它们保存在一个数组中,然后通过移动pos,来获取后续的Filter并执行的,符合之前的链式处理流程。

FilterChain pos的维护使用过pos++实现的,我们可能会担心是否会有线程安全问题。但其实FilterChain是不存在线程安全问题的。

ApplicationFilterChain对象是由ApplicationFilterFactory工厂的createFilterChain方法生成的。而这个方法在ApplicationDispatcher的invoke方法内被调用。这个invoke方法是Connector把新请求传递给Container的方式。这样来看每个请求都会创建一个FilterChain对象,所以不用担心会有线程安全问题

对于FilerChain,我们需要注意:

  • 只要FilterChain中任意一个Filter没有调用FilterChain.doFilter方法,则目标Servlet的service方法都不会被执行
  • FilterChain中的各个Filter的拦截顺序与它们在web.xml文件中的映射顺序一致
  • 最后一个Filter调用doFilter方法将激活目标Servlet的service方法,该逻辑在FilterChain.doFilter方法中实现

2.3 编写一个Filter

通常我们编写一个Filter,主要包含以下两个步骤:

  1. 定义一个Filter类,该类实现了Filter接口
  2. 将自定义的Filter类配置到web.xml中

2.3.1 实现Filter类

public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化方法,在web应用程序启动时调用一次
    }

    @Override
    public void destroy() {
        // 销毁方法,当web应用移除或服务器停止时才调用一次来卸载Filter对象
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 1. preCode,对Request做前置处理,也可以在这里实现一些Filter过滤器前置逻辑,比如读取Filter配置的init-param信息,存储到成员变量中,实现过滤逻辑
        // ……

        // 2. 当前过滤器放行,继续执行下一个过滤器的过滤逻辑
        filterChain.doFilter(servletRequest, servletResponse);

        // 3. postCode,对服务器响应执行后处理
        // ……
    }
}

2.3.2 Filter配置

在web.xml中配置过滤器,需要谨记一条原则:在web.xml中,监听器 > 过滤器 > servlet。也就是说web.xml需要先配置监听器,然后配置过滤器,最后是Servlet,否则会出错。

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

    <filter>
        <filter-name>testFilter</filter-name>
        <filter-class>com.zhuoli.service.servlet.TestFilter</filter-class>
        <init-param>
            <param-name>cacheTimeout</param-name>
            <param-value>600</param-value>
        </init-param>
    </filter>
    <>
        <filter-name>testFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>testServlet</servlet-name>
        <servlet-class>com.zhuoli.service.servlet.TestServlet</servlet-class>
        <init-param>
            <param-name>myParam</param-name>
            <param-value>paramValue</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>testServlet</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
</web-app>

<filter>元素用于配置一个过滤器

  • <filter-name>:用于为过滤器指定一个名字,不能为空
  • <filter-class>:用于指定过滤器的完整的限定类名,不能为空
  • <init-param>:用于为过滤器指定初始化参数,它的子元素<param-name>指定参数的名字,<param-value>指定参数的值。在过滤器中,可以使用FilterConfig接口对象来访问初始化参数

<filter-mapping>元素用于设置一个Filter所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet名称(<servlet-name>)和资源访问的请求路径(<url-pattern>)

  • <filter-name>:用于设置filter的注册名称,该值必须是在<filter>元素中声明过的过滤器的名字
  • <url-pattern>:用于设置filter所拦截的请求路径(过滤器关联的URL样式)
    • 作用与所有web资源:<url—pattern>/*</url-pattern>。则客户端请求访问任意资源文件时都要经过过滤器的过滤
    • 作用于某一文件夹下所有文件:<url—pattern>/dir/*</url-pattern>
    • 作用于某一种类型的文件:<url—pattern>*.扩展名</url-pattern>。比如<url—pattern>*.jsp</url-pattern>过滤所有对jsp文件的访问请求
    • 作用于某一文件夹下某一类型文件:<url—pattern>/dir/*.扩展名</url-pattern>
  • <servlet-name>:用于设置filter所拦截的Servlet名称
  • <dispatcher>:用于资源访问时,设置过滤器是否被Servlet容器调用,可以是REQUEST、INCLUDE、FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截
    • REQUEST:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用
    • INCLUDE:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用
    • FORWARD:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用
    • ERROR:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用

2.3.3 Filter执行顺序

Filter的执行顺序是按照FilterChain来执行的,FilterChain中Filter的顺序不同配置方式下的组织情况不同,具体如下:

  • 使用web.xml配置:根据对应的Mapping的顺序组织,谁定义在上边谁就在前
  • 基于注解配置(Spring注解):按照类名的字符串比较规则比较,值小的先执行

2.3.4 实际运用

  • 处理全站中文乱码问题
  • 过滤非法请求
  • 实现自动登录
  • 过滤敏感词汇
  • 选择性让浏览器缓存
  • 结合日志记录用户操作

3. Listener

监听器是一个专门用于对其他对象身上发生的事件或状态改变进行监听和相应处理的对象,当被监视的对象发生情况时,立即采取相应的行动。监听器其实就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法立即被执行。而这里讲的Listener其实就是是Servlet的监听器,它可以监听客户端请求(Request)、服务端Session、服务端ServletContext生命周期内的变化。通过监听器,可以自动激发一些操作,比如监听在线的用户的数量等。

被监听对象A中,关联着监听器B对象。事件源A类对外提供一个方法,用于设置监听器对象B到A类的某一实例变量中。在需要被监听的事件源的方法中,方法体的某一处先构造创建一个Event对象,将监听器对象B与相关的动作封装进Event对象中,然后调用监听器B对象的xxAdded(event)、xxCreated(event)方法,将事件对象传入方法实参中,实现事件源修改触发监听器监听的动作。通俗一点讲就是:

  • 事件源:即谁产生的事件
  • 事件对象:即产生了什么事件
  • 监听器:监听事件源的动作

按照划分Java Web监听器可分为三类:

  • ServletContext监听器:监听ServletContext对象的初始化和销毁
  • Request监听器:监听Request对象的初始化和销毁以及属性改变
  • Session监听器:监听Session对象的创建和销毁以及属性改变

3.1 ServletContext监听器

3.1.1 ServletContextListener

ServletContextListener用于对ServletContext进行监听(创建、销毁)。

  • contextInitialized:监听ServletContext初始化动作
  • contextDestroyed:监听ServletContext销毁动作
  • getServletContext:获取ServletContext

这里我们写个demo,实现使用监听器对数据库连接池DataSource进行初始化。

public class ListenerTest implements ServletContextListener{       
   // 应用监听器的销毁方法     
   public void contextDestroyed(ServletContextEvent servletContextEvent) {     
        ServletContext servletContext = servletContextEvent.getServletContext();  
        // 在整个web应用销毁之前调用,将所有应用空间所设置的内容清空  
        servletContext.removeAttribute("dataSource");  
        System.out.println("销毁工作完成...");    
   }     
    // 应用监听器的初始化方法     
    public void contextInitialized(ServletContextEvent servletContextEvent) {        
        // 通过这个事件可以在整个web应用下面启动的时候做一些初始化的内容添加工作     
        ServletContext servletContext = servletContextEvent.getServletContext();    
        // 设置一些基本的内容;比如一些参数或者是一些固定的对象     
        // 创建DataSource对象,连接池技术 dbcp     
        BasicDataSource basicDataSource = new BasicDataSource();   
        basicDataSource.setDriverClassName("com.jdbc.Driver");   
        basicDataSource.setUrl("jdbc:mysqlocalhost:3306/");   
        basicDataSource.setUsername("root");     
        basicDataSource.setPassword("root");     
        basicDataSource.setMaxActive(10);//最大连接数     
        basicDataSource.setMaxIdle(5);//最大管理数     
        servletContext.setAttribute("dataSource", basicDataSource);     
        System.out.println("应用监听器初始化工作完成...");     
        System.out.println("已经创建DataSource...");    
    }     
}
//web.xml中配置监听器   
<listener>     
    <listener-class>com.zhuoli.service.listener.ListenerTest</listener-class>     
</listener> 

3.1.2 ServletContextAttributeListener

ServletContextAttributeListener用于对ServletContext属性变更的监听(增删改属性)

  • attributeAdded:监听ServletContext attributes添加kv动作
  • attributeRemoved:监听ServletContext attributes删除kv动作
  • attributeRepalced:ServletContext attributes替换kv动作
  • getName:获取属性名称
  • getValue:属性的值

这里我们看一下ApplicationContext类中对ServletContext方法setAttribute的实现:

public void setAttribute(String name, Object value) {
    // 1. 检查attribute name不能为null
    if (name == null) {
        throw new NullPointerException
            (sm.getString("applicationContext.setAttribute.namenull"));
    }

    // 2. 如果attribute value为null则删除该attribute
    if (value == null) {
        removeAttribute(name);
        return;
    }

    // Add or replace the specified attribute
    // 3. 如果attribute为readOnlyAttribute,直接返回
    if (readOnlyAttributes.containsKey(name))
        return;

    // 4.添加或替换attribute
    Object oldValue = attributes.put(name, value);
    // 5. 根据返回值,判断是添加操作还是替换操作
    boolean replaced = oldValue != null;

    // 获取所有的监听器,通知监听器,实现监听动作
    Object listeners[] = context.getApplicationEventListeners();
    if ((listeners == null) || (listeners.length == 0))
        return;
    ServletContextAttributeEvent event = null;
    if (replaced)
        event =
            new ServletContextAttributeEvent(context.getServletContext(),
                                             name, oldValue);
    else
        event =
            new ServletContextAttributeEvent(context.getServletContext(),
                                             name, value);

    for (int i = 0; i < listeners.length; i++) {
        if (!(listeners[i] instanceof ServletContextAttributeListener))
            continue;
        ServletContextAttributeListener listener =
            (ServletContextAttributeListener) listeners[i];
        try {
            if (replaced) {
                context.fireContainerEvent
                    ("beforeContextAttributeReplaced", listener);
                listener.attributeReplaced(event);
                context.fireContainerEvent("afterContextAttributeReplaced",
                                           listener);
            } else {
                context.fireContainerEvent("beforeContextAttributeAdded",
                                           listener);
                listener.attributeAdded(event);
                context.fireContainerEvent("afterContextAttributeAdded",
                                           listener);
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            if (replaced)
                context.fireContainerEvent("afterContextAttributeReplaced",
                                           listener);
            else
                context.fireContainerEvent("afterContextAttributeAdded",
                                           listener);
            // FIXME - should we do anything besides log these?
            log(sm.getString("applicationContext.attributeEvent"), t);
        }
    }
}

逻辑是非常简单的,事件源ApplicationContext的attribute修改动作,出发监听器的监听。

这里我们写一个demo,对ServletContext attribute属性的变更做监听。

public class MyServletContextAttributeListener implements
        ServletContextAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent scab) {
        String str =MessageFormat.format(
                "ServletContext域对象中添加了属性:{0},属性值是:{1}"
                ,scab.getName()
                ,scab.getValue());
        System.out.println(str);
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent scab) {
        String str =MessageFormat.format(
                "ServletContext域对象中删除属性:{0},属性值是:{1}"
                ,scab.getName()
                ,scab.getValue());
        System.out.println(str);
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent scab) {
        String str =MessageFormat.format(
                "ServletContext域对象中替换了属性:{0}的值"
                ,scab.getName());
        System.out.println(str);
    }
//web.xml配置监听器
<listener>
   <listener-class>com.zhuoli.service.listener.MyServletContextAttributeListener</listener-class>
</listener>

3.2 Request监听器

3.2.1 ServletRequestListener

ServletRequestListener用于对Request请求进行监听(创建、销毁)。

  • requestInitialized:监听ServletRequest初始化动作
  • requestDestroyed:监听ServletRequest销毁动作
  • getServletRequest:从Evenet对象中获取ServletRequest对象
  • getServletContext:从Event对象中获取ServletContext

这里我们写一个demo,使用servletRequestListener来实现web浏览量的变化。

public class ListenerTest3 implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent arg0) {
        System.out.println("requestDestroyed" + "," + new Date());
        System.out.println("当前访问次数:" + arg0.getServletContext().getAttribute("count"));
    }

    @Override
    public void requestInitialized(ServletRequestEvent arg0) {
        System.out.println("requestInitialized" + "," + new Date());
        Object count = arg0.getServletContext().getAttribute("count");
        Integer cInteger = 0;
        if (count != null) {
            cInteger = Integer.valueOf(count.toString());
        }
        System.out.println("历史访问次数::" + count);
        cInteger++;
        arg0.getServletContext().setAttribute("count", cInteger);
    }
}

但是需要注意的是,这里的是非线程安全的。

3.2.2 ServletRequestAttributeListener

ServletRequestAttributeListener用于对ServletRequest属性添加、删除、修改做监听。

  • attributeAdded:监听ServletRequest attribute添加动作
  • attributeRemoved:监听ServletRequest attribute删除动作
  • attributeReplaced:监听ServletRequest attribute替换动作
  • getName:获取ServletRequest attribute名称
  • getValue:获取ServletRequest attribute值
public class ServletRquestListeners implements ServletRequestListener,ServletRequestAttributeListener  
{  
    //这个是在请求后调用  
    public void requestDestroyed(ServletRequestEvent s)  
    {  
        System.out.println("销毁了ServletReqeust");  
    }  
  
    //它是在请求前调用  
    public void requestInitialized(ServletRequestEvent s)  
    {  
        ServletRequest servletRequest = s.getServletRequest();  
        HttpServletRequest request = (HttpServletRequest) servletRequest;  
        String pathInfo = request.getServletPath();  
        System.out.println("请求地址:"+pathInfo);  
    }  
  
    public void attributeAdded(ServletRequestAttributeEvent srae)  
    {  
        HttpServletRequest request = (HttpServletRequest) srae.getServletRequest();  
        System.out.println("增加request--->"+request.getAttribute("requestName"));  
    }  
  
    public void attributeRemoved(ServletRequestAttributeEvent srae)  
    {  
        HttpServletRequest request = (HttpServletRequest) srae.getServletRequest();  
        System.out.println("删除request--->"+request.getAttribute("requestName"));  
    }  
  
    public void attributeReplaced(ServletRequestAttributeEvent srae)  
    {  
        HttpServletRequest request = (HttpServletRequest) srae.getServletRequest();  
        System.out.println("替换request--->"+request.getAttribute("requestName"));  
    }  
} 
//web.xml配置
<!-- 自定义ServletRquestListener的监听 --> 
<listener> 
    <listener-class>com.zhuoli.service.listener.ServletRquestListeners</listener-class> 
</listener> 

3.3 Session监听器

3.3.1 HttpSessionListener

HttpSessionListener用于对Session的整体状态(创建,销毁)进行监听。

  • sessionCreated:监听session的创建动作
  • sessionDestroyed:监听session的销毁动作
  • getSession:获取session

下面通过一个demo,实现对sessino创建销毁的监听。

public class MyListener implements HttpSessionListener {  
 
    ServletContext sc;
    ArrayList list = new ArrayList();  
    // 新建一个session时触发此操作  
    public void sessionCreated(HttpSessionEvent se) {  
        sc = se.getSession().getServletContext();  
        System.out.println("新建一个session");  
    }  
  
    // 销毁一个session时触发此操作  
    public void sessionDestroyed(HttpSessionEvent se) {  
        System.out.println("销毁一个session");  
        if (!list.isEmpty()) {  
            list.remove((String) se.getSession().getAttribute("userName"));  
            sc.setAttribute("list", list);  
        }  
    } 
}  

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>    
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee     
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">    
    <listener>    
        <listener-class>    
            com.zhuoli.service.listener.MyListener
        </listener-class>    
    </listener>    
    <!--默认的会话超时时间间隔,以分钟为单位  -->    
    <session-config>    
        <session-timeout>1</session-timeout>    
    </session-config>
</web-app>
//index.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
<%  
    String path = request.getContextPath();  
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";  
%>  
<html>  
    <body>  
        <%  
            session = request.getSession(false);  
            if (session != null)  
                session.invalidate();  
        %>  
        <form action="isOnline.jsp" method="post">  
            用户名:  
            <input type="text" name="uName" />  
            <input type="submit" value="上线">  
    </body>  
</html> 
//isOnline.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
<%  
String path = request.getContextPath();  
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";  
%>  
<html>  
  <body>  
    <%  
session=request.getSession();  
session.setAttribute("userName",request.getParameter("uName"));  
response.sendRedirect("showOnline.jsp");  
%>  
  </body>  
</html>
//showOnline.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
<%  
String path = request.getContextPath();  
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";  
%> 
<html>  
    <body>  
        <%  
ArrayList showList=(ArrayList)(getServletContext().getAttribute("list"));  
out.print("在线人数 "+showList.size()+"<br>");  
for(int i=0;i<showList.size();i++){  
out.print(showList.get(i)+"在线"+"<br>");  
}  
%>  
        <br>  
        <a href="index.jsp">退出</a>  
    </body>  
</html>

HttpSessionListener中sessionCreated是新建一个会话时候触发,也可以说是客户端第一次和服务器交互时候触发。 sessionDestroyed是会话销毁的时候触发,一般来说只有某个按钮触发进行销毁或者配置定时销毁。

3.3.2 HttpSessionAttributeListener

HttpSessionAttributeListener用于对session的属性(添加,删除,替换)进行监听。

  • attributeAdded:监听session attribute添加动作
  • attributeRemoved:监听session attribute删除动作
  • attributeReplaced:监听session attribute替换动作
  • getSession:获取session
  • getName:获取session attribute名称
  • getValue:获取session attribute值

下面通过一个demo,实现对session attribute的监听。

public class MyAttributeListener implements HttpSessionAttributeListener {  
    @Override  
    public void attributeAdded(HttpSessionBindingEvent event) {  
        String attributeName = event.getName();  
        Object attributeValue = event.getValue();  
        System.out.println("HttpSessionAttributeListener Attribute added : " + attributeName + " : "  
                + attributeValue);  
    }  
  
    @Override  
    public void attributeRemoved(HttpSessionBindingEvent event) {  
        String attributeName = event.getName();  
        Object attributeValue = event.getValue();  
        System.out.println("HttpSessionAttributeListener Attribute removed : " + attributeName + " : "  
                + attributeValue);  
    }  
  
    @Override  
    public void attributeReplaced(HttpSessionBindingEvent event) {  
        String attributeName = event.getName();  
        Object attributeValue = event.getValue();  
        System.out.println("Attribute replaced : " + attributeName + " : "  
                + attributeValue);  
    }  
}  
//web.xml配置监听器
<listener>  
    <listener-class>com.zhuoli.service.listener.MyAttributeListener</listener-class>  
</listener>  
//test.jsp
<html>
<body>  
    <%  
        session = request.getSession();  
        session.setAttribute("url", "51gjie.com"); //HttpSessionAttributeListener attributeAdded() is executed  
        session.setAttribute("url", "www.51gjie.com"); //attributeReplaced() is executed  
        session.removeAttribute("url"); //HttpSessionAttributeListener attributeRemoved() is executed  
    %>  
</body>  
</html>  

4. 理解Java Web三大组件

当我们进入Java Web开发后会发现,我们好像已经不再关心我们的程序是如何、从何处被调用的。我们写一个类,甚至从来没new过,它也可以运行起来。不想我们在开发JavaSE时,一切都是那么清晰,定义一个类,然后在main方法中实例化该类,最后进行方法调用。

所以当我们一开始接触Java Web开发时肯定是非常疑惑的,特别是接触了Java Web中一些列的组件Servlet/Filter/Listener后。没有main方法,也不需要new。只需要在web.xml进行配置后,就可以生效了。

那么我们可以想象,既然我们没有自己定义Servlet/Filter/Listener等组件访问的入口,那是不是有人替我们写了入口,并通过web.xml的配置进行了调用。答案是肯定的,Java Web应用跟普通应用一样,也是有访问入口的,而这个入口其实就是Tomcat(Servlet容器)替我们生成的。Tomcat根据我们的配置文件,生成我们定义的组件实例,并进行方法调用(这个过程可以称为“注入”、“回调”)。

假如我们想象Tomcat中存在这么一个main方法入口:

是不是我们定义的组件在这可以在这里生效了,其实实际上Tomcat也是通过上图的方式,实现我们自定义组件调用的,只不过考虑的东西更多,更加复杂,但是原理是一致的。

参考链接:

1. Java中Servlet Filter配置(web.xml详解)

2. Web三大组件-Filter

3. Java过滤器Filter的使用详解

4. java web 三大组件

5. Java中Servlet Listener监听器详解(原理)

6. JavaSchool——Servlet入门教程

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐