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



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

我想你可能有一些概念有点混乱,希望这个解释是有意义的,并帮助你解决问题。

会话位于应用程序服务器上。创建后,它会通过使用 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));
    }
}

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

并且没有"同一会话的两个不同对象归属"。会话保证给定名称只有一个条目。每次从其他选项卡执行请求时,您都会覆盖它。

最新更新