1.web.xml配置
<?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_4_0.xsd"
version="4.0">
<display-name>servlet-test</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<context-param>
<param-name>context-param-1</param-name>
<param-value>context-value-1</param-value>
</context-param>
<context-param>
<param-name>context-param-2</param-name>
<param-value>context-value-2</param-value>
</context-param>
<!-- 配置listener -->
<listener>
<listener-class>com.example.listener.HelloListener</listener-class>
</listener>
<!-- 配置filter -->
<filter>
<filter-name>helloFilter</filter-name>
<filter-class>com.example.fileter.HelloFilter</filter-class>
<init-param>
<param-name>filter-param-1</param-name>
<param-value>filter-value-1</param-value>
</init-param>
<init-param>
<param-name>filter-param-2</param-name>
<param-value>filter-value-2</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>helloFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 配置servlet -->
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.example.servlet.HelloServlet</servlet-class>
<init-param>
<param-name>servlet-param-1</param-name>
<param-value>servlet-value-1</param-value>
</init-param>
<init-param>
<param-name>servlet-param-2</param-name>
<param-value>servlet-value-2</param-value>
</init-param>
<!--
servlet启动加载,servlet原本是第一次访问创建对象;
load-on-startup:服务器启动的时候创建对象;值越小优先级越高,越先创建对象
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/helloServlet</url-pattern>
</servlet-mapping>
</web-app>
2.java代码
Listener
public class HelloListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("- - - HelloListener contextInitialized 1 - - -");
ServletContext servletContext = sce.getServletContext();
Enumeration<String> enumeration = servletContext.getInitParameterNames();
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = servletContext.getInitParameter(key);
System.out.println(String.format("key: %s value: %s", key, value));
}
String contextPath = servletContext.getContextPath();
String rootPath = servletContext.getRealPath("/");
System.out.println(String.format("contextPath: %s", contextPath));
System.out.println(String.format("rootPath: %s", rootPath));
System.out.println("- - - HelloListener contextInitialized 2 - - -");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("- - - HelloListener contextDestroyed - - -");
}
}
Filter
public class HelloFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("- - - HelloFilter init 1 - - -");
Enumeration<String> enumeration = filterConfig.getInitParameterNames();
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = filterConfig.getInitParameter(key);
System.out.println(String.format("key: %s value: %s", key, value));
}
System.out.println("- - - HelloFilter init 2 - - -");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 进入servlet之前调用
System.out.println("- - - HelloFilter doFilter 1 - - -");
chain.doFilter(request, response);
// 处理之后调用
System.out.println("- - - HelloFilter doFilter 2 - - -");
}
@Override
public void destroy() {
System.out.println("- - - HelloFilter destroy - - -");
}
}
Servlet
public class HelloServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("- - - HelloServlet init 1 - - -");
Enumeration<String> enumeration = this.getInitParameterNames();
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = this.getInitParameter(key);
System.out.println(String.format("key: %s value: %s", key, value));
}
System.out.println("- - - HelloServlet init 2 - - -");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("- - - HelloServlet doGet 1 - - -");
System.out.println(request.getContextPath());
response.sendRedirect(request.getContextPath() + "/success.html");
System.out.println("- - - HelloServlet doGet 2 - - -");
}
}
3.结果
启动的顺序为 Listener -> Filter -> Servlet
结果:
13-Aug-2023 18:41:39.727 信息 [main] org.apache.catalina.core.StandardEngine.startInternal 正在启动 Servlet 引擎:[Apache Tomcat/9.0.78]
13-Aug-2023 18:41:39.744 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["http-nio-8080"]
13-Aug-2023 18:41:39.766 信息 [main] org.apache.catalina.startup.Catalina.start [91]毫秒后服务器启动
Connected to server
[2023-08-13 06:41:40,081] Artifact servlet-demo:war exploded: Artifact is being deployed, please wait...
- - - HelloListener contextInitialized 1 - - -
key: context-param-1 value: context-value-1
key: context-param-2 value: context-value-2
contextPath: /servlet-demo
rootPath: /home/zxm/IdeaProjects/jdk8-demo/out/artifacts/servlet_demo_war_exploded/
- - - HelloListener contextInitialized 2 - - -
- - - HelloFilter init 1 - - -
key: filter-param-1 value: filter-value-1
key: filter-param-2 value: filter-value-2
- - - HelloFilter init 2 - - -
- - - HelloServlet init 1 - - -
key: servlet-param-1 value: servlet-value-1
key: servlet-param-2 value: servlet-value-2
- - - HelloServlet init 2 - - -
[2023-08-13 06:41:40,556] Artifact servlet-demo:war exploded: Artifact is deployed successfully
[2023-08-13 06:41:40,556] Artifact servlet-demo:war exploded: Deploy took 475 milliseconds
- - - HelloFilter doFilter 1 - - -
- - - HelloServlet doGet 1 - - -
/servlet-demo
- - - HelloServlet doGet 2 - - -
- - - HelloFilter doFilter 2 - - -
- - - HelloFilter doFilter 1 - - -
- - - HelloFilter doFilter 2 - - -
/opt/apache-tomcat-9.0.78/bin/catalina.sh stop
13-Aug-2023 18:42:00.110 信息 [main] org.apache.catalina.core.StandardServer.await 通过关闭端口接收到有效的关闭命令。正在停止服务器实例。
13-Aug-2023 18:42:00.111 信息 [main] org.apache.coyote.AbstractProtocol.pause 暂停ProtocolHandler["http-nio-8080"]
13-Aug-2023 18:42:00.115 信息 [main] org.apache.catalina.core.StandardService.stopInternal 正在停止服务[Catalina]
13-Aug-2023 18:42:00.128 信息 [main] org.apache.coyote.AbstractProtocol.stop 正在停止ProtocolHandler ["http-nio-8080"]
- - - HelloFilter destroy - - -
- - - HelloListener contextDestroyed - - -
13-Aug-2023 18:42:00.140 信息 [main] org.apache.coyote.AbstractProtocol.destroy 正在摧毁协议处理器 ["http-nio-8080"]
4.原理
1) 解析 web.xml,配置到 context
ContextConfig#webConfig() 方法解析 web.xml
ContextConfig#configureContext(WebXml webxml) 把 filter、listener、servlet 保存到 context
filter 有:com.example.fileter.HelloFilter
listener 有:com.example.listener.HelloListener
servlet 有:org.apache.catalina.servlets.DefaultServlet、org.apache.jasper.servlet.JspServlet、com.example.servlet.HelloServlet
servletMapping 有:".jspx" -> "jsp"、".jsp" -> "jsp"、"/helloServlet" -> "helloServlet"、"/" -> "default"
context 是 StandardContext、wrapper 是 StandardWrapper
// ContextConfig.java
private void configureContext(WebXml webxml) {
...
for (FilterDef filter : webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
for (FilterMap filterMap : webxml.getFilterMappings()) {
context.addFilterMap(filterMap);
}
for (String listener : webxml.getListeners()) {
context.addApplicationListener(listener);
}
...
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
if (servlet.getLoadOnStartup() != null) {
wrapper.setLoadOnStartup(servlet.getLoadOnStartup().intValue());
}
if (servlet.getEnabled() != null) {
wrapper.setEnabled(servlet.getEnabled().booleanValue());
}
wrapper.setName(servlet.getServletName());
wrapper.setServletClass(servlet.getServletClass());
context.addChild(wrapper);
}
...
}
2) StandardContext 启动
StandardContext#start() 方法
// StandardContext.java
@Override
protected synchronized void startInternal() throws LifecycleException {
...
// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// Configure and call application filters
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" servlets
if (ok) {
if (!loadOnStartup(findChildren())) {
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
...
}
实例化 listener 有:com.example.listener.HelloListener、org.apache.tomcat.websocket.server.WsSessionListener、org.apache.tomcat.websocket.server.WsContextListener
实例化 filter 有:org.apache.tomcat.websocket.server.WsFilter、com.example.fileter.HelloFilter
示例化 servlet 有:org.apache.catalina.servlets.DefaultServlet、org.apache.jasper.servlet.JspServlet、com.example.servlet.HelloServlet
// StandardContext.java
public boolean listenerStart() {
...
// Instantiate the required listeners
String listeners[] = findApplicationListeners();
Object results[] = new Object[listeners.length];
boolean ok = true;
for (int i = 0; i < results.length; i++) {
if (getLogger().isDebugEnabled()) {
getLogger().debug(" Configuring event listener class '" + listeners[i] + "'");
}
try {
String listener = listeners[i];
results[i] = getInstanceManager().newInstance(listener);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString("standardContext.applicationListener", listeners[i]), t);
ok = false;
}
}
...
// Sort listeners in two arrays
List<Object> eventListeners = new ArrayList<>();
List<Object> lifecycleListeners = new ArrayList<>();
for (Object result : results) {
if ((result instanceof ServletContextAttributeListener) ||
(result instanceof ServletRequestAttributeListener) || (result instanceof ServletRequestListener) ||
(result instanceof HttpSessionIdListener) || (result instanceof HttpSessionAttributeListener)) {
eventListeners.add(result);
}
if ((result instanceof ServletContextListener) || (result instanceof HttpSessionListener)) {
lifecycleListeners.add(result);
}
}
// Listener instances may have been added directly to this Context by
// ServletContextInitializers and other code via the pluggability APIs.
// Put them these listeners after the ones defined in web.xml and/or
// annotations then overwrite the list of instances with the new, full
// list.
eventListeners.addAll(Arrays.asList(getApplicationEventListeners()));
setApplicationEventListeners(eventListeners.toArray());
for (Object lifecycleListener : getApplicationLifecycleListeners()) {
lifecycleListeners.add(lifecycleListener);
if (lifecycleListener instanceof ServletContextListener) {
noPluggabilityListeners.add(lifecycleListener);
}
}
setApplicationLifecycleListeners(lifecycleListeners.toArray());
...
}
public boolean filterStart() {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Starting filters");
}
// Instantiate and record a FilterConfig for each defined filter
boolean ok = true;
synchronized (filterConfigs) {
filterConfigs.clear();
for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
if (getLogger().isDebugEnabled()) {
getLogger().debug(" Starting filter '" + name + "'");
}
try {
ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString("standardContext.filterStart", name), t);
ok = false;
}
}
}
return ok;
}
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer,ArrayList<Wrapper>> map = new TreeMap<>();
for (Container child : children) {
Wrapper wrapper = (Wrapper) child;
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0) {
continue;
}
Integer key = Integer.valueOf(loadOnStartup);
map.computeIfAbsent(key, k -> new ArrayList<>()).add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
} catch (ServletException e) {
getLogger().error(
sm.getString("standardContext.loadOnStartup.loadException", getName(), wrapper.getName()),
StandardWrapper.getRootCause(e));
// NOTE: load errors (including a servlet that throws
// UnavailableException from the init() method) are NOT
// fatal to application startup
// unless failCtxIfServletStartFails="true" is specified
if (getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
3) 匹配请求
CoyoteAdapter#service() 方法处理请求
CoyoteAdapter#postParseRequest() 根据 url 找到 servlet
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
执行
// CoyoteAdapter.java
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
try {
// Parse and set Catalina and configuration specific
// request parameters
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
// check valves if we support async
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
}
...
} catch (IOException e) {
// Ignore
}
...
}
protected boolean postParseRequest(org.apache.coyote.Request req, Request request, org.apache.coyote.Response res,
Response response) throws IOException, ServletException {
...
while (mapRequired) {
connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData());
}
...
}
Mapper#internalMapExactWrapper() 方法,根据 url 找到 MappedWrapper (包含 url 对应的 StandardWrapper,也就是 servlet),赋值给 MappingData
此时 request 的 mappingData 成员变量包含匹配的 servlet
// Mapper.java
public void map(MessageBytes host, MessageBytes uri, String version, MappingData mappingData) throws IOException {
if (host.isNull()) {
String defaultHostName = this.defaultHostName;
if (defaultHostName == null) {
return;
}
host.setChars(MessageBytes.EMPTY_CHAR_ARRAY, 0, 0);
host.getCharChunk().append(defaultHostName);
}
host.toChars();
uri.toChars();
internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}
private void internalMap(CharChunk host, CharChunk uri, String version, MappingData mappingData)
throws IOException {
...
// Wrapper mapping
if (!contextVersion.isPaused()) {
internalMapWrapper(contextVersion, uri, mappingData);
}
...
}
private void internalMapWrapper(ContextVersion contextVersion, CharChunk path, MappingData mappingData)
throws IOException {
...
// Rule 1 -- Exact Match
MappedWrapper[] exactWrappers = contextVersion.exactWrappers;
internalMapExactWrapper(exactWrappers, path, mappingData);
...
}
4) 执行
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
执行
// CoyoteAdapter.java
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception {
try {
// Parse and set Catalina and configuration specific
// request parameters
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
// check valves if we support async
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
}
...
} catch (IOException e) {
// Ignore
}
...
}
执行顺序:
- StandardEngineValve#invoke()
- AccessLogValve#invoke()
- ErrorReportValve#invoke()
- StandardHostValve#invoke()
- NonLoginAuthenticator#invoke()
- StandardContextValve#invoke()
- StandardWrapperValve#invoke()
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
创建过滤器链
filterChain.doFilter(request.getRequest(), response.getResponse())
执行过滤器链
// StandardWrapperValve.java
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {
...
StandardWrapper wrapper = (StandardWrapper) getContainer();
Servlet servlet = null;
Context context = (Context) wrapper.getParent();
...
// Create the filter chain for this request
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
// Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
Container container = this.container;
try {
if ((servlet != null) && (filterChain != null)) {
// Swallow output if needed
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
filterChain.doFilter(request.getRequest(), response.getResponse());
}
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
context.getLogger().info(log);
}
}
} else {
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
filterChain.doFilter(request.getRequest(), response.getResponse());
}
}
}
}
...
}
创建 ApplicationFilterChain,把 ApplicationFilterConfig 添加到 ApplicationFilterChain 的 filters 成员变量中
// ApplicationFilterFactory.java
public static ApplicationFilterChain createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet) {
// If there is no servlet to execute, return null
if (servlet == null) {
return null;
}
// Create and initialize a filter chain object
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request) request;
if (Globals.IS_SECURITY_ENABLED) {
// Security: Do not recycle
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0)) {
return filterChain;
}
// Acquire the information we will need to match filter mappings
DispatcherType dispatcher = (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);
String requestPath = null;
Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
if (attribute != null) {
requestPath = attribute.toString();
}
String servletName = wrapper.getName();
// Add the relevant path-mapped filters to this filter chain
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMap, requestPath)) {
continue;
}
ApplicationFilterConfig filterConfig =
(ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Add filters that match on servlet name second
for (FilterMap filterMap : filterMaps) {
if (!matchDispatcher(filterMap, dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMap, servletName)) {
continue;
}
ApplicationFilterConfig filterConfig =
(ApplicationFilterConfig) context.findFilterConfig(filterMap.getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Return the completed filter chain
return filterChain;
}
执行所有 filter#doFilter() 方法后,执行 servlet.service(request, response) 方法
// ApplicationFilterChain.java
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (Globals.IS_SECURITY_ENABLED) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
java.security.AccessController.doPrivileged((java.security.PrivilegedExceptionAction) () -> {
internalDoFilter(req, res);
return null;
});
} catch (PrivilegedActionException pe) {
Exception e = pe.getException();
if (e instanceof ServletException) {
throw (ServletException) e;
} else if (e instanceof IOException) {
throw (IOException) e;
} else if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new ServletException(e.getMessage(), e);
}
}
} else {
internalDoFilter(request, response);
}
}
private void internalDoFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() &&
"false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if (Globals.IS_SECURITY_ENABLED) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal = ((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[] { req, res, this };
SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !servletSupportsAsync) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
// Use potentially wrapped request from this point
if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal = ((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[] { req, res };
SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args, principal);
} else {
servlet.service(request, response);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}