1. The four corresponding implementation classes, Engine Host Context and Wrapper, which are used by tomcat to process requests, respectively correspond to the implementation classes StnadardEngine, StandardHost, StandardContext and StandardWrapper
Engine: representing the engine of tomcat, there can be multiple engines. The location of the corresponding application under the virtual machine host.
Host: represents a virtual host, the name of each host cannot be the same, appBase represents the application publishing location of each virtual host;
Context: represents an application, Context can be processed according to the servlet defined in the /WEB-INF/web.xml file of the application ask. There can be multiple Contexts under a Host;
Wrapper: Represents a servlet or jsp, which is responsible for managing a servlet, including servlet loading, initialization, execution, and resource recycling.
Second, the flow of requests between these containers is through the pipeline Pipeline pipeline is controlled by their parent container ContainerBase, and the flow in the pipeline is something like Value, and each container implements its own value. It is StandardEngineValue, StandardHostValue,
StandardContextValue and StandardWrapperValue .
Please see the request that has been initially encapsulated from the adapter CoyoteAdapter, as shown in the Service method
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); if (request == null) { // Create objects request = connector.createRequest(); request.setCoyoteRequest(req); response = connector.createResponse(); response.setCoyoteResponse(res); // Link objects request.setResponse(response); response.setRequest(request); // Set as notes req.setNote(ADAPTER_NOTES, request); res.setNote(ADAPTER_NOTES, response); // Set query string encoding req.getParameters().setQueryStringEncoding (connector.getURIEncoding()); } if (connector.getXpoweredBy()) { response.addHeader("X-Powered-By", POWERED_BY); } boolean comet = false; boolean async = false; try { // Parse and set Catalina and configuration specific // request parameters req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName()); boolean postParseSuccess = postParseRequest(req, request, res, response); if (postParseSuccess) { //check valves if we support async request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported()); // The first of Calling the container is actually a class like StandardEngineValve connector.getService().getContainer().getPipeline().getFirst().invoke(request, response); // code omitted
Focus on this sentence code
connector.getService().getContainer().getPipeline().getFirst().invoke(xxx,xxx)
Since the connector finds the parent class Service container and then finds the top-level container of a group of containers that processes the request, that is, the Engine, the container pipeline, is the StandardEngineValue that flows in this pipeline, that is, the getFirst() method is called and
handed over to this Value value for processing (invoke)
The following is invoke in the StandardEngineValue method
@Override public final void invoke(Request request, Response response) throws IOException, ServletException { // Select the Host to be used for this Request Host host = request.getHost(); if (host == null) { response.sendError (HttpServletResponse.SC_BAD_REQUEST, sm.getString("standardEngine.noHost", request.getServerName())); return; } if (request.isAsyncSupported()) { request.setAsyncSupported(host.getPipeline().isAsyncSupported()); } // Ask this Host to process this request host.getPipeline().getFirst().invoke(request, response); }
also used the same code
host.getPipeline().getFirst().invoke(request, response);
Continue StandardHostValue until the invoke method of StandardWrapperValue
@Override public final void invoke(Request request, Response response) throws IOException, ServletException { // Initialize local variables we may need boolean unavailable = false; Throwable throwable = null; // This should be a Request attribute... long t1=System.currentTimeMillis(); requestCount++; StandardWrapper wrapper = (StandardWrapper) getContainer(); Servlet servlet = null; Context context = (Context) wrapper.getParent(); // Check for the application being marked unavailable if (!context.getState().isAvailable()) { response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardContext.isUnavailable")); unavailable = true; } // Check for the servlet being marked unavailable if (!unavailable && wrapper.isUnavailable()) { container.getLogger().info(sm.getString("standardWrapper.isUnavailable", wrapper.getName())); long available = wrapper.getAvailable(); if ((available > 0L) && (available < Long.MAX_VALUE)) { response.setDateHeader("Retry-After", available); response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName())); } else if (available == Long.MAX_VALUE) { response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("standardWrapper.notFound", wrapper.getName())); } unavailable = true; } // Allocate a servlet instance to process this request try { if (!unavailable) { //Load the instantiated Servlet instance (the init(ServletConfig config) method is also initialized) //It can be seen that the servlet is a singleton servlet = wrapper.allocate(); } // omit code...
Then the allocate() method of StandardWrapper checks the creation process of the servlet. The
code is as follows
public Servlet allocate() throws ServletException { // If we are currently unloading this servlet, throw an exception if (unloading) throw new ServletException (sm.getString("standardWrapper.unloading", getName())); boolean newInstance = false; // If not SingleThreadedModel, return the same instance every time if (!singleThreadModel) { // Load and initialize our instance if necessary if (instance == null) { synchronized (this) { if (instance == null) { try { if (log.isDebugEnabled()) log.debug("Allocating non-STM instance"); instance = loadServlet (); if (!singleThreadModel) { // For non-STM, increment here to prevent a race // condition with unload. Bug 43683, test case // #3 newInstance = true; countAllocated.incrementAndGet(); } } catch (ServletException e) { throw e; } catch (Throwable e) { ExceptionUtils.handleThrowable(e); throw new ServletException (sm.getString("standardWrapper.allocate"), e); } } } } if (!instanceInitialized) { //Instantiate Servlet initServlet(instance); } if (singleThreadModel) { if (newInstance) { // Have to do this outside of the sync above to prevent a // possible deadlock synchronized (instancePool) { instancePool.push(instance); nInstances++; } } } else { if (log.isTraceEnabled()) log.trace(" Returning non-STM instance"); // For new instances, count will have been incremented at the // time of creation if (!newInstance) { countAllocated.incrementAndGet(); } return (instance); } } synchronized (instancePool) { while (countAllocated.get() >= nInstances) { // Allocate a new instance if possible, or else wait if (nInstances < maxInstances) { try { instancePool.push (loadServlet ()); nInstances++; } catch (ServletException e) { throw e; } catch (Throwable e) { ExceptionUtils.handleThrowable(e); throw new ServletException (sm.getString("standardWrapper.allocate"), e); } } else { try { instancePool.wait(); } catch (InterruptedException e) { // Ignore } } } if (log.isTraceEnabled()) log.trace(" Returning allocated STM instance"); countAllocated.incrementAndGet(); return instancePool.pop(); } }
The above code shows that
1. If SingleThreadModel is not implemented, it will ensure that there is only one instance instance, that is, a single servlet instance
, that is, each request accesses the same servlet
. 2. If the servlet implements this interface, it will ensure that there will be no two threads executing the service of the servlet at the same time. method. The servlet container does this by synchronizing access to a single instance of the servlet, or by maintaining a pool of servlet instances that allocates new requests to an idle servlet.
3. Note: SingleThreadModel will not solve all thread safety hazards. For example, session properties and static variables can still be accessed concurrently by multiple requests from multiple threads, even if the SingleThreadModel servlet is used. It is recommended that developers should take other measures to solve these problems than implementing this interface, such as avoiding the use of instance variables or synchronizing code blocks when accessing resources.