Questions:#
- When is the filterChain executed?
- Where do the filters in the filterChain come from?
- When does the standardContext start collecting the filter collection?
1. When is the filterChain executed?#
In the org.apache.catalina.core.StandardWrapperValve
class, there is a method as follows:
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Omitted large amounts of unrelated code
Servlet servlet = null;
Context context = (Context) wrapper.getParent();
// Omitted large amounts of unrelated code
try {
if (!unavailable) {
// 1. create servlet
servlet = wrapper.allocate();
}
} catch(){
...
}
// 2. Create the filter chain for this request
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
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 {
// execute doFilter method of the FilterChain object.
filterChain.doFilter(request.getRequest(),
response.getResponse());
}
} finally {
}
} else {
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
}
}
} catch(){
} finally {
// Release the filter chain (if any) for this request
if (filterChain != null) {
filterChain.release();
}
// Deallocate the allocated servlet instance
try {
if (servlet != null) {
wrapper.deallocate(servlet);
}
}
}
}
We will look back at how it creates the filter chain object later; first, let's see how it executes the filter chain. The execution flow of the filter chain object is implemented as follows:
public final class ApplicationFilterChain implements FilterChain {
// Current executing filter index
private int pos = 0;
// Matched filter count
private int n = 0;
// Servlet
private Servlet servlet = null;
// Matched filter arrays
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// Security has been turned on
if( Globals.IS_SECURITY_ENABLED ) {
// ... security bean filter
} else {
// Execute filter logic
internalDoFilter(request,response);
}
}
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Loop and call the next filter if there is one
if (pos < n) {
// pos index ++
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
if( Globals.IS_SECURITY_ENABLED ) {
} else {
// Execute the doFilter() of the current filter in the filter chain
filter.doFilter(request, response, this);
}
} catch(){
...
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
} else {
// If there are no filters to be executed, the servlet's service method will be executed, which contains the business logic that we have written.
servlet.service(request, response);
}
} catch(){
...
} finally{
...
}
}
}
From the source code of the filterChain class, it can be seen that the underlying structure contains the matched filter array, meaning that the added matching filter objects are ordered, which is determined at the time of addition.
So when is it added?
2. Where do the filters in the filterChain come from?#
In fact, in the org.apache.catalina.core.StandardWrapperValve
class's invoke method:
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
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();
// Get an array of all the filters registered in the ServletContext object
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 (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Add filters that match on servlet name second
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
}
// Return the completed filter chain
return filterChain;
}
It can be seen that when creating the filterChain object, it retrieves all registered filters from the ServletContext and adds the necessary ones to the filterChain object created for this request. Moreover, the registered filters in the servletContext object are themselves an array (which is inherently ordered), so the matching during traversal is also ordered.
3. When does the ServletContext start collecting the array?#
The filterMaps are in the StandardContext class:
public class StandardContext extends ContainerBase
implements Context, NotificationEmitter {
private final ContextFilterMaps filterMaps = new ContextFilterMaps();
//
@Override
public void addFilterMap(FilterMap filterMap) {
validateFilterMap(filterMap);
// Add this filter mapping to our registered set
filterMaps.add(filterMap);
fireContainerEvent("addFilterMap", filterMap);
}
}
When starting Tomcat, we can check the call order through the stack:
StandardContext.startInternal()
-->fireLifecycleEvent()
-->ContextConfig.lifecycleEvent()
-->ContextConfig.lifecycleEventcon
-->ContextConfig.con.figureStart()
-->ContextConfig.webConfig()
Looking at the method:
protected void webConfig() {
if (ok) {
configureContext(webXml);
}
}
private void configureContext(WebXml webxml) {
for (FilterMap filterMap : webxml.getFilterMappings()) {
context.addFilterMap(filterMap);
}
}
Here, the web.xml is used to collect all filter mappings into a set, and then the collected filter set is converted into an array and set to the StandardContext object.