Servlet - ServletContext object

ServletContext

Two questions:
1. When people visit a website, they often see that the first page of the website shows the number of visitors (website counter) you are. How is this achieved?
2. When we visit a bbs website, we often show how many people are online. How is this achieved?

The conventional implementation ideas that we may think of: database or file. This method is relatively simple, but it will access the database or files too frequently, and the overhead is relatively high.

The solution is to useServletContext

What is ServletContext

To understand ServletContext, you must compare it with Cookie and Session, as shown below:

ServletContex

You can think of it as a public space that can be accessed by all clients, that is, client A can access D, client B can access D, and client C can also access D.

  • When the WEB container is started, it will create a corresponding ServletContext for each Web application , which represents the current Web application . And it is shared by all clients.

  • The ServletContext object can ServletConfig.getServletContext()obtain a reference to the ServletContext object through methods, and can also this.getServletContext()obtain a reference to its object through methods.

  • Since all servlets in a WEB application share the same ServletContext object, communication between servlet objects can be achieved through the ServletContext object. The ServletContext object is also commonly referred to as the context domain object. It is used in public chat rooms.

  • When the web application shuts down, Tomcat shuts down, or the web application is reloaded, the ServletContext object will be destroyed

How to use ServletContext

Using ServletContext:
(1) How to get the ServletContext object

this.getServletContext();
this.getServletConfig().getServletContext();

(2) You can think of it as a table, which is very similar to Session: each row is an attribute, as follows:

name (String) value (Object)
   
   
  • Add properties:setAttribute(String name, Object obj);
  • Get value: getAttribute(String name), this method returns Object
  • Remove attributes:removeAttribute(String name)

(3) Life cycle
The life cycle of the attributes in the ServletContext starts from creation and ends when the server is shut down.

A quick-start case:
we create Servlet1 and Servlet2, Servlet1 is used to create attributes in ServletContext, Servlet2 is used to read attributes from ServletContext:
Servlet1's doGet method is:

public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    response.setContentType("text/html;charset=utf-8");
    PrintWriter out = response.getWriter();
    // 获取ServletContext对象的引用
    // 第一种方法
    ServletContext servletContext = this.getServletContext();
    // 第二种方法
    // ServletContext servletContext2 = this.getServletConfig().getServletContext();
    servletContext.setAttribute("name", "小明");
    out.println("将 name=小明  写入了ServletContext");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

The doGet method of Servlet2 is:

public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    response.setContentType("text/html;charset=utf-8");
    PrintWriter out = response.getWriter();
    // 取出ServletContext的某个属性
    //1.首先获取到ServletContext
    ServletContext servletContext = this.getServletContext();
    //2.取出属性
    String name = (String)servletContext.getAttribute("name");
    out.println("name="+name);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

With this access to Servlet1 and Servlet2, we can see the output as follows:

Servlet1

Servlet2

The running result seems to be no different from the application of Session and Cookie. In fact, it looks the same, but in fact it is completely different. As long as we don't close Tomcat or reload the application, then we close the current browser, or change a browser. Suppose we switch from Chrome to IE and visit Servlet2 again, we can still see the results! This is the biggest difference between them. , because the ServletContext is a common space in the server memory, which can be accessed by all user clients.

ServletContext application

(1) Multiple Servlets realize data sharing through the ServletContext object

This is well understood. Similar to Session, we can also share data through the ServletContext object, but it should be noted that Session can only share data with one client, and it has exclusive use of one client. The data in the ServletContext can be shared by all clients.

(2) Implement Servlet request forwarding

The request forwarding we learned earlier is through the request object:
request.getRequestDispatcher("/url").forward(request, response);

It should be noted here that ServletContext can also implement request forwarding:
this.getServletContext().getRequestDispatcher("/url").forward(request, response);
the two forwarding effects are the same.

(3) Get the initialization parameters of the Web application

In [Servlet - development details + ServletConfig object] , we introduced that when servlet is deployed, we can use one or more <init-param>tags to configure some initialization parameters for the servlet, and then we obtain these parameters through the ServletConfig object, if there are the following MyServlet, which is configured as:

<servlet>  
    <servlet-name>MyServlet</servlet-name>  
    <servlet-class>com.gavin.servlet.MyServlet</servlet-class>  
    <init-param>  
        <param-name>encoding</param-name>  
        <param-value>utf-8</param-value>  
    </init-param>  
</servlet>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

You can see that it is configured with an initialization parameter: encoding=utf-8, then we need to get this parameter in the source code of MyServlet like this:

String encoding = this.getServletConfig().getInitParameter("encoding");
  • 1

The above parameter configuration method is only valid for a specific Servlet. Now we can obtain the global initialization parameters of the entire Web application through the ServletContext. The global initialization parameters are configured in the web.xml file as follows:

<!-- 如果希望所有的Servlet都可以使用该配置,则必须这么做 -->
<context-param>
    <param-name>name</param-name>
    <param-value>gavin</param-value>
</context-param>
  • 1
  • 2
  • 3
  • 4
  • 5

Then we can use ServletContext to get this parameter in any servlet:

String name = this.getServletContext().getInitParameter("name");
  • 1

(4) Using the ServletContext object to read resource files (such as properties files)
Reading resource files is divided into two cases according to the location of the resource files:

(1) The file is in the WebRoot folder, which is the root directory of our Web application. At this time we can use ServletContext to read the resource file.

Suppose we have a dbinfo.properties file that configures database information in our Web root directory, which configures the name and password attributes. At this time, we can read this file through ServletContext:

// 这种方法的默认读取路径就是Web应用的根目录
InputStream stream = this.getServletContext().getResourceAsStream("dbinfo.properties");
// 创建属性对象
Properties properties = new Properties();
properties.load(stream);
String name = properties.getProperty("name");
String password = properties.getProperty("password");
out.println("name="+name+";password="+password);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

(2) But if this file is placed in the src directory, it cannot be read through the ServletContext, and the class loader must be used to read it.

// 类加载器的默认读取路径是src根目录
InputStream stream = MyServlet.class.getClassLoader().getResourceAsStream("dbinfo.properties")
  • 1
  • 2

But if this file is not directly in the src directory at this time, but in a package in the src directory, such as under the com.gavin package, the class loader should add the path of the package, as follows:

InputStream stream = MyServlet.class.getClassLoader().getResourceAsStream("com/gavin/dbinfo.properties")
  • 1

In addition, to add, ServletContext can get the full path of the file, of course, this is also the file in the root directory of the Web application. For example, we have an images folder in the WebRoot folder, and there is a Servlet.jpg image in the images folder. In order to get the full path of this image, it is as follows:

// 如何读取到一个文件的全路径,这里会得到在Tomcat的全路径
String path = this.getServletContext().getRealPath("/images/Servlet.jpg");
  • 1
  • 2

In website development, there are many functions to use ServletContext, such as
1. Website counter
2. Display of online users of the website
3. Simple chat system

In short, if it involves different users sharing data, and the amount of data is not large, and at the same time do not want to write to the database, we can consider using ServletContext implementation .

Practical Case - Website Counter

In website construction, the number of times a certain webpage is viewed is often counted, so how is this website counter implemented?
What counts as a valid click? Each website has different standards:
1. As long as you have visited the page, even if it is once, it will be counted as a refresh. Of course, this is the easiest. But this is a bit false.
2. If different IPs visit the webpage, it counts as a valid click; if it is the same IP for a certain period of time (such as one day), no matter how many times you browse the webpage, it counts as one click.
3. If the user exits the website, it counts as one visit again.

Now we use the third option to implement this simple case.
First, we write 3 servlets, Login, LoginCl and Manager, which are the login form, login processing and management main page respectively. After the user logs in successfully, we increment the counter in the ServletContext by 1, and then redirect the request to the Manager page. The Manager page shows the total number of visits to the website.

The code of the Login page is very simple and will not be shown here. In LoginCl, we judge whether the user's password is "123", as follows:

public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    response.setContentType("text/html;charset=utf-8");
    String passwd = request.getParameter("password");
    if ("123".equals(passwd)) {
        // 合法
        // 向ServletContext添加属性
        ServletContext servletContext = this.getServletContext();
        int nums = 0;
        try {
            nums = Integer.parseInt((String) servletContext.getAttribute("nums"));
        } catch (Exception e) {
            nums = 0;
        }
        nums += 1;
        servletContext.setAttribute("nums", String.valueOf(nums));
        // request.getRequestDispatcher("/Manager").forward(request,response);
        response.sendRedirect("/Counter/Manager");
    } else {
        // 非法
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

Take the count value of nums from the Manager page, as follows:

public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    response.setContentType("text/html;charset=utf-8");
    PrintWriter out = response.getWriter();
    out.println("<h1>管理页面</h1>");       
    ServletContext servletContext = this.getServletContext();
    String nums = (String) servletContext.getAttribute("nums");
    out.println("该管理页面被访问了"+nums+"次");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

[1. Attention]

Why do we use request redirection sendRedirect instead of request forwarding from LoginCl to Manager, this is because after the request is forwarded to the Manager
page , if we refresh the Manger page again, the corresponding form will still be submitted once. That is to say, we did not log out and log in again, but just
refresh the Manager page to increase the value of the website counter nums by 1. This obviously does not meet the requirements.

【2. There are problems】

Question: When the server is shut down, our counter is cleared, so how can we ensure the stable growth of the counter?

Solution: Before the server is shut down, save the count value in the ServletContext in the file, and then read the count value from the file and put it into the ServletContext when the server is started next time.

So naturally, we thought of how to do these jobs when the server is turned on and off? —> Remember the life cycle of the servlet, remember the two automatically invoked methods init()and destroy().

Which init()is called when the servlet is initialized, and the destroy()method is called before the servlet is destroyed. Then we can use these two methods to read and save our count value. Suppose this count value is stored in the record.txt file in the root directory of the web application.

So we write the InitServlet code as follows:

public class InitServlet extends HttpServlet {
    public void destroy() {
        // 把ServletContext的值重新保存在文件中
        ServletContext servletContext = this.getServletContext();
        String nums = (String) servletContext.getAttribute("nums");
        System.out.println("destroy.nums:"+nums);
        String path = servletContext.getRealPath("record.txt");
        FileWriter fileWriter = null;
        BufferedWriter bw = null;
        try {
            fileWriter = new FileWriter(path);
            bw = new BufferedWriter(fileWriter);
            bw.write(nums);
            bw.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                bw.close();
                fileWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 初始化方法
    public void init() throws ServletException {
        ServletContext servletContext = this.getServletContext();
        // 1.首先得到该文件真实路径
        String path = servletContext.getRealPath("record.txt");
        // 2.打开文件
        FileReader fileReader = null;
        BufferedReader br = null;
        try {
            fileReader = new FileReader(path);
            br = new BufferedReader(fileReader);
            String nums = br.readLine();
            // 把nums添加到ServletContext
            servletContext.setAttribute("nums", nums);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭流,后打开先关闭
            try {
                br.close();
                fileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

Then, we also want this InitServlet to be automatically loaded into the server's memory, so configure the load-on-startup element to start it automatically:

<servlet>
        <servlet-name>InitServlet</servlet-name>
        <servlet-class>com.gavin.InitServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
 </servlet>
  • 1
  • 2
  • 3
  • 4
  • 5

Finally, because we automatically load the ServletContext every time the server starts, and add the nums attribute, the corresponding LoginCl code should also be modified. It is very simple and will not be repeated here.


[Final usage principle]
Because the data in the ServletContext will be stored in the server for a long time and occupy memory, we recommend not to add too large data to the ServletContext!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326075207&siteId=291194637