Servlet单列常见高危缺陷

许多 Servlet 开发人员都不了解 Servlet 为单例模式。Servlet 只有一个实例,并通过使用和重复使用该单个实例来处理需要由不同线程同时处理的多个请求。

这种误解的共同后果是,开发者使用 Servlet 成员字段的这种方式会导致某个用户可能在无意中看到其他用户的数据。换言之,即把用户数据存储在 Servlet 成员字段中会引发数据访问的 race condition。

例 1:以下 Servlet 把请求参数值存储在成员字段中,然后将参数值返回给响应输出流。

public class GuestBook extends HttpServlet {

  String name;

  protected void doPost (HttpServletRequest req, HttpServletResponse res) {
    name = req.getParameter("name");
    ...
    out.println(name + ", thanks for visiting!");
  }
}

 

当该代码在单一用户环境中正常运行时,如果有两个用户几乎同时访问 Servlet,可能会导致这两个请求以如下方式处理线程的插入:

线程 1: 将“Dick”分配给 name
线程 2    Jane”分配给 name
线程 1: print“Jane, thanks for visiting!”
线程 2: print“Jane, thanks for visiting!”

因此会向第一个用户显示第二个用户的用户名。
建议
不要为任何参数(常量除外)使用 Servlet 成员字段。(例如,确保所有成员字段都是 static final)。

当开发者需要把代码内某一部分中的数据传输到另一部分时,他们经常使用 Servlet 成员字段存储用户数据。如果您也是这么做的,可以考虑声明一个单独的类,并仅使用 Servlet“封装”这个新类。

示例 2:Example 1 中的 bug 可以通过以下方式进行更正:

public class GuestBook extends HttpServlet {

protected void doPost (HttpServletRequest req, 
  HttpServletResponse res) {
    GBRequestHandler handler = new GBRequestHandler();
    handler.handle(req, res);
  }
}

public class GBRequestHandler {

  String name;

  public void handle(HttpServletRequest req, HttpServletResponse res) {
    name = req.getParameter("name");
    ...
    out.println(name + ", thanks for visiting!");
  }

}

此外,Servlet 也可以利用同步代码块来访问 servlet 实例变量。但是,使用同步代码块可能会导致严重的性能问题。

请注意,如果将字段访问封装在同步块中,则只有在该成员上的所有读取和写入操作均在同一同步块或方法中执行时方可防止出现问题。

示例 3:将Example 1 写入操作(赋值)封装在一个同步块中不会解决问题,因为线程必须获得一个锁才能修改 name 字段,但是随后线程将释放该锁,从而使其他线程能够再次更改该值。如果在更改 name 值后,第一个线程恢复执行,则输出的值将是第二个线程赋予的值:

public class GuestBook extends HttpServlet {

    String name;

   protected void doPost (HttpServletRequest req, HttpServletResponse res) {
      synchronized(name) {
        name = req.getParameter("name");
      }
      ...
      out.println(name + ", thanks for visiting!");
  }
}

为了解决争用条件,共享成员字段上的所有写入和读取操作应在同一同步块中自动运行:

public class GuestBook extends HttpServlet {

    String name;

    protected void doPost (HttpServletRequest req, HttpServletResponse res) {
      synchronized(name) {
        name = req.getParameter("name");
        ...
        out.println(name + ", thanks for visiting!");
      }
   }
}

 

posted @ 2022-04-12 22:17  达摩院的BLOG  阅读(190)  评论(0)    收藏  举报