Tech stack used: Java 1.8, Servlet Context v.3.0.1. Refer to project POM for more details.
|The Maven project behind this article is publicly available on gitHub so feel free to check it out: https://github.com/gmihaylov840/ServletWorkflow.git|
During server startup the Web Container will recognized and deploy all available web applications. In doing so, it will create one Servlet Context Object per application by parsing and reading the application level data in the deployment descriptor file: web.xml. The Servlet Context Object is shared between all servlets in single application. In our case, the Web Container will recognize and deploy myApp to the server before preparing it’s Servlet Context Object.
|Since the Servlet Context Object is loaded at application startup any changes to the web.xml file afterwards will required application restart to take effect.|
Sending The Request
When the web application is deployed and started, navigating to application root “http://localhost:8080/myApp/ ” will trigger automatic request for index.html page unless any other pages are specified in deployment descriptor file, like shown in example deployment descriptor file webConfigExample.xml:
The welcome file info is defined in the <welcome-file-list> element.
In our case, there are no welcome files defined, so requesting the application root will bring back index.html.
The index page has two buttons that will send requests for “http://localhost:8080/myApp/servletTest ” that will be intercepted by MyCustomServlet. The first button will send GET and the second will send POST request that will be processed by their respected doGet() and doPost() methods inside MyCustomServlet class. When either of those buttons are clicked, requests will be send by the browser with the following actions illustrated in the Workflow Diagram:
- Browser will create request and will sent to the protocol;
- Then the protocol will establish socket connection between the client browser and the server using request URL’s IP address and port number;
- The protocol will then convert browser request to it’s own format with header and body part. The header part contains request headers that are details about the client browser: locale, supported formats, encoding. The body part contains the request parameters;
- When the request is ready, the protocol will transport it to the server;
- The server will check if the request is valid. If it is, it will be forwarded to the Servlet Container;
Once the request hits the Servlet Container it is routed to the appropriate Servlet based on request URL: http://localhost:8080/myApp/servletTest.
|In web applications, Web Container and Servlet Container usually reference the same container object, so feel free to consider them as one.|
To do that the Servlet Container will use the request URL to identify requested application’s name (in our case “/myApp”) and will also check the type of the requested resource (in our case “/servletTest”) based on its extension.
If the requested resource turn out to be static like HTML or image – no servlets will be invoked and the resource will be served immediately to the browser. But if the requested resource is a servlet URL pattern then the Servlet Container will forward the request to the appropriate servlet under application’s classes folder. To do that the Servlet Container inspects the deployment descriptor file:
The URL pattern is described within the element’s <url-pattern> tag. It shows that all HTTP requests, regardless of method type, that ask for “/servletTest” will be processed by MyCustomServlet. Which is exactly what the buttons from index.html are doing.
So far, so good, but the Servlet Container does not yet know MyCustomServlet’s package directory and that is why it also has to be specified within a <servlet> element. The declaration is simply saying that any servlet-mapping element that uses MyCustomServlet will find it in the classes folder at this location: com.joro.servlet.MyCustomServlet.
If the Servlet Container cannot find the class at that location, it will look for it in application “/lib” folder in the form of a JAR file. If the class is also not found there, the Servlet Container will raise an exception.
From here on, the Servlet Container know exactly how to find the appropriate servlet for this request and will initiate that servlet’s life-cycle.
Since this project is using XML free JavaConfig style the Servlet is setup using the @WebServlet annotation on MyCustomServlet class.
It may use annotation, but the principle is still the same. @WebServlet is equivalent of the XML element and its parameter “/servletTest” is equivalent of the XML element in the web.xml file. This annotation is saying the same thing as the webConfigExample.xml file.
The actual servlet implementation is in MyCustomSevlet java file. It has overridden doGet() and doPost() methods from parent HttpServlet class. Those are the methods that will process GET and POST requests from the client browser. I have also overridden the init() and service() methods to include logs that will tell when each of those methods is getting called.
Servlet Lifecycle Diagram
Once the Servlet Container has identified appropriate Servlet, it will start Servlet life-cycle by doing the following actions:
- Servlet Container checks if the target Servlet isn’t already loaded due to previous calls. If it is, skip to step 5;
- Servlet Loading: the bytecode of MyCustomServlet class will be transferred and loaded into memory by Servlet Container with command: class.forName(“MyCustomServlet”);
- Servlet Instantiation: Now that bytecode is loaded the Servlet Container will create an instance of the MyCustomServlet using the newInstance() command;
- Servlet Initialization: In the case of HTTP Servlet, initialization will be complete after the Servlet Context executes two methods:
- Firstly, init(ServletConfig) method with one parameter is executed. The parameter is created by Servlet Context and is know as Servlet Config Object.
- Inside the first init(ServletConfig), a second init() method with 0 arguments will be executed internally.
Those methods are used to setup data that will be used by this servlet during it’s life-cycle and will be executed only once;
- Execute Service Method: service(ServletRequest, ServletResponse) method is the method that does the actual work! It will receive and process HTTP request and send HTTP response back to the client browser. This service() method will convert its parameters ServletRequesta and ServletResponse into the familiar HTTPServletRequest and HTTPServletResponse. After the conversion those parameters will be internally passed to another service() method that will check the HTTP request type (GET, POST, PUT, DELETE) and call doGet(), doPost(), doPut(), or doDelete() methods as appropriate. The important thing here is that each time the server receives a request for a servlet, the Servlet Context executes service() in a new thread. So one servlet instance can process requests concurrently. Once the service() method returns, the executing thread will return back to its pool. When the end of the service() method is reached the Servlet Container will know that the request processing at server side is done;
- Servlet De-Instantiation: Servlet’s life-cycle is ended by the Servlet Context with execution of Servlet’s destroy() method, right after the Context Config Object for that Servlet, created in step 4, is being destroyed. That usually happens during application undeployment from the server or if Servlet timeout is reached because of no activity. Servlets can be configured to wait specific amount of time between requests before the Servlet Container destroys them. This tactic is mainly used to save resources and it depends on Web Container implementation;
- Servlet Unloading: After servlet was destroyed the byte-code definitions are unloaded from memory.
Servlet’s life-cycle is pretty straightforward, but exposes one huge disadvantage and that is the heavy loading and initialization phases. You don’t want to be the guy that hits the servlet first and wait till its ready and loaded, because it will take a while. Every request after that, in the bounds of the timeout interval of course, will be served lightning fast. This disadvantage is one of the reasons for the birth of Java Server Pages.
Returning The Response
When the Servlet’s service() method finished it will pass a Servlet Response Object back to the server so that will push it to the protocol which will parse it into it’s own response format. That response object will have header and body parts. The header part contains response headers (Status code, Acceptable media types, Size of the resource, Cache control directives). The body part contains the actual dynamic response.
When the response object is ready, the protocol will transport it back to the client browser. The browser will show the content of the body of the response, which in our case is HTML code.
Behind the scenes, once the response is rendered by the browser, the protocol will terminate the previously established connection to the server. The server will detect that and will know that the response has been delivered and will destroy the no longer needed HTTPServletRequest and HTTPServletResponse objects created at step 5 of Servlet Life-Cycle. After that, the Servlet Container will enter waiting state, the duration of which depends entirely on the implementation of the container.
When the Servlet remains idle for a long period of time the Servlet Container will destroy it by starting Servlet De-Instantiation. With this the Servlet Workflow is completed.
Finally, the Servlet Context Object will be destroyed when the Server is shutting down or the web application is getting undeployed.
Nothing special to note here. This is standard project setup so I won’t focus on it.
Running The App
Just issue the following command to Maven “jetty:run” and embedded Jetty server will automatically get deployed and started. To view the example index.html file, open browser and request http://localhost:8080/myApp.
The index.html page looks like this:
Clicking on any of the two buttons will request http://localhost:8080/myApp/servletTest that will be processed by MyCustomServlet.
The “Send GET request to MyCustomServlet” button will send GET request that will trigger MyCustomServler’s lifecycle. Servlet’s servise() method will call doGet() method. Prove of that can be seen in server logs or the console:
MyCustomServlet: init called in com.joro.servlet.MyCustomServlet
MyCustomServlet: service called in com.joro.servlet.MyCustomServlet
MyCustomServlet: doGet called in com.joro.servlet.MyCustomServlet
The response body returns HTML page that is rendered like this:
The “Send POST request to MyCustomServlet” button will send POST request and will be served this HTML page:
This request will call MyCustomSevrlet’s doPost() method as seen in the logs:
MyCustomServlet: service called in com.joro.servlet.MyCustomServlet
MyCustomServlet: doPost called in com.joro.servlet.MyCustomServlet
Links & Sources
Project repository on gitHub: https://github.com/gmihaylov840/ServletWorkflow.git
End-to-end explanation of Servlet Lifeycle video from Durgasoft YouTube channel
Complete set of video tutorials from Ram J YouTube channel