当我使用同一浏览器的 2 个以上选项卡时,我的 Web 应用程序中 Servlet 中的 Session 属性的范围和生命周期让我感到困惑

发布于 2024-11-06 22:58:50 字数 6127 浏览 0 评论 0原文

我正在做一个简单的网络项目(你可以看到下面的代码)。据我所知,会话属性与一个会话相关。当我打开同一浏览器的两个选项卡并运行输入 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";
    }
}

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?

/*
 * 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";
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

年少掌心 2024-11-13 22:58:50

我认为您可能对一些概念有点困惑,希望这个解释能够理解并帮助您理清思路。

会话存在于您的应用程序服务器上。创建后,它会通过使用 cookie(通常名为 JSESSIONID)传送到您的浏览器。当您的浏览器将该 cookie 作为请求的一部分提供给网络服务器时,服务器可以检索已附加到该会话的会话和关联对象(应该是可序列化的,请参阅其他 SO 问题)(前提是该会话尚未过期) )。

由于这些会话变量仅存在于服务器上,因此服务器使用它们来构建给客户端的响应。但为了得到回应,您的客户需要提出请求。您发出了请求并更改了第一个选项卡的状态,但由于第二个选项卡没有发出自己的请求,因此它的状态尚未更新。 (因为这些选项卡位于同一浏览器中,所以它们共享会话 cookie,并检索相同的会话来满足其请求)。通过更多的构建,您可以利用一些客户端技术(例如 AJAX)定期发出有关会话状态的小请求并刷新浏览器窗口的显示。 (您可以通过让这些请求调用不同的资源或请求上的不同接受类型来区分这些请求)。

现在,随着代码的设计......我没有太深入地研究它,但您可能想更多地完成您的流程。看来 GET 总是会重置你的测验,而帖子会继续它? (这对我来说有点奇怪,但我无法弄清楚为什么......我建议阅读 REST 以及由此驱动的设计。JAX-RS 和 Jersey 非常可爱:))。

编辑:这是一个更简单的 servlet,您可以使用它。将其放入战争中,然后打开 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));
    }
}

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).

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).

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 REST and designs driven from such. JAX-RS & Jersey is pretty sweet :) ).

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.

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));
    }
}
郁金香雨 2024-11-13 22:58:50

当会话建立时,服务器将 cookie 发送到您的浏览器。每次 URL 与 cookie 范围匹配时,都会发送回相同的 cookie,该范围(大多数情况下)是由域和域定义的。 cookie 的路径属性。因此,您的浏览器中打开的选项卡是 2 个、10 个还是 50 个都没有关系。只要您访问的 URL 与会话 cookie 范围匹配,您就会获得相同的会话。就浏览器而言,不存在会话这样的东西,因此不要假设您的浏览器知道它。它只是简单地发送cookie。就这样。

并且不存在“同一会话属性的两个不同对象”。会话保证给定名称只有一个条目。每次从其他选项卡发出请求时,您只需覆盖它即可。

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.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文