《Servlet、JSP和Spring MVC初学指南》——第2章 会话管理 2.1URL重写

简介: 清单2.1中的Top10Servlet类会显示最受游客青睐的10个伦敦和巴黎的景点。信息分成两页展示,第一页展示指定城市的5个景点,第二页展示另外5个。该Servlet使用URL重写来记录所选择的城市和页数。该类扩展自HttpServlet,并通过/top10访问。

本节书摘来自异步社区《Servlet、JSP和Spring MVC初学指南》一书中的第2章,第2.1节,作者:【加】Budi Kurniawan(克尼亚万) , 【美】Paul Deck著,更多章节内容可以访问云栖社区“异步社区”公众号查看

第2章 会话管理

由于HTTP的无状态性,使得会话管理或会话跟踪成为Web应用开发一个无可避免的主题。默认下,一个Web服务器无法区分一个HTTP请求是否为第一次访问。

例如,一个Web邮件应用要求用户登录后才能查看邮件,因此,当用户输入了相应的用户名和密码后,应用不应该再次提示需要用户登录,应用必须记住哪些用户已经登录。换句话说,应用必须能管理用户的会话。

本章将阐述4种不同的状态保持技术:URL重写、隐藏域、cookies和HTTPSession对象。本章的示例代码为app02a。

2.1 URL重写

URL重写是一种会话跟踪技术,它将一个或多个token添加到URL的查询字符串中,每个token通常为key=value形式,如下:

url?key-1=value-1&key-2=value-2 ... &key-n=value-n
注意,URL和tokens间用问号(?)分割,token间用与号(&)。

URL重写适合于tokens无须在太多URL间传递的情况下,然而它有如下限制:

URL在某些浏览器上最大长度为2000字符;
若要传递值到下一个资源,需要将值插入到链接中,换句话说,静态页面很难传值;
URL重写需要在服务端上完成,所有的链接都必须带值,因此当一个页面存在很多链接时,处理过程会是一个不小的挑战;
某些字符,例如空格、与和问号等必须用base64编码;
所有的信息都是可见的,某些情况下不合适。
因为存在如上限制,URL重写仅适合于信息仅在少量页面间传递,且信息本身不敏感。

清单2.1中的Top10Servlet类会显示最受游客青睐的10个伦敦和巴黎的景点。信息分成两页展示,第一页展示指定城市的5个景点,第二页展示另外5个。该Servlet使用URL重写来记录所选择的城市和页数。该类扩展自HttpServlet,并通过/top10访问。

清单2.1 Top10Servlet类

package app02a.urlrewriting;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "Top10Servlet", urlPatterns = { "/top10" })
public class Top10Servlet extends HttpServlet {
    private static final long serialVersionUID = 987654321L;

    private List<String> londonAttractions;
    private List<String> parisAttractions;

    @Override
    public void init() throws ServletException {
        londonAttractions = new ArrayList<String>(10);
        londonAttractions.add("Buckingham Palace");
        londonAttractions.add("London Eye");
        londonAttractions.add("British Museum");
        londonAttractions.add("National Gallery");
        londonAttractions.add("Big Ben");
        londonAttractions.add("Tower of London");
        londonAttractions.add("Natural History Museum");
        londonAttractions.add("Canary Wharf");
        londonAttractions.add("2012 Olympic Park");
        londonAttractions.add("St Paul's Cathedral");

        parisAttractions = new ArrayList<String>(10);
        parisAttractions.add("Eiffel Tower");
        parisAttractions.add("Notre Dame");
        parisAttractions.add("The Louvre");
        parisAttractions.add("Champs Elysees");
        parisAttractions.add("Arc de Triomphe");
        parisAttractions.add("Sainte Chapelle Church");
        parisAttractions.add("Les Invalides");
        parisAttractions.add("Musee d'Orsay");
        parisAttractions.add("Montmarte");
        parisAttractions.add("Sacre Couer Basilica");
    }

    @Override
    public void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException,
            IOException {
        String city = request.getParameter("city");
        if (city != null &&
                (city.equals("london") || city.equals("paris"))) {
            // show attractions
            showAttractions(request, response, city);
        } else {
            // show main page
            showMainPage(request, response);
        }
    }

    private void showMainPage(HttpServletRequest request,
            HttpServletResponse response) throws ServletException,
            IOException {
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();
        writer.print("<html><head>" +
                "<title>Top 10 Tourist Attractions</title>" +
                "</head><body>" +
                "Please select a city:" +
                "<br/><a href='?city=london'>London</a>" +
                "<br/><a href='?city=paris'>Paris</a>" +
                "</body></html>");
    }

    private void showAttractions(HttpServletRequest request,
            HttpServletResponse response, String city)
            throws ServletException, IOException {

        int page = 1;
        String pageParameter = request.getParameter("page");
        if (pageParameter != null) {
            try {
                page = Integer.parseInt(pageParameter);
            } catch (NumberFormatException e) {
                // do nothing and retain default value for page
            }
            if (page > 2) {
                page = 1;
            }            
        }
        List<String> attractions = null;
        if (city.equals("london")) {
            attractions = londonAttractions;
        } else if (city.equals("paris")) {
            attractions = parisAttractions;
        }
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();
        writer.println("<html><head>" +
                "<title>Top 10 Tourist Attractions</title>" +
                "</head><body>");
        writer.println("<a href='top10'>Select City</a> ");
        writer.println("<hr/>Page " + page + "<hr/>");
        int start = page * 5 - 5;
        for (int i = start; i < start + 5; i++) {
            writer.println(attractions.get(i) + "<br/>");
        }
        writer.print("<hr style='color:blue'/>" +
                "<a href='?city=" + city +
                "&page=1'>Page 1</a>");
        writer.println("&nbsp; <a href='?city=" + city +
                "&page=2'>Page 2</a>");
        writer.println("</body></html>");
    }
}

init方法,仅当该servlet第一次被用户访问时调用,构造两个类级别的列表,londonAttractions和parisAttractions,每个列表有10个景点。

doGet方法,该方法每次请求时被调用,检查URL中是否包括请求参数city,并且其值是否为“london”或“paris”,方法据此决定是调用showAttractions方法还是showMainPage方法:

String city = request.getParameter("city");
        if (city != null &&
                (city.equals("london") || city.equals("paris"))) {
            // show attractions
            showAttractions(request, response, city);
        } else {
            // show main page
            showMainPage(request, response);
        }

用户一开始访问该servlet时不带任何请求参数,此时调用showMainPage,该方法发送两个链接到浏览器,每个链接都包含token:city=cityName。用户所见如图2.1所示,现在用户可以选择一个城市。

screenshot

图2.1 Top10Servlet的初始页面

如果你查看网页源代码,你会看见如下HTML:

Please select a city:<br/>
<a href='?city=london'>London</a><br/>
<a href='?city=paris'>Paris</a>

请注意a元素中的href属性,该属性值包括一个问号加token city=london或city=paris. 注意,此处为相对URL,即URL中没有协议部分,相对于当前页面。因此,若你点击了任一链接,则会提交

http://localhost:8080/app02a/top10?city=london

http://localhost:8080/app02a/top10?city=paris
到服务器上。

根据用户所点击的链接,doGet方法识别请求参数的city值并传递给showAttractions方法,该方法会检查URL中是否包含page参数,如果没有该参数或该参数值无法转换为数字,则该方法设定page参数值为1,并将头5个景点发给客户端。图2.2为选择伦敦时的界面。

showAttractions方法还发送了3个链接到客户端:Select City、Page 1和Page 2。Select City 是无参数访问servlet,Page 1和Page 2链接包括两个tokens,即city和page:

http://localhost:8080/app02a/top10?city=cityName&page=pageNumber
若选择了伦敦,并点击了Page 2,则将以下URL发送给服务端:

http://localhost:8080/app02a/top10?city=london&page=2

screenshot

图2.2 伦敦前十景点,第一页

此时系统会展示伦敦的另外5个景点,如图2.3所示。

screenshot

图2.3 伦敦前十景点,第二页

本例展示了如何用URL重写技术来传递参数——city到服务端以便服务端能正确展示。

相关文章
|
5月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
12月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
673 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
12月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
1270 0
|
12月前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
751 0
|
12月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
676 0
|
12月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
473 0
|
8月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
566 0
|
8月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
233 0
|
8月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
343 0
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
797 29