java 使用同一浏览器的 2 个以上选项卡时,Web 应用程序中 Servlet 中 Session 属性的作用域和生命周期让我感到困惑
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6003148/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
The Scope and the life time of Session attribute in Servlet in my web application get me confused when using more than 2 tabs of the same browser
提问by matiman
I was doing a simple web project (you can see the code below). As far as I know, session attributes are related to one session. When I opened two tabs of the same browser and run type the URL, only one session ID is created, but two different objects of the same session attribute are running (i.e I don't want to run two quizzes at the same time. But, when I changed the question in one of the tab, it doesn't affect the session attributes of the other tab). Can you explain me why it happened like that? How can I change my code to make the session variables shared so that when I changed one of the session attributes in one of the tabs, I wanted the other tab's session variables to be affected to?
我在做一个简单的网络项目(你可以看到下面的代码)。据我所知,会话属性与一个会话相关。当我打开同一个浏览器的两个标签并运行输入 URL 时,只创建了一个会话 ID,但是运行了相同会话属性的两个不同对象(即我不想同时运行两个测验。但是,当我在其中一个选项卡中更改问题时,它不会影响另一个选项卡的会话属性)。你能解释一下为什么会这样吗?如何更改我的代码以共享会话变量,以便当我更改其中一个选项卡中的一个会话属性时,我希望另一个选项卡的会话变量受到影响?
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.quizServlet;
import QuizApp.Quiz;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
*
* @author Mati
*/
@WebServlet(name = "QuizServlet", urlPatterns = {"/Quiz"})
public class QuizServlet extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
} catch (Exception ex) {
out.write("<font style='color:red'><b>" + ex.getMessage() + "</b></font>");
} finally {
out.close();
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
if (request.getSession().getAttribute("QuizzObject") == null) {
Quiz quiz = new Quiz();
quiz.addQuestion(new int[]{1, 2, 3, 4});
quiz.addQuestion(new int[]{1, 1, 2, 3, 5, 8});
quiz.addQuestion(new int[]{0, 5, 10, 15, 20, 25});
request.getSession().setAttribute("QuizzObject", quiz);
}
if (request.getSession().getAttribute("questionsLeft") == null) {
request.getSession().setAttribute("questionsLeft", true);
}
Quiz qq = (Quiz) request.getSession().getAttribute("QuizzObject");
qq.reset();
StringBuilder SB = new StringBuilder();
SB.append("<form name='myform' method='post'>");
SB.append("<h3>Have fun with NumberQuiz!</h3>");
SB.append("<p><input type='submit' name='btnNext' value='Start quiz' /></p>");
SB.append("</form>");
out.print(SB.toString());
} catch (Exception ex) {
out.write("<font style='color:red'><b>" + ex.getMessage() + "</b></font>");
} finally {
out.close();
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
try {
StringBuilder SB = new StringBuilder();
String msg="";
SB.append("<html><head></head><body>");
Quiz qq = (Quiz) request.getSession().getAttribute("QuizzObject");
SB.append(request.getSession().getId());
boolean questionsLeft = (Boolean) request.getSession().getAttribute("questionsLeft");
if (questionsLeft) {
qq.addAttempts();
if (request.getParameter("txtAnswer") != null) {
if (qq.isCorrect(Integer.parseInt(request.getParameter("txtAnswer")))) {
qq.scoreAnswer();
} else {
msg="<p><font style='color:red'>Wrong Answer .. Try Again</font></p>";
}
}
if (qq.getCurrentQuestion() == null) {
request.getSession().setAttribute("questionsLeft", false);
SB.append("Congratulations, you have completed the quiz!");
SB.append("<br>Your final score is:" + qq.getScore());
SB.append("<br>Total attempts:" + qq.getAttempt());
qq.reset();
request.getSession().setAttribute("questionsLeft",null);
} else {
SB.append("<form name='myform' method='post'>");
//SB.append("<h3>Have fun with NumberQuiz!</h3>");
SB.append("<p>Your current score is " + qq.getScore() + ".</p>");
SB.append("<p>Guess the next number in the sequence!</p>");
SB.append("<p>" + qq.getCurrentQuestion().toString().replaceAll("\?", "<font style='color:red'><b>?</b></font>") + "</p>");
SB.append("<p>Your answer:<input type='text' id='txtAnswer' name='txtAnswer' value='' /></p>");
SB.append("<p><input type='submit' name='btnNext' value='Next' onclick='return validate()' />");
SB.append("<input type='Reset' name='btnStart' value='Restart!' onclick=\"document.location.href='/QuizzWeb/Quiz';return false;\" /></p>");
SB.append(msg);
SB.append("</form>");
SB.append("<script type='text/javascript'>function validate(){if(document.getElementById('txtAnswer').value==''){alert('You should write an answer');return false;}return true;}</script>");
}
SB.append("</body></html>");
out.print(SB.toString());
}
} catch (Exception ex) {
out.print("<font style='color:red'><b>" + ex.getMessage() + "</b></font>");
} finally {
out.close();
}
}
@Override
public String getServletInfo() {
return "Short description";
}
}
回答by Charlie
I think you may have a few concepts muddled a bit, hopefully this explanation will make sense and help you sort things out.
我想你可能有一些概念有点混乱,希望这个解释有意义并帮助你理清思路。
A session lives on your application server. When created, it is communicated to your browser through use of a cookie (often named JSESSIONID). When your browser supplies that cookie to the webserver as part of a request, the server can retrieve the session and associated objects (should be serializable, see other SO questions) that had already been attached to that session (provided that this session has not expired).
会话存在于您的应用程序服务器上。创建后,它会通过使用 cookie(通常称为 JSESSIONID)传送到您的浏览器。当您的浏览器将该 cookie 作为请求的一部分提供给网络服务器时,服务器可以检索已经附加到该会话的会话和关联对象(应该是可序列化的,请参阅其他 SO 问题)(前提是该会话尚未过期) )。
As these session variables live only on the server, they are used by the server to build the response given to the client. But in order to have a response, your client needs to make a request. You made a request and changed the state of the first tab, but because the second tab didn't make a request of its own, it's state hasn't updated. (Because these tabs are in the same browser, they share a session cookie, and retrieve the same session to fufill their requests). With some more building out, you could make use of some client side technologies such as AJAX to perodically make small requests about the session state and refresh the display of your browser windows. (You could distinguish such requests by having them call a different resource, or different accept types on the request).
由于这些会话变量仅存在于服务器上,因此服务器使用它们来构建提供给客户端的响应。但是为了得到响应,您的客户需要提出请求。您发出请求并更改了第一个选项卡的状态,但由于第二个选项卡没有发出自己的请求,因此它的状态尚未更新。(因为这些选项卡在同一个浏览器中,它们共享一个会话 cookie,并检索同一个会话来满足他们的请求)。通过更多的构建,您可以利用一些客户端技术(例如 AJAX)定期发出有关会话状态的小请求并刷新浏览器窗口的显示。(您可以通过让这些请求调用不同的资源或请求的不同接受类型来区分此类请求)。
Now with the design of your code... I didn't look at it in too much depth, but you may want to work through your flow a bit more. It seems a GET will always reset your quiz and a post continues it? (This feels somewhat odd to me, but I can't put my finger on why... I'd recommend reading up on RESTand designs driven from such. JAX-RS & Jersey is pretty sweet :) ).
现在设计你的代码......我没有深入研究它,但你可能想要更多地完成你的流程。似乎 GET 总是会重置您的测验,并且帖子会继续吗?(这对我来说有点奇怪,但我无法解释为什么......我建议阅读REST和由此类驱动的设计。JAX-RS 和 Jersey 非常可爱 :))。
Edit: Here's a much simpler servlet that you could use to play with. Plop it into a war, and open 2 tabs, one just to the servlet itself, and another appending the query string ?checkOnly=true. Play with refreshing each tab independently and see what happens to the count.
编辑:这是一个更简单的 servlet,您可以使用它。将其投入到一场War中,并打开 2 个选项卡,一个仅针对 servlet 本身,另一个附加查询字符串 ?checkOnly=true。独立刷新每个选项卡,看看计数会发生什么。
package test.servlets;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Counting servlet counts the number of requests to it.
* @author Charlie Huggard-Lee
*/
@SuppressWarnings("nls")
public class CountingServlet extends HttpServlet {
/**
* The serialVersionUID.
*/
private static final long serialVersionUID = 4279853716717632192L;
/**
* {@inheritDoc}
*/
@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
final HttpSession session = req.getSession();
AtomicInteger counter = (AtomicInteger) session.getAttribute("Count");
if (counter == null) {
counter = new AtomicInteger();
session.setAttribute("Count", counter);
}
final boolean checkOnly = Boolean.parseBoolean(req.getParameter("checkOnly"));
final int thisCount;
if (checkOnly) {
thisCount = counter.get();
} else {
thisCount = counter.getAndIncrement() + 1;
}
resp.setStatus(200);
resp.setHeader("Content-Type", "text/plain"); //$NON-NLS-1$ //$NON-NLS-2$
resp.setCharacterEncoding("UTF-8"); //$NON-NLS-1$
final PrintWriter writer = resp.getWriter();
if (session.isNew()) {
writer.append("Hey new user!\n");
} else {
writer.append("Welcome Back!\n");
}
writer.append("Session ID: ");
writer.append(session.getId());
writer.append("\n");
if (checkOnly) {
writer.append("(checking) ");
}
writer.append("Count: ");
writer.append(Integer.toString(thisCount));
}
}
回答by Jacek Prucia
When session is established server sends cookie to your browser. The same cookie is sent back everytime an URL matches cookie scope, which (most of the time) is defined by domain & path attributes of a cookie. So it doesn't matter if you have 2, 10 or 50 open tabs in your browser. As long as there's a match between URL you're accessing and your session cookie scope, you will get the same session. As far as browser is concerned, there is no such thing as session, so don't assume that your browser is aware of it. It just simply sends cookies. That's all.
建立会话后,服务器将 cookie 发送到您的浏览器。每次 URL 匹配 cookie 范围时都会发回相同的 cookie,这(大部分时间)由 cookie 的域和路径属性定义。因此,您的浏览器中是否有 2、10 或 50 个打开的选项卡并不重要。只要您访问的 URL 与会话 cookie 范围匹配,您就会获得相同的会话。就浏览器而言,没有会话这样的东西,所以不要假设您的浏览器知道它。它只是简单地发送 cookie。就这样。
And there are no "two different objects of the same session atribute". Session guarantees that there is only one entry for a given name. You just overwrite it everytime you do a request from other tab.
并且没有“同一会话属性的两个不同对象”。会话保证给定名称只有一个条目。每次从其他选项卡发出请求时,您只需覆盖它。