我有一个Web应用程序,其中多个servlet使用一定量的相同逻辑进行预初始化(设置日志记录,会话跟踪等)。我所做的是在javax.servlet.http.HttpServlet
和我的具体servlet类之间引入一个中间层:
public abstract class AbstractHttpServlet extends HttpServlet {
// ... some common things ...
}
然后:
public class MyServlet extends AbstractHttpServlet {
// ... specialized logic ...
}
我在AbstractHttpServlet的默认(且唯一)构造函数中做的一件事是设置一些私有成员变量。在这种情况下,它是一个UUID,用作会话标识符:
public abstract class AbstractHttpServlet extends HttpServlet {
private UUID sessionUuid;
public AbstractHttpServlet() {
super();
this.sessionUuid = UUID.randomUUID();
// ... there's more, but snipped for brevity ...
}
protected UUID getSessionUuid() {
return this.sessionUuid;
}
}
然后我在getSessionUuid()
中使用MyServlet
来提供请求中的会话跟踪。这非常有用,例如在日志记录中,能够筛选大型日志文件并获取与单个HTTP请求相关的所有条目。原则上,会话标识符可以是任何内容;我刚刚选择使用UUID,因为它很容易生成随机的,并且不必担心不同服务器之间的冲突,种子问题,搜索日志文件,将匹配作为较长字符串的一部分等等。
我没有看到为什么多个执行应该在sessionUuid
成员变量中获得相同值的任何原因,但实际上,它们似乎是这样。这就好像即使在很长一段时间内(似乎直到服务器进程重新启动),类的实例被重用于多个请求。
在我的例子中,类实例化开销与类完成的有用工作相比较小,所以理想情况下我希望Tomcat始终为每个请求创建新的类实例,从而强制它每次都单独执行构造函数。 是否有可能对类进行注释以确保按请求实例化?非常优选不需要更改服务器配置的答案。
如果失败了,有没有办法(除了在每个do *()方法中单独执行此操作,例如doGet()
,doPost()
等)确保某种初始化是根据HTTP请求完成的,这导致执行特定的servlet?
答案 0 :(得分:1)
此代码不是线程安全的。 servlet容器通常会创建一个servlet实例,所有请求都将使用它。这意味着sessionUUID将被所有请求共享,并将不断被覆盖。
如果您需要在每个请求的基础上保留此值,请考虑使用ThreadLocal对象并将UUID放在那里。
答案 1 :(得分:1)
就好像即使在很长一段时间内(似乎直到服务器进程重新启动),类的实例仍被重用于多个请求。
是的,这正是将要发生的事情以及您应该期待的事情。
servlet并不是一个会话 - 它只是作为处理程序。
如果您想对每个请求执行“某些操作”,无论采用何种方法,您都可以覆盖service
方法,执行任何操作,然后调用super.service()
。但是,您不应该更改servlet本身的状态 - 请记住,多个请求可能同时在同一个servlet中执行。
基本上,你所要求的是与servlet的设计相反 - 你应该使用设计而不是反对它。您可以修改请求本身(使用setAttribute
)来存储与此请求相关的一些信息 - 但我可能会在比HTTP更高的级别上执行此操作。 (我试着让servlet本身非常小,只是尽可能地委托给非servlet的类,这样可以更容易测试。)
答案 2 :(得分:0)
即使在很长一段时间内,该类的实例也会被重用于多个请求。
在 JVM 的任何给定时间点,始终有一个Servlet类实例。因此,实例变量在Servlet中不是线程安全的。每个Servlet请求都将由一个线程处理。在service()
,doPost()
和doGet()
内声明的局部变量将是线程安全的。
因此,您可以将您的逻辑移动到其他类,在服务方法中实例化它并以线程安全的方式使用它。您甚至可以使用ThreadLocal对象。
有一项规定要实施SingleThreadModel,它已被弃用,这样做不仅糟糕而且荒谬。
确保servlet一次只处理一个请求。这个界面没有方法。
如果servlet实现了此接口,则可以保证在servlet的服务方法中不会同时执行两个线程。 servlet容器可以通过同步对servlet的单个实例的访问,或者通过维护一个servlet实例池并将每个新请求分派给一个免费的servlet来实现这一保证。
最好实现ServletRequestListener并将逻辑放在那里。