在Servlet规范中,同时使用web.xml
和注解(如@WebServlet
)为同一个Servlet类配置映射时,会导致容器创建两个独立的Servlet实例。
1. Servlet配置的两种方式
web.xml
配置:传统部署描述符,显式定义<servlet>
和<servlet-mapping>
。- 注解配置:Servlet 3.0+支持
@WebServlet
注解,直接在类上声明映射(例如@WebServlet("/b")
)。
2. 容器处理机制
- 独立注册:Servlet容器(如Tomcat)将
web.xml
和注解视为两个独立的配置源。 - 重复定义判定:容器不会检查两类配置是否指向同一个Servlet类。即使类名相同,也会被当作两个不同的Servlet定义处理。
// 注解配置(被视为Servlet定义1) @WebServlet(name = "myServlet", urlPatterns = "/b") public class MyServlet extends HttpServlet { ... }
<!-- web.xml配置(被视为Servlet定义2) --> <servlet> <servlet-name>myServlet</servlet-name> <servlet-class>com.example.MyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>myServlet</servlet-name> <url-pattern>/a</url-pattern> </servlet-mapping>
3. 为什么创建两个实例?
- 独立的配置项:容器会:
- 根据
web.xml
创建第一个实例,绑定URL/a
。 - 根据注解创建第二个实例,绑定URL
/b
。
- 根据
- 实例隔离:每个实例有独立的:
ServletConfig
对象(含初始化参数)。- 生命周期(
init()
和destroy()
各自调用)。 - 内存空间(成员变量不共享)。
4. 验证实验
在Servlet中添加日志:
public class MyServlet extends HttpServlet {
public MyServlet() {
System.out.println("Servlet实例创建: " + this.hashCode());
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("处理请求的实例: " + this.hashCode());
}
}
- 访问
/a
和/b
时,控制台会输出两个不同的hashCode,证明是两个实例。
5. 规范依据
- Servlet 3.1规范 第4.1节:
“部署描述符(web.xml)和注解的配置是叠加的。如果同一个Servlet类被两种方式声明,容器会将其视为两个独立的Servlet定义。”
6. 解决方案:避免重复配置
- 只使用一种方式:选择
web.xml
或 注解,不要混用。 - 禁用注解扫描:在
web.xml
中添加<metadata-complete>true</metadata-complete>
,强制容器忽略所有注解。<web-app ... metadata-complete="true"> <!-- 容器不再扫描@WebServlet --> </web-app>
总结
访问路径 | 配置来源 | 结果 |
---|---|---|
/a | web.xml | 实例化第一个MyServlet 对象 |
/b | 注解 | 实例化第二个MyServlet 对象 |
始终遵循单一配置原则,可避免此问题。