Note: This article source code analysis based on Tomcat 9.0.43, source gitee warehouse warehouse address: gitee.com/funcy/tomca… .
This article is the third part of the Tomcat source code analysis, which analyzes the Context startup process.
Tomcat startup flowchart:
As you can see, the Context is started by Host, in Host#start(…) Method, Context#init(…) is called. With Context# start (…). Method, Context#init(…) Nothing concrete is done, we focus on Context#start(…) .
In Tomcat, Context is implemented as StandardContext, and this paper mainly analyzes the startup process of StandardContext.
1. StandardContext#startInternal
To analyze the Context startup process, we go directly to the StandardContext#startInternal method:
protected synchronized void startInternal(a) throws LifecycleException {
// Omitted some code.try {
if (ok) {
// Omitted some code.// 1. The CONFIGURE_START event is triggered
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
// 2. Start child, the child of context is Wrapper
for (Container child : findChildren()) {
if(! child.getState().isAvailable()) { child.start(); }}}// Omitted some code./ / 3. ServletContainerInitializers processing
for(Map.Entry<ServletContainerInitializer, Set<Class<? >>> entry : initializers.entrySet()) {try {
/ / execution ServletContainerInitializers# onStartup (...). methods
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break; }}// 4. Load Listener
if (ok) {
if(! listenerStart()) { log.error(sm.getString("standardContext.listenerFail"));
ok = false; }}// 5. Load filter
if (ok) {
if(! filterStart()) { log.error(sm.getString("standardContext.filterFail"));
ok = false; }}// 6. Handle the servlet, load and initialize the child of the Context as Wrapper, used to store the servlet
if (ok) {
if(! loadOnStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail"));
ok = false; }}}finally {
// Omitted some code. }// Omitted some code. }Copy the code
The StandardContext#startInternal method is very long, and we’ll focus on servlet-related operations here. The process for this method is as follows:
- The trigger
CONFIGURE_START
Events, which are resolved hereweb.xml
; - Start the
Context
The son ofContainer
, that is,Wrapper
But judging from the code,Wrapper
Initialization and startup do not do anything, so I will not analyze; - To deal with
ServletContainerInitializers
, which is executionServletContainerInitializers
The implementation classonStartup(...)
Methods; - loading
servlet
Components:Listener
- loading
servlet
Components:filter
- loading
servlet
Components:servlet
Ok, so this article looks at these processes one by one.
2. Triggering events:LifecycleBase#fireLifecycleEvent(...)
Let’s look at the action that triggers the CONFIGURE_START event with the code:
// 1. The CONFIGURE_START event is triggered
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
Copy the code
Let’s follow:
protected void fireLifecycleEvent(String type, Object data) {
LifecycleEvent event = new LifecycleEvent(this, type, data);
for(LifecycleListener listener : lifecycleListeners) { listener.lifecycleEvent(event); }}Copy the code
This loops through all LifecycleListener, calling LifecycleListener#lifecycleEvent(…) one by one Note that LifecycleListener is provided by Tomcat itself. Note the difference between the listener provided by the servlet.
What are these lifecycleListeners? Recall that we did something like this in Demo01#test:
public class Demo01 {
@Test
public void test(a) throws Exception {...// this Context is StandardContext
Context context = tomcat.addContext("", docBase);
// Get lifecycleListener ContextConfig
LifecycleListener lifecycleListener = (LifecycleListener)
Class.forName(tomcat.getHost().getConfigClass())
.getDeclaredConstructor().newInstance();
// Add the resulting lifecycleListener to the contextcontext.addLifecycleListener(lifecycleListener); . }}Copy the code
In the above code, tomcat.addContext(…) LifecycleListener gets ContextConfig, which can be seen in StandardHost:
private String configClass =
"org.apache.catalina.startup.ContextConfig";
Copy the code
So, this code adds a LifecycleListener of type ContextConfig to StandardContext.
LifecycleListener is mentioned because ContextConfig is so important! Following the execution of the code, we enter ContextConfig#lifecycleEvent(…) Methods:
public void lifecycleEvent(LifecycleEvent event) {
// Identify the context we are associated with
try {
context = (Context) event.getLifecycle();
} catch (ClassCastException e) {
log.error(sm.getString("contextConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
// Handle startup events
configureStart();
} else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
// Handle events before startup
beforeStart();
} else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) {
// Restore docBase for management tools
if(originalDocBase ! =null) { context.setDocBase(originalDocBase); }}else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) {
// Handle the stop event
configureStop();
} else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) {
// Handle the initialization event
init();
} else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
// Process the destruction eventdestroy(); }}Copy the code
ContextConfig#lifecycleEvent(…) There are a lot of events to handle, but we’ll focus on CONFIGURE_START_EVENT, followed by ContextConfig#configureStart(…) ContextConfig#webConfig(…) Methods:
protected void webConfig(a) {
// Omit the "parse web.xml" code./ / find ServletContainerInitializer implementation
if (ok) {
processServletContainerInitializers();
}
if(! webXml.isMetadataComplete() || typeInitializerMap.size() >0) {
// The @handlestypes annotation will be handled here
processClasses(webXml, orderedFragments);
}
// omit the "merge web.xml" code.// Apply configuration. Apply configuration information from webXml to Context
configureContext(webXml);
// omit the code for "find static resources in jar packages"./ / add ServletContainerInitializer to the Context
if (ok) {
for(Map.Entry<ServletContainerInitializer, Set<Class<? >>> entry : initializerClassMap.entrySet()) {if (entry.getValue().isEmpty()) {
context.addServletContainerInitializer(entry.getKey(), null);
} else{ context.addServletContainerInitializer( entry.getKey(), entry.getValue()); }}}}Copy the code
This method is mainly dealing with web configuration, mainly parse web. The XML file, but doesn’t work in our project, not analysis, but in our project to ServletContainerInitializer, this class is loaded, here we mainly to analyze it.
2.1 findServletContainerInitializer
We recall the ServletContainerInitializer implementation, in our configuration, We custom ServletContainerInitializer in the meta-inf/services/javax.mail servlet. ServletContainerInitializer file, the contents of this file is:
org.apache.tomcat.demo.MyServletContainerInitializer
Copy the code
This is our custom ServletContainerInitializer package name. The name of the class, we enter the processServletContainerInitializers () to see how it is looking for:
public class ContextConfig implements LifecycleListener {
/ * * the current type is introduced by which ServletContainerInitializer instance * /
protected finalMap<Class<? >, Set<ServletContainerInitializer>> typeInitializerMap =new HashMap<>();
/ * * instance ServletContainerInitializer introduced type, type can have multiple * /
protected finalMap<ServletContainerInitializer, Set<Class<? >>> initializerClassMap =newLinkedHashMap<>(); ./ * * * handle ServletContainerInitializer load * /
protected void processServletContainerInitializers(a) {
List<ServletContainerInitializer> detectedScis;
try {
WebappServiceLoader<ServletContainerInitializer> loader
= new WebappServiceLoader<>(context);
// The load is done here
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {
...
ok = false;
return;
}
// Collect the annotation class
for (ServletContainerInitializer sci : detectedScis) {
initializerClassMap.put(sci, new HashSet<>());
HandlesTypes ht;
try {
/ / get ServletContainerInitializer @ HandlesTypes annotation
ht = sci.getClass().getAnnotation(HandlesTypes.class);
} catch (Exception e) {
...
continue; }...// Get the type specified by @handlestypesClass<? >[] types = ht.value(); .for(Class<? > type : types) {// The type in @handlestypes can also be an annotation
if (type.isAnnotation()) {
handlesTypesAnnotations = true;
} else {
handlesTypesNonAnnotations = true;
}
/ / save
Set<ServletContainerInitializer> scis = typeInitializerMap.get(type);
if (scis == null) {
scis = newHashSet<>(); typeInitializerMap.put(type, scis); } scis.add(sci); }}}... }Copy the code
This method is used to handle ServletContainerInitializer loaded, first call WebappServiceLoader# load (…). Load the javax.mail. Servlet. ServletContainerInitializer and instantiate, then process ServletContainerInitializer @ HandlesTypes annotation on the class.
1. The loadServletContainerInitializer
Let’s look at WebappServiceLoader#load(…) Method:
public List<T> load(Class<T> serviceType) throws IOException {
/ / SERVICES value is the meta-inf/SERVICES/", the incoming serviceType for "ServletContainerInitializer. Class"
/ / so configFile value as "meta-inf/services/javax.mail. Servlet. ServletContainerInitializer"
String configFile = SERVICES + serviceType.getName();
/ / for this
ClassLoader loader = context.getParentClassLoader();
Enumeration<URL> containerResources;
/ / use this lookup meta-inf/services/javax.mail servlet. ServletContainerInitializer file
if (loader == null) {
containerResources = ClassLoader.getSystemResources(configFile);
} else {
containerResources = loader.getResources(configFile);
}
LinkedHashSet<String> containerServiceClassNames = new LinkedHashSet<>();
Set<URL> containerServiceConfigFiles = new HashSet<>();
while (containerResources.hasMoreElements()) {
URL containerServiceConfigFile = containerResources.nextElement();
containerServiceConfigFiles.add(containerServiceConfigFile);
// Read the contents of a file
parseConfigFile(containerServiceClassNames, containerServiceConfigFile);
}
// omit the "read from jar" code.// instantiate serviceClass
return loadServices(serviceType, containerServiceClassNames);
}
Copy the code
The work done by this method is quite clear, is from the classpath, jar package to check the meta-inf/services/javax.mail servlet. ServletContainerInitializer file, then read the content, finally using reflection instantiation, I’m not going to go into this one.
2. @HandlesTypes
The function of the
Let’s go back to ContextConfig# processServletContainerInitializers method, after loading the custom implementations of ServletContainerInitializer, The rest of the code revolves around @handlestypes, and all this work ends up with just two structures:
/ * * the current type is introduced by which ServletContainerInitializer instance * /Map<Class<? >, Set<ServletContainerInitializer>> typeInitializerMap;/ * * instance ServletContainerInitializer introduced type, type can have multiple * /Map<ServletContainerInitializer, Set<Class<? >>> initializerClassMap;Copy the code
This operation is just some basic collection operation, so I don’t need to analyze it too much, but we’ll use these two structures later, and when we use them, we just need to know that this is where the content is loaded.
So what exactly is @Handlestypes? It looks like this:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
/ * * *@return array of classes
*/Class<? >[] value(); }Copy the code
This annotation is a single property of type Class array and is commented as follows:
This annotation is used to declare an array of application classes which are passed to a {@link javax.servlet.ServletContainerInitializer}.
The notation used to declare the application class of arrays, these applications will be passed to the {@ link javax.mail. Servlet. ServletContainerInitializer}.
This comment doesn’t make much sense. We don’t use @handlestypes in our code. Here’s a look at spring in action:
/ / for WebApplicationInitializer HandlesTypes specified type
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/ * * * webAppInitializerClasses: here is the incoming WebApplicationInitializer * /
@Override
public void onStartup(@NullableSet<Class<? >> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if(webAppInitializerClasses ! =null) {
// This for loop handles some operations
for(Class<? > waiClass : webAppInitializerClasses) {if(...). {try {
// Reflection instantiationinitializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance()); }... }}}.../ / call the method that WebApplicationInitializer traversal
for(WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); }}}Copy the code
In spring, the realization ServletContainerInitializer SpringServletContainerInitializer class, function is as follows:
SpringServletContainerInitializer
in@HandlesTypes
Type is specified asWebApplicationInitializer
;- And when I do that,
onStartup(...)
methodswebAppInitializerClasses
And the incomingSet<Class<? >>
Type in theClass
forWebApplicationInitializer.class
; - Instantiate incoming using reflection
WebApplicationInitializer
And call itsonStartup(...)
Methods; - So, in Spring, we just want to implement
WebApplicationInitializer
Rewrite theonStartup(...)
Method can also be implementedservlet
The load is not required inMETA-INF/services
What files to add to the folder.
Here, should understand @ HandlesTypes role, it is used to filter the Class, we can use it to make the specified Class introduced to ServletContainerInitializer# onStartup (…). Methods.
2.2 Processing Classes:processClasses(...)
Let’s go back to ContextConfig#webConfig(…)
protected void webConfig(a) {...if(! webXml.isMetadataComplete() || typeInitializerMap.size() >0) {
// The @handlestypes annotation will be handled hereprocessClasses(webXml, orderedFragments); }... }Copy the code
This analysis of the @handlestypes annotation raises the question: where are these classes filtered? Do you need to do Class loading ahead of time (get the Class object of the Class and determine the type)? Tomcat handles @handlestypes in processClasses(…) In the method, let’s go on.
Follow processClasses (…). ContextConfig#processAnnotationsStream(…) Methods:
protected void processAnnotationsStream(InputStream is, WebXml fragment,
boolean handlesTypesOnly, Map<String,JavaClassCacheEntry> javaClassCache)
throws ClassFormatException, IOException {
// The incoming is is the corresponding input stream to the class file
ClassParser parser = new ClassParser(is);
JavaClass clazz = parser.parse();
// Check the type to see if it matches the type specified by '@handlestypes'
checkHandlesTypes(clazz, javaClassCache);
// If it is handlesTypesOnly, the execution ends here
if (handlesTypesOnly) {
return;
}
// Bonus: found @webServlet, @webfilter, @webListener processing
processClass(fragment, clazz);
}
Copy the code
Let’s analyze the implementation of this method in detail.
1. ClassParser
At the beginning of this method, we create the ClassParser to parse the input stream corresponding to the class file. Let’s look at the ClassParser:
/** * Wrapper class that parses a given Java .class file. The method parse returns a * JavaClass object on success. When an I/O error or an * inconsistency occurs an appropriate Exception is propagated back to * the caller. * * Resolves the wrapper class for the given java.class file. On success, the method parse returns the * JavaClass object. When an IO error or inconsistency occurs, * propagates the appropriate exception back to the caller. * * The structure and the names comply, except for a few conveniences, * exactly with the < A href = "http://docs.oracle.com/javase/specs/" > * the JVM specification 1.0 < / A >, See this paper for * Further details about the structure of a bytecode file. * * Except for some convenience, the structure and name * fully comply with the JVM specification 1.0. For more details on bytecode file structures, * see this article. * /
public final class ClassParser {... }Copy the code
If we use the Java class object directly, we can determine the type in two steps: class.forname (…). Class. IsAssignableFrom (…) To use a Class object, you have to load the Class first, and by doing so, all classes are loaded. Some classes may not be used for the entire JVM life, resulting in additional memory waste.
To avoid classes being loaded prematurely, Tomcat implements the JVM specification itself, with ClassParser and JavaClass.
2. checkHandlesTypes(...)
This method checks if the current class matches the class specified by @handlestypes and is commented as follows:
For classes packaged with the web application, the class and each super class needs to be checked for a match with {@link HandlesTypes} or for an annotation that matches {@link HandlesTypes}.
For classes packaged with your Web application, you need to check whether the class and each superclass match the class specified by @Handlestypes, or the annotation specified by @Handlestypes.
The execution of this method will not be analyzed.
3. handlesTypesOnly
ContextConfig#processAnnotationsStream(…) HandlesTypesOnly = true; handlesTypesOnly = true; handlesTypesOnly = true
It is assigned in ContextConfig#scanWebXmlFragment(…) Method with the following code:
// Only need to scan for @HandlesTypes matches if any of the
// following are true:
// - it has already been determined only @HandlesTypes is required
// (e.g. main web.xml has metadata-complete="true"
// - this fragment is for a container JAR (Servlet 3.1 section 8.1)
// - this fragment has metadata-complete="true"
booleanhtOnly = handlesTypesOnly || ! fragment.getWebappJar() || fragment.isMetadataComplete();Copy the code
This attribute means that only @handlestypes are handled, true if:
- It just needs to be treated
@HandlesTypes
“, meaning it can be set outsidehandlesTypesOnly
The value of the; - Jar package run mode, should refer to embedded mode run, different from the past run time to make a
webapp
Folder, wantweb.xml
File; - Specified in the web.xml file
metadata-complete="true"
.
The example demo in this series has no WebApp folder, no web.xml file, and the value of handlesTypesOnly is true.
4. processClass(...)
Although ContextConfig# processAnnotationsStream (…). The method returns if (handlesTypesOnly), but let’s look at processClass(…). What he did:
protected void processClass(WebXml fragment, JavaClass clazz) {
AnnotationEntry[] annotationsEntries = clazz.getAnnotationEntries();
if(annotationsEntries ! =null) {
String className = clazz.getClassName();
for (AnnotationEntry ae : annotationsEntries) {
String type = ae.getAnnotationType();
if ("Ljavax/servlet/annotation/WebServlet;".equals(type)) {
processAnnotationWebServlet(className, ae, fragment);
}else if ("Ljavax/servlet/annotation/WebFilter;".equals(type)) {
processAnnotationWebFilter(className, ae, fragment);
}else if ("Ljavax/servlet/annotation/WebListener;".equals(type)) {
fragment.addListener(className);
} else {
// Unknown annotation - ignore}}}}Copy the code
If you’re familiar with Servlet 3.0, these are three familiar faces:
@WebServlet
: Used to markservlet
Class, the class marked with this annotation is registered asServlet
@WebFilter
: Used to markfilter
Class, the class marked with this annotation is registered asFilter
@WebListener
: Used to marklistener
Class, the class marked with this annotation is registered asListener
In the servlet 3.0 era, we register servlets, filters, and listeners with the corresponding annotations, which is very convenient! If handlesTypesOnly is true, processClass(…) will be processed. Methods are not executed! However, the SpringBoot framework solves this problem for us. In SpringBoot, we can still use the above three annotations boldly. For the solution, please refer to the servlet component registration process of The SpringBoot Web application.
Finally, let’s see where it ends up annotating servlets, filters, and listeners:
servlet
:WebXml#servlets
That type ofMap<String,ServletDef>
filter
:WebXml#filters
That type ofMap<String,FilterDef>
listener
:WebXml#listeners
That type ofSet<String>
These locations are important and will be used later for servlet, filter, and listener processing.
2.3 configurationContext
:ContextConfig#configureContext
Let’s go back to ContextConfig#webConfig(…) Methods:
protected void webConfig(a) {...// Apply configuration. Apply configuration information from webXml to ContextconfigureContext(webXml); . }Copy the code
Although we don’t have a web. XML file in our project, Tomcat still generates webXml objects that hold the configuration in the project, such as the servlets, filters, listeners shown above, After that, the ContextConfig#configureContext method is called to apply the configuration to the context. Here we are focusing on the application of the three main components of the servlet. Go to ContextConfig#configureContext:
private void configureContext(WebXml webxml) {...for (FilterDef filter : webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
for (String listener : webxml.getListeners()) {
context.addApplicationListener(listener);
}
for (ServletDef servlet : webxml.getServlets().values()) {
Wrapper wrapper = context.createWrapper();
// Omit a lot of attribute configuration. wrapper.setName(servlet.getServletName()); wrapper.setServletClass(servlet.getServletClass());// Add to the Context subcontainercontext.addChild(wrapper); }... }Copy the code
This method is very long, and I have only listed the servlet, filter, and listener components for processing. Eventually these components are added to the Context, and we will see how they are used later.
2.4 saveServletContainerInitializer
Let’s go back to ContextConfig#webConfig(…) Methods:
protected void webConfig(a) {.../ / add ServletContainerInitializer to the Context
if (ok) {
for(Map.Entry<ServletContainerInitializer, Set<Class<? >>> entry : initializerClassMap.entrySet()) {if (entry.getValue().isEmpty()) {
context.addServletContainerInitializer(entry.getKey(), null);
} else{ context.addServletContainerInitializer(entry.getKey(), entry.getValue()); }}}... }Copy the code
ServletContainerInitializer, is to find a place to keep them up to the next, let’s see where it keeps, enter StandardContext# addServletContainerInitializer method:
privateMap<ServletContainerInitializer,Set<Class<? >>> initializers =new LinkedHashMap<>();
/** * add to StandardContext#initializers */
public void addServletContainerInitializer( ServletContainerInitializer sci, Set
> classes)
> {
initializers.put(sci, classes);
}
Copy the code
As you can see, it’s finally added into StandardContext#initializers.
To this, the ServletContainerInitializer loading is finished.
3. PerformServletContainerInitializers#onStartup(...)
methods
Let’s go back to the StandardContext#startInternal() method and continue with the following analysis:
protected synchronized void startInternal(a) throws LifecycleException {
// Omitted some code./ / 3. ServletContainerInitializers processing
for(Map.Entry<ServletContainerInitializer, Set<Class<? >>> entry : initializers.entrySet()) {try {
/ / execution ServletContainerInitializers# onStartup (...). methods
entry.getKey().onStartup(entry.getValue(), getServletContext());
} catch(ServletException e) { ... }}... }Copy the code
This code need to do is perform ServletContainerInitializers# onStartup (…). Method, in the last section, the eldest brother sweat finally collected all ServletContainerInitializer, save structure for StandardContext# initializers.
/ * * * save ServletContainerInitializer instance class, * key: ServletContainerInitializer instance, i.e., * ` meta-inf/services/XXX. ServletContainerInitializer ` * the value of the specified class instance: The introduction of class, it is used on ServletContainerInitializer@HandlesTypesThe specified class * */
privateMap<ServletContainerInitializer, Set<Class<? >>> initializers =new LinkedHashMap<>();
Copy the code
We’ll look at ServletContainerInitializers# onStartup (…). The parameters of:
/ * * *@paramC: Class set@HandlesTypesThe specified class *@paramCTX: ServletContext, provided by the servlet */
void onStartup(Set
> c, ServletContext ctx)
> throws ServletException;
Copy the code
Set
/ / execution ServletContainerInitializers# onStartup (...). methods
// getServletContext() : Get the ServletContext
entry.getKey().onStartup(entry.getValue(), getServletContext());
Copy the code
3.1 StandardContext#getServletContext()
The StandardContext#getServletContext() method looks like this:
protected ApplicationContext context = null;
/** * get ServletContext */
@Override
public ServletContext getServletContext(a) {
// Context refers to ApplicationContext
if (context == null) {
context = new ApplicationContext(this);
if(altDDName ! =null)
context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
}
return context.getFacade();
}
Copy the code
The ServletContext returned is returned by calling context.getfacade (). Let’s look at the implementation of ApplicationContext#getFacade() :
/** * ApplicationContext implements ServletContext */
public class ApplicationContext implements ServletContext {
/** * Provides access to the external class ServletContext */
private final ServletContext facade = new ApplicationContextFacade(this);
/** * get ServletContext */
protected ServletContext getFacade(a) {
return this.facade; }... }Copy the code
ApplicationContext has a member variable facade of type ApplicationContextFacade. ApplicationContext#getFacade() is used to return this facade, Continue to look at the constructor of the ApplicationContextFacade:
public class ApplicationContextFacade implements ServletContext {
/** * This context is the ApplicationContext */
private final ApplicationContext context;
/** * constructor */
public ApplicationContextFacade(ApplicationContext context) {...this.context = context; . }... }Copy the code
This is a typical facade pattern. In the ApplicationContextFacade method, you can see that its instance invokes the methods provided by ApplicationContext:
public class ApplicationContextFacade implements ServletContext {...@Override
public int getMajorVersion(a) {
return context.getMajorVersion();
}
@Override
public int getMinorVersion(a) {
returncontext.getMinorVersion(); }... }Copy the code
Other methods are also called this way, so I won’t list them all.
3.2 registeredservlet
:ServletContext#addServlet(...)
We went back to MyServletContainerInitializer class, perform ServletContainerInitializers# onStartup (…). Methods methods, we custom MyServletContainerInitializer# onStartup is called:
public void onStartup(Set
> clsSet, ServletContext servletContext)
>
throws ServletException {
MyHttpServlet servlet = new MyHttpServlet();
ServletRegistration.Dynamic registration = servletContext.addServlet("servlet", servlet);
When loadOnStartup is set to -1, init is called only on the first request
registration.setLoadOnStartup(-1);
registration.addMapping("/ *");
}
Copy the code
As you can see, the servlet is registered with ServletContext#addServlet(…). ApplicationContextFacade#addServlet(…) :
@Override
public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) {
if (SecurityUtil.isPackageProtectionEnabled()) {
return (ServletRegistration.Dynamic) doPrivileged("addServlet".new Class[]{String.class, Servlet.class},
new Object[]{servletName, servlet});
} else {
// This will be executed
returncontext.addServlet(servletName, servlet); }}Copy the code
This context is ApplicationContext, so let’s go ahead and go to ApplicationContext#addServlet(…) Methods:
private ServletRegistration.Dynamic addServlet(String servletName, String servletClass, Servlet servlet, Map
initParams)
,string> throws IllegalStateException {.../ / get the wrapper
Wrapper wrapper = (Wrapper) context.findChild(servletName);
// If the wrapper for servletName does not exist, the servlet is wrapped as a wrapper and added to the child container of the Context,
// The wrapper is implemented as StandardWrapper and the child container of the Context is wrapper
if (wrapper == null) {
wrapper = context.createWrapper();
wrapper.setName(servletName);
// This context is StandardContext
context.addChild(wrapper);
} else{... }// Set some parameters for the servlet to the wrapper.if(initParams ! =null) {
for(Map.Entry<String, String> initParam: initParams.entrySet()) { wrapper.addInitParameter(initParam.getKey(), initParam.getValue()); }}// Return the registered object
ServletRegistration.Dynamic registration =
newApplicationServletRegistration(wrapper, context); .return registration;
}
Copy the code
Adding a servlet is relatively simple. The process is as follows: If wapper does not exist, wrap the servlet as a Wrapper object, add it to the child container of the Context, and return the servlet registration.
What is this registration, and why does manipulating it affect servlets? We entered the ApplicationServletRegistration:
public class ApplicationServletRegistration implements ServletRegistration.Dynamic {
/** Servlet wrapper class, implemented as StandardWrapper */
private final Wrapper wrapper;
/** Implementation class StandardContext */
private final Context context;
/** ** Assignment */
public ApplicationServletRegistration(Wrapper wrapper, Context context) {
this.wrapper = wrapper;
this.context = context;
}
/** * Set the loadOnStartup attribute */
@Override
public void setLoadOnStartup(int loadOnStartup) { wrapper.setLoadOnStartup(loadOnStartup); }... }Copy the code
ApplicationServletRegistration implements ServletRegistration. Dynamic ServletRegistration. Dynamic is provided by the servlet, used to handle the servlet registered, ApplicationServletRegistration has two member variables: in the wrapper, the context, the wrapper for the packing of the servlet class, implemented as StandardWrapper.
We call ServletRegistration. Dynamic# setLoadOnStartup (…). Wrapper#setLoadOnStartup(…) Method, and other properties are set in similar ways, eventually calling Wrapper related methods, which is why registration can handle servlet properties.
4. Loadservlet
component
Let’s go back to the StandardContext#startInternal method:
protected synchronized void startInternal(a) throws LifecycleException {
// Omitted some code.// 4. Load Listener
if (ok) {
if(! listenerStart()) { log.error(sm.getString("standardContext.listenerFail"));
ok = false; }}// 5. Load filter
if (ok) {
if(! filterStart()) { log.error(sm.getString("standardContext.filterFail"));
ok = false; }}// 6. Handle the servlet, load and initialize the child of the Context as Wrapper, used to store the servlet
if (ok) {
if(! loadOnStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail"));
ok = false; }}... }Copy the code
With all this preparation, we’re finally ready to load the three major components of the servlet!
4.1 loadingListener
The Listener loading operation is as follows:
// 4. Load Listener
if (ok) {
if(! listenerStart()) { log.error(sm.getString("standardContext.listenerFail"));
ok = false; }}Copy the code
Let’s go to the StandardContext#listenerStart method:
public boolean listenerStart(a) {
// findApplicationListeners
// The contents are added in ContextConfig#configureContext
String listeners[] = findApplicationListeners();
Object results[] = new Object[listeners.length];
boolean ok = true;
for (int i = 0; i < results.length; i++) {
try {
String listener = listeners[i];
// Create a listener instance
results[i] = getInstanceManager().newInstance(listener);
} catch(Throwable t) { ... }}// Omitted some code.// Handle various types of listeners. It's amazing how many types of listeners servlets provide
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 instanceofHttpSessionListener)) { lifecycleListeners.add(result); }}// Omitted some code. }Copy the code
This method is quite clear, the general process is as follows:
- Get all of
listener
In theContextConfig#configureContext
Method, is going to takelistener
Added to theContext
Now,findApplicationListeners(...)
Method to obtain; - Access to the
listener
Is instantiated using reflection. - And then there’s yeah
Listener
Classified. Surpriseservlet
To provide thelistener
There are so many types.
Where do these listeners get run? Because the listener type is different, the events to be listened to are different. Therefore, the execution timing is different. Only the specific listener type can be analyzed.
Spring provides an important listner: ContextLoaderListener, which implements ServletContextListener:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {... }... }Copy the code
Where is the ServletContextListener#contextInitialized method executed? Again in the StandardContext#listenerStart method:
public boolean listenerStart(a) {
// A lot of code is omitted./ / ServletContextListener processing
for (Object instance : instances) {
if(! (instanceinstanceof ServletContextListener)) {
continue;
}
/ / call ServletContextListener# contextInitialized (...).
ServletContextListener listener = (ServletContextListener) instance;
try {
fireContainerEvent("beforeContextInitialized", listener);
// called here
if (noPluggabilityListeners.contains(listener)) {
listener.contextInitialized(tldEvent);
} else {
listener.contextInitialized(event);
}
fireContainerEvent("afterContextInitialized", listener);
} catch(Throwable t) { ... }}return ok;
}
Copy the code
At the end of the StandardContext#listenerStart method, it iterates through all the listeners and finds the ServletContextListener, The ServletContextListener#contextInitialized method is then executed one by one.
4.2 loadingfilter
The code to handle filter loading is as follows:
if(! filterStart()) { log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
Copy the code
Let’s go to the StandardContext#filterStart method:
/** Save ApplicationFilterConfig */
private Map<String, ApplicationFilterConfig> filterConfigs = new HashMap<>();
/** * process filter */
public boolean filterStart(a) {
boolean ok = true;
synchronized (filterConfigs) {
filterConfigs.clear();
for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
try {
// Generate the filterConfig object and save the filter
ApplicationFilterConfig filterConfig =
new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
} catch(Throwable t) { ... }}}return ok;
}
Copy the code
This method simply converts the filter to ApplicationFilterConfig and saves it to StandardContext#filterConfigs. It seems that the filter is not used in this step.
4.3 loadingservlet
The code to handle servlet loading is as follows:
if(! loadOnStartup(findChildren())){ log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
Copy the code
LoadOnStartup (findChildren()) contains two methods:
findChildren()
: Find the current subcontainer. From the previous analysis,Context
Deposit isWrapper
theWrapper
againservlet
Packaging class;loadOnStartup
: handlingservlet
The method of loading;
Let’s go to the StandardContext#loadOnStartup method:
public boolean loadOnStartup(Container children[]) {
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
// Sort according to loadOnStartup
for (Container child : children) {
Wrapper wrapper = (Wrapper) child;
int loadOnStartup = wrapper.getLoadOnStartup();
// If loadOnStartUp < 1, it will not be loaded at startup
if (loadOnStartup < 0) {
continue;
}
// To handle sort operations, TreeMap itself is ordered, and the same loadOnStartup values are put into the same ArrayList
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
/ / load
wrapper.load();
} catch(ServletException e) { ... }}}return true;
}
Copy the code
This method does two things:
- Sort: by
loadOnStartup
If the value is less than 0, it will not be processed. If the value is the same, it will be put into the sameArrayList
In the - Load: Traverses the sorted
wrapper
Call them one by oneWrapper#load()
methods
Let’s see what StandardWrapper#load does:
/** * Wrapper load operation */
public synchronized void load(a) throws ServletException {
// Load the servlet, which is instantiated
instance = loadServlet();
if(! instanceInitialized) {// Initialize the servlet by executing servlet #init(...) methods
initServlet(instance);
}
// Other omissions. }Copy the code
This method first calls the loadServlet() method to get the servlet instance, and then calls initServlet(…). LoadServlet (); loadServlet();
/** * Load serlvet, the result is a servlet instance */
public synchronized Servlet loadServlet(a) throws ServletException {
// The current servlet is instantiated and returns directly
if(! singleThreadModel && (instance ! =null))
returninstance; Servlet servlet; .try {
// Instantiate the servlet
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch(ClassCastException e) { ... }...return servlet;
}
Copy the code
This method simply handles fetching the servlet object, and calls reflection instantiation when the object does not exist.
Moving on to initServlet(…) Method, the core code is as follows:
private synchronized void initServlet(Servlet servlet) throws ServletException {
// Check whether initialization is performed
if(instanceInitialized && ! singleThreadModel)return;
try {
// Call the Servlet#init method
if( Globals.IS_SECURITY_ENABLED) {
boolean success = false;
try {
Object[] args = new Object[] { facade };
SecurityUtil.doAsPrivilege("init", servlet, classType, args);
success = true;
} finally {
if(! success) { SecurityUtil.remove(servlet); }}}else {
servlet.init(facade);
}
instanceInitialized = true;
} catch(UnavailableException f) { ... }}Copy the code
This method is used to execute the servlet#init method.
5. To summarize
This article analyzes the Context startup process and summarizes as follows:
- To deal with
The servlet 3.0
Specification, that is, loadingServletContainerInitializer
class - Parsing the Web configuration, not only means
web.xml
, as well as@HandlersTypes
Specified class, and@WebServlet
,@WebFilter
,@WebListener
annotations - Apply the parsed configuration to
Context
In this step, it will be resolvedservlet
,filter
,listener
Added to theContext
In the - perform
ServletContainerInitializer#onStartUp
Method to add the configuration content toContext
Then it’s time to apply the configuration - loading
servlet
Three components:servlet
,filter
,listener
Limited to the author’s personal level, there are inevitable mistakes in the article, welcome to correct! Original is not easy, commercial reprint please contact the author to obtain authorization, non-commercial reprint please indicate the source.
This article was first published in the wechat public number Java technology exploration, if you like this article, welcome to pay attention to the public number, let us explore together in the world of technology!