在Java Web项目的web.xml配置中,Listener、Filter、Servlet的加载顺序是由Servlet规范明确规定的,和它们在配置文件中的排列顺序没有直接关系。这个顺序直接影响项目初始化阶段的资源加载和请求处理流程,是Java Web开发的基础知识点。

一、明确的加载顺序规则
Servlet规范中规定的加载优先级从高到低依次为:Listener → Filter → Servlet,具体规则如下:
- Listener会在Servlet容器启动,web应用初始化阶段最先被加载和初始化,不同Listener之间按照在web.xml中的注册顺序初始化
- Filter的加载和初始化在所有的Listener完成初始化之后进行,如果存在多个Filter,按照在web.xml中的注册顺序依次初始化
- Servlet的加载默认是在第一次被请求访问时才会初始化,不过如果配置了<load-on-startup>标签,则会在Filter初始化完成后按数值从小到大依次初始化
二、web.xml配置示例
下面是一个符合规范的web.xml配置示例,包含Listener、Filter、Servlet三类组件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 配置Listener -->
<listener>
<listener-class>com.example.MyListener</listener-class>
</listener>
<!-- 配置Filter -->
<filter>
<filter-name>firstFilter</filter-name>
<filter-class>com.example.FirstFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>firstFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>secondFilter</filter-name>
<filter-class>com.example.SecondFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>secondFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置Servlet -->
<servlet>
<servlet-name>demoServlet</servlet-name>
<servlet-class>com.example.DemoServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>demoServlet</servlet-name>
<url-pattern>/demo</url-pattern>
</servlet-mapping>
</web-app>
三、对应组件代码验证
1. Listener代码
实现ServletContextListener接口,在上下文初始化时打印日志:
package com.example;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("Listener初始化完成,ServletContext已创建");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Listener销毁,ServletContext即将关闭");
}
}
2. Filter代码
两个Filter分别打印初始化日志,验证加载顺序:
package com.example;
import javax.servlet.*;
import java.io.IOException;
public class FirstFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("FirstFilter初始化完成");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("FirstFilter销毁");
}
}
package com.example;
import javax.servlet.*;
import java.io.IOException;
public class SecondFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("SecondFilter初始化完成");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("SecondFilter销毁");
}
}
3. Servlet代码
配置<load-on-startup>为1,让Servlet在启动时初始化:
package com.example;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class DemoServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("DemoServlet初始化完成");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("demo servlet response");
}
}
四、启动后的日志输出
启动项目后,控制台会按照以下顺序输出日志:
Listener初始化完成,ServletContext已创建
FirstFilter初始化完成
SecondFilter初始化完成
DemoServlet初始化完成
这个输出完全符合我们之前提到的加载顺序规则,即使把web.xml中Filter的配置放到Listener前面,输出的顺序也不会改变。
五、注意事项
- 如果Filter没有配置<filter-mapping>,那么该Filter不会被加载初始化
- Servlet的<load-on-startup>数值越小,优先级越高,越早被初始化,如果数值相同则按照配置顺序初始化
- 销毁顺序和加载顺序相反,即Servlet先销毁,然后是Filter,最后是Listener