Loading... # Servlet基础 ## 什么是Servlet - Servlet是一种规范,对于B/S结构,需要一个Server处理由Browser发送的请求,Servlet是一个处理这些请求的模块,通过Servlet可以处理浏览器的请求并且不涉及底层源码。 - Servlet提供了一系列规范,这样使得所有遵循Servlet规范的webapp都可以在不同的服务器运行,例如Tomcat。 - 规范了哪些接口 - 规范了哪些类 - 规范了一个web应用中应该有哪些配置文件 - 规范了一个web应用中配置文件的名字 - 规范了一个web应用中配置文件存放的路径 - 规范了一个web应用中配置文件的内容 - 规范了一个合法有效的web应用它的目录结构应该是怎样的。 - ..... ## 符合Servlet规范的webapp开发步骤 - 第一步:在webapps目录下新建一个目录,起名crm(这个crm就是webapp的名字)。 - 注意:crm就是这个webapp的根 - 第二步:在webapp的根下新建一个目录:WEB-INF - 注意:全部大写。 - 第三步:在WEB-INF目录下新建一个目录:classes - 这个目录下存放的是Java程序编译之后的class文件。 - 第四步:在WEB-INF目录下新建一个目录:lib - 注意:这个目录不是必须的。但如果一个webapp需要第三方的jar包的话,这个jar包要放到这个lib目录下,这个目录的名字也不能随意编写,必须是全部小写的lib。例如java语言连接数据库需要数据库的驱动jar包。那么这个jar包就一定要放到lib目录下。 - 第五步:在WEB-INF目录下新建一个文件:web.xml ```xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd" version="5.0" metadata-complete="true"> <!--servlet描述信息--> <!--任何一个servlet都对应一个servlet-mapping --> <servlet> <servlet-name>HelloServlet</servlet-name> <!--这个位置必须是带有包名的全限定类名--> <servlet-class>com.bjpowernode.servlet.HelloServlet</servlet-class> </servlet> <!--servlet映射信息--> <servlet-mapping> <!--这个也是随便的,不过这里写的内容要和上面的一样。--> <servlet-name>HelloServlet</servlet-name> <!--这里需要一个路径--> <!--这个路径唯一的要求是必须以 / 开始--> <!--当前这个路径可以随便写--> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> ``` - 目录结构 ``` webapproot |------WEB-INF |------classes(存放字节码) |------lib(第三方jar包) |------web.xml(注册Servlet) |------html |------css |------javascript |------image .... ``` ## Servlet示例 ```java import jakarta.servlet.*; import java.io.IOException; //实现Servlet接口 public class AServlet implements Servlet { // init为初始化 // init方法只执行一次 // 在AServlet对象第一次被创建之后执行。 // init方法通常是完成初始化操作的。 // init方法在执行的时候AServlet对象已经被创建出来了。 @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("AServlet's init method execute!"); } // service方法:是处理用户请求的核心方法。 // 只要用户发送一次请求,service方法必然会执行一次。 @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("AServlet's service method execute!"); } // destroy方法也是只执行一次。 // Tomcat服务器在销毁AServlet对象之前会调用一次destroy方法 // destroy方法在执行的时候,AServlet对象的内存还没有被销毁。即将被销毁。 // destroy方法中可以编写销毁前的准备。 // 比如说,服务器关闭的时候,AServlet对象开启了一些资源,这些资源可能是流,可能是数据库连接 // 那么,关闭服务器的时候,要关闭这些流,关闭这些数据库连接,那么这些关闭资源的代码就可以写到destroy方法当中。 @Override public void destroy() { System.out.println("AServlet's destroy method execute!"); } @Override public ServletConfig getServletConfig() { return null; } @Override public String getServletInfo() { return null; } } ``` ## Servlet的生命周期 - Servlet对象的生命周期 - 在第一次被用户访问时创建 - 在服务器关闭时销毁 - 在整个webapp运行中,Servlet对象只有一个(单例) - Servlet对象的维护 - Servlet对象的创建,方法调用,销毁均由服务器控制 - 生命周期也由Web容器控制 - 启动服务器时,Servlet对象并没有被创建 如果希望在启动时创建Servlet对象,可以在web.xml加入参数\<load-on-startup> 该标签中填写整数,数字越小,优先级越高 ```xml <servlet> <servlet-name>aservlet</servlet-name> <servlet-class>com.bjpowernode.javaweb.servlet.AServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>aservlet</servlet-name> <url-pattern>/a</url-pattern> </servlet-mapping> ``` - Servlet生命周期 - 服务器启动,Servlet对象未创建 - 用户访问对应url,创建Servlet对象并调用init方法。之后调用service方法相应请求。 - 之后每次用户访问,只调用service方法响应请求,因此有几次请求就会调用多少次service方法 - 服务器关闭前,执行destroy方法,销毁Servlet对象。 - 注:不建议手动定义Servlet对象的构造函数,Tomcat服务器使用反射机制创建Servlet对象,如若重写构造函数造成无参构造函数消失,创建对象时会导致服务器内部错误(500错误) - 需要长久保存的信息,在destroy中重写 ## GenericServlet - 直接编写Servlet,比较复杂 有时候init,destroy方法不一定需要,但是却必须重写,导致大量无用代码生成。 - 构建一个GenericServlet(通用Servlet)抽象类,其中重写所有Servlet接口的方法,并将service方法设置为abstract 我们需要使用Servlet的时候可以继承GenericServlet,减少重写的代码量 同时必须继承service方法实现用户逻辑 - 例 ```java import jakarta.servlet.*; import java.io.IOException; public abstract class GenericServlet implements Servlet { // 成员变量 private ServletConfig config; /** * init方法中的ServletConfig对象由服务器创建。 */ @Override public final void init(ServletConfig config) throws ServletException { this.config = config; // 调用init()方法 this.init(); } /** * 这个init方法是供子类重写的。 */ public void init(){ } @Override public ServletConfig getServletConfig() { return config; } /** * 抽象方法,这个方法最常用。所以要求子类必须实现service方法。 * @param servletRequest * @param servletResponse * @throws ServletException * @throws IOException */ public abstract void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException; @Override public String getServletInfo() { return null; } @Override public void destroy() { } } ``` - 一般称这种设计模式为适配器设计模式 ## ServletConfig - Servlet的配置信息对象 - 存储了web.xml中的配置信息 - 一个Servlet对应一个ServletConfig对象 - 在Servlet的init函数调用时会传入一个ServletConfig对象 - ServletConfig常用方法 ```java public String getInitParameter(String name); // 通过初始化参数的name获取value public Enumeration<String> getInitParameterNames(); // 获取所有的初始化参数的name public ServletContext getServletContext(); // 获取ServletContext对象 public String getServletName(); // 获取Servlet的name ``` - 注意:以上方法在GenericServlet中可以直接使用this调用,因为GenericServlet实现了ServletConfig接口。 ## ServletContext - Servlet的环境(上下文)对象,在一个webapp中只有一个,也就是说不管几个Servlet对象,它们的ServletContext是共用的。 - ServletContext在服务器启动时创建,服务器关闭时终止。 - 常用方法 ```java public String getInitParameter(String name); // 通过初始化参数的name获取value public Enumeration<String> getInitParameterNames(); // 获取所有的初始化参数的name ``` - 其参数存放在web.xml文件当中 ```xml <!--以上两个方法是ServletContext对象的方法,这个方法获取的是以下的配置信息--> <context-param> <param-name>pageSize</param-name> <param-value>10</param-value> </context-param> <context-param> <param-name>startIndex</param-name> <param-value>0</param-value> </context-param> <!--注意:以上的配置信息属于应用级的配置信息,一般一个项目中共享的配置信息会放到以上的标签当中。--> <!--如果你的配置信息只是想给某一个servlet作为参考,那么你配置到servlet标签当中即可,使用ServletConfig对象来获取。--> ``` ```java // 获取应用的根路径 public String getContextPath(); // 获取文件的绝对路径(真实路径) public String getRealPath(String path); // 通过ServletContext对象记录日志 public void log(String message); public void log(String message, Throwable t); // 这些日志信息记录到 // localhost.2022-04-02.log ``` - ServletContext属于应用域,与其对应的还有请求域,会话域等,在不同的域可以存放一些数据,在对应的域之内均可使用,例如在应用域存放的数据在整个webapp中均可使用。 ```java // 向ServletContext应用域中存数据 public void setAttribute(String name, Object value); // map.put(k, v) // 从ServletContext应用域中取数据 public Object getAttribute(String name); // Object v = map.get(k) // 删除ServletContext应用域中的数据 public void removeAttribute(String name); // map.remove(k) ``` ## HTTP协议、HttpServlet - 在实际web开发中,通常不继承GenericServlet而继承HTTPServlet,HTTPServlet也是抽象类,其继承于GenericServlet,并且专门为HTTP协议的请求做了一些调整和优化。 ### HTTP协议 - HTTP协议:是W3C制定的一种超文本传输协议。 - 包括请求协议于响应协议 #### 请求协议 - 其包括四部分: - 请求行 - 请求头 - 空白行 - 请求体 - HTTP请求样例报文: GET: ```http GET /servlet05/getServlet?username=wyy&userpwd=12345678 HTTP/1.1 请求行 Host: localhost:8080 请求头 Connection: keep-alive sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost:8080/servlet05/index.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 空白行 请求体 ``` POST: ```http POST /servlet05/postServlet HTTP/1.1 请求行 Host: localhost:8080 请求头 Connection: keep-alive Content-Length: 25 Cache-Control: max-age=0 sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 Origin: http://localhost:8080 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Referer: http://localhost:8080/servlet05/index.html Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 空白行 username=wyy&userpwd=12345678 请求体 ``` - 样例分析 - 请求行 - 请求类型 - get - post - delete - put - head - options - trace - 常用get和post - URI - 统一资源标识符。代表网络中某个资源的名字。 - 如:/servlet05/postServlet - HTTP协议版本 - 表示本请求使用的HTTP协议版本 - 请求头 - 请求的主机:端口 - 浏览器身份信息 - Cookie - 语言 - 编码方式 - …… - 请求体 - 请求包含的具体数据 #### 响应协议 - 也包括四部分 - 状态行 - 响应头 - 空白行 - 响应体 - 响应报文示例: ```http HTTP/1.1 200 ok 状态行 Content-Type: text/html;charset=UTF-8 响应头 Content-Length: 160 Date: Mon, 08 Nov 2021 13:19:32 GMT Keep-Alive: timeout=20 Connection: keep-alive 空白行 <!doctype html> 响应体 <html> <head> <title>from get servlet</title> </head> <body> <h1>from get servlet</h1> </body> </html> ``` - 样例分析 - 状态行 - 协议版本 - 状态码 - 200表示请求响应成功 - 404找不到目标资源 - 405请求类型不符(前端get请求后端只能处理post请求) - 500服务器内部错误 - 状态描述信息 - 对前边状态码的解释 - 例如OK表示成功 - 404 NotFound - 响应头 - 响应的内容类型 - 响应的编码格式,语言 - 响应时间 - 是否长期存在 - …… - 响应体 - 响应返回的具体内容 ### HttpServlet - HttpServlet类是专门为HTTP协议准备的。比GenericServlet更加适合HTTP协议下的开发。 - jakarta.servlet.http.HttpServlet - 旧版本使用javax.servlet.http.HttpServlet - http包下有 - jakarta.servlet.http.HttpServlet (HTTP协议专用的Servlet类,抽象类) - jakarta.servlet.http.HttpServletRequest (HTTP协议专用的请求对象) - jakarta.servlet.http.HttpServletResponse (HTTP协议专用的响应对象) - HttpServletRequest对象中封装了什么信息? - HttpServletRequest对象。 - HttpServletRequest中封装了请求协议的全部内容。 - Tomcat服务器(WEB服务器)将“请求协议”中的数据全部解析出来,并将这些数据全部封装到request对象当中了。 - 也就是说,我们只要使用HttpServletRequest,就可以获取请求协议中的数据。 - HttpServlet源码 ```java // HttpServlet类 public abstract class HttpServlet extends GenericServlet { @Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; try { // 将ServletRequest和ServletResponse向下转型为HttpServletRequest和HttpServletResponse request = (HttpServletRequest) req; response = (HttpServletResponse) res; } catch (ClassCastException e) { throw new ServletException(lStrings.getString("http.non_http")); } // 调用重载的service方法。 service(request, response); } // 这个service方法的两个参数都是带有Http的。 // 这个service是一个模板方法。 // 在该方法中定义核心算法骨架,具体的实现步骤到子类中去完成。 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 获取请求方式 // 这个请求方式最终可能是:"" // 注意:request.getMethod()方法获取的是请求方式,可能是七种之一: // GET POST PUT DELETE HEAD OPTIONS TRACE String method = req.getMethod(); // 如果请求方式是GET请求,则执行doGet方法。 if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { // 如果请求方式是POST请求,则执行doPost方法。 doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } } protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{ // 报405错误 String msg = lStrings.getString("http.method_get_not_supported"); sendMethodNotAllowed(req, resp, msg); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 报405错误 String msg = lStrings.getString("http.method_post_not_supported"); sendMethodNotAllowed(req, resp, msg); } } /* 通过以上源代码分析: 假设前端发送的请求是get请求,后端程序员重写的方法是doPost 假设前端发送的请求是post请求,后端程序员重写的方法是doGet 会导致:发生405错误。405表示前端的错误,发送的请求方式不对。和服务器不一致。不是服务器需要的请求方式。 通过以上源代码可以知道:只要HttpServlet类中的doGet方法或doPost方法执行了,必然405. 怎么避免405的错误呢? 后端重写了doGet方法,前端一定要发get请求。 后端重写了doPost方法,前端一定要发post请求。 这样可以避免405错误。 不建议同时重写doGet和doPost,该报错就让它报错更容易找到错误 */ ``` - 最好不要直接重写service方法,否则无法触发405错误 - 最终Servlet应用开发方法: - 创建一个Servlet应用类继承HttpServlet。 - 重写doGet或者doPost - 将Servlet配置到web.xml文件中或者使用注解配置 - 编写或者导入相应的前端页面,进行测试 ## HttpServletRequest - 接口,继承于ServletRequest - 对于Tomcat服务器,org.apache.catalina.connector.RequestFacade 实现了 HttpServletRequest接口 ### HttpServletRequest中的信息 - 包括用户请求的HTTP报文中的所有信息 - 生命周期 - Request和Responce分别是请求与返回对象 - 只在当前请求有效 - 属于请求域 - 常用方法 - 获取用户提交的数据 ```java Map<String,String[]> getParameterMap() 获取键值对Map Enumeration<String> getParameterNames() 获取Map集合中所有的key String[] getParameterValues(String name) 根据key获取Map集合的value String getParameter(String name) 获取这个键对应的第一个元素。 ``` - 注意,如果前端提交了数字,则提交时会变为字符串,也就是后端接收到的永远是字符串。 - 请求域相关方法 - HttpServletRequest属于请求域,可以通过它对请求域的数据进行操作 - 请求域只在一次请求范围内有效,相比于应用域,其作用域要小得多 - 一次请求之后,这个请求域将会销毁 - 相关方法 ```java void setAttribute(String name, Object obj); // 向域当中绑定数据。 Object getAttribute(String name); // 从域当中根据name获取数据。 void removeAttribute(String name); // 将域当中绑定的数据移除 ``` - 在对请求域和应用域选择时,尽量选择作用域小的。 - 转发 - ```java // 第一步:获取请求转发器对象 RequestDispatcher dispatcher = request.getRequestDispatcher("/b"); // 第二步:调用转发器的forward方法完成跳转/转发 dispatcher.forward(request,response); ``` - 因此在两个Servlet之间传递信息时,可以直接将数据放到应用域,但不建议,因为应用域作用范围较大,不安全,效率低。更好的方法是将信息提交到请求域之后再进行转发。 - 注意,转发不一定要转发到一个Servlet对象,也可以转发到一切web应用例如html,jsp…… - 注意,转发的路径必须以“/”开始,无应用名 - 其他常用方法 ```java // 获取客户端的IP地址 String remoteAddr = request.getRemoteAddr(); // POST请求 // Tomcat10之后,request请求体当中的字符集默认就是UTF-8,不需要设置字符集,不会出现乱码问题。 // Tomcat9前(包括9在内),如果出现乱码,执行以下代码。 request.setCharacterEncoding("UTF-8"); // 在Tomcat9之前(包括9),响应中文 response.setContentType("text/html;charset=UTF-8"); // get请求乱码问题 // get请求发送的时候,数据是在请求行上提交的,不是在请求体当中提交的。 // 方案:修改CATALINA_HOME/conf/server.xml配置文件 <Connector URIEncoding="UTF-8" /> // 注意:从Tomcat8之后,URIEncoding的默认值就是UTF-8,所以GET请求也没有乱码问题了。 // 获取应用的根路径 String contextPath = request.getContextPath(); // 获取请求方式 String method = request.getMethod(); // 获取请求的URI String uri = request.getRequestURI(); // /aaa/testRequest // 获取servlet path String servletPath = request.getServletPath(); // /testRequest ``` ## 一些注意事项 ### Web首页的配置 - 对于直接访问应用名,未指定资源名时,应该显示网站首页,例如访问 http://localhost:8080/servlet 并未指定要访问那个文件,此时应该显示http://localhost:8080/servlet/index.html - 对于首页的配置 在web.xml中配置 ```xml <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> ``` - 可以设置多个首页页面 ```xml <welcome-file-list> <welcome-file>abc/a.html</welcome-file> <welcome-file>index.html</welcome-file> </welcome-file-list> ``` 默认使用上面的,找不到顺序向下找 - 注意: - 如果不配置,Tomcat服务器就会以index.html index.htm index.jsp作为一个web站点的首页。 - 首页也可以是一个Servlet应用 ### WEB-INF的访问权限 - WEB-INF中的文件不能被访问,静态资源必须放在其目录之外 ## 转发与重定向 - 在一个web应用中有两种方法跳转资源 - 转发 - 重定向 - 转发和重定向的代码 - 转发 ```java // 获取请求转发器对象 RequestDispatcher dispatcher = request.getRequestDispatcher("/dept/list"); // 调用请求转发器对象的forward方法完成转发 dispatcher.forward(request, response); // 合并一行代码 request.getRequestDispatcher("/dept/list").forward(request, response); ``` - 重定向 ```java // 注意:路径上要加一个项目名。 // 浏览器发送请求,请求路径上是需要添加项目名的。 // 以下这一行代码会将请求路径“/oa/dept/list”发送给浏览器 // 浏览器会自发的向服务器发送一次全新的请求:/oa/dept/list response.sendRedirect("/oa/dept/list"); ``` - 转发与重定向的区别 - 转发是在服务器内部进行操作,是一次请求,浏览器不需要额外操作 - 重定向是把要跳转的路径返回给浏览器由浏览器再次访问是两次请求 - 使用转发与重定向的判定 - 如果在原请求的Servlet中向请求域绑定了数据,则使用转发,同时将绑定的数据同步给新的资源 - 除此之外,均使用重定向。 - 因此重定向使用较多 - 注:跳转的下一个资源不一定是Servlet - 转发可能存在刷新时的一些问题(? ## Servlet注解代替xml配置 - 在之前的开发中,每一个Servlet应用都需要在web.xml中配置,导致开发繁琐易出错,可能有时需要不停切换java源码与配置文件 - Servlet3.0+引入了注解开发 - 使用注解开发,可以减小开发难度,增加开发效率 - 并不是说使用注解开发完全放弃xml配置文件,因为有时有些参数或者需要改变的数据,在配置文件中方便修改,避免调试或维护过程频繁修改代码。 - jakarta.servlet.annotation.WebServlet - 在Servlet类上使用@WebServlet注解,它具有以下参数。 - name:以指定Servlet的名字,等价于<servlet-name> - urlPartterns:指定映射的路径,可以指定多个字符串。 - loadOnStartUp:指定初始化等级 - 注:使用时只需填写需要的,不需要全部填写。 - 例 ```java package com.bjpowernode.javaweb.servlet; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; //@WebServlet(urlPatterns = {"/welcome1", "/welcome2"}) // 注意:当注解的属性是一个数组,并且数组中只有一个元素,大括号可以省略。 //@WebServlet(urlPatterns = "/welcome") // 这个value属性和urlPatterns属性一致,都是用来指定Servlet的映射路径的。 //@WebServlet(value = {"/welcome1", "/welcome2"}) // 如果注解的属性名是value的话,属性名也是可以省略的。 //@WebServlet(value = "/welcome1") //@WebServlet({"/wel", "/abc", "/def"}) @WebServlet("/wel") public class WelcomeServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print("欢迎学习Servlet。"); } } ``` ## Session(会话机制) ### Session机制 - 什么是Session - 一个用户打开浏览器在一个网站完成很多操作,直到关闭浏览器,叫做一个会话 - 一个会话包含多次请求 - Session对应Servlet包 - HttpSession - jarkata.servlet.http.HttpSession - Session的作用 - 保存用户状态。 - 由于HTTP是无状态协议(每次请求是独立的),需要一个机制来识别哪些请求来自同一个用户,例如登录一次后,在浏览器开启状态下继续使用无需重新登陆。 - 为什么不设计成有状态协议 有可能连接的用户数量非常多,无状态方便节省资源,不需要始终保持一个连接。 - 会话域 - 之前已经知道了请求域,应用域。会话域比请求域更广但没有应用域广 - 会话域的范围在一个会话之内,也就是一个用户之内 - 如果直接使用应用域存储用户信息,会造成无法分辨用户,及数据不安全的情况。 - Session对象的使用 - 在请求中,得到会话对象 ```java HttpSession session = request.getSession(); ``` - 注:不同的人请求返回的session对象不同,同一个人请求返回的对象相同。 - 常用方法 ```java // 获取key为name的对象 session.getAttribute(String name); // 获取keys,返回枚举(Enums) session.getAttributeNames(); // 设置一个数据 session.setAttribute(String s, Object o); // 删除名为s的数据 session.removeAttribute(String s); ``` - 请求域,会话域,应用域使用原则 - 尽量使用作用范围小的域。 - 例:在用户访问某个需要登录的网页时,判断其Session中是否有数据,没有就重定向到登录页面,有就正常显示。登陆成功之后将用户的登录信息添加到Session域 - 销毁Session对象 - 在登出或者过期的时候可以选择销毁Session对象 ```java session.invalidate(); ``` - Session的原理 - 浏览器在内存中以Cookie形式保存了JSESSIONID=xxxxxx...xxxx,当浏览器关闭时,这个SessionId就没有了。 - Session的池是一个Map,其中key是sessionid,value是session对象 在浏览器初次访问网页时,服务器自动生成session对象同时生成id并随响应返回给浏览器 在之后的访问中,服务器自动将sessionid随请求发送给服务器,服务器通过sessionid取出该用户对应的session对象以识别该用户身份。 - 若浏览器禁用Cookie,session机制依旧可以通过url重写机制实现,但需要在每个请求路径后加一个sessionid,大大增加项目开发复杂度,所以一般若禁用cookie就无法使用session机制 ### Cookie - cookie最终是保存在浏览器客户端上的。 - 可以保存在运行内存中。(浏览器只要关闭cookie就消失了。) - 也可以保存在硬盘文件中。(永久保存。) - cookie的作用 - cookie和session机制其实都是为了保存会话的状态。 - cookie是将会话的状态保存在浏览器客户端上。(cookie数据存储在浏览器客户端上的。) - session是将会话的状态保存在服务器端上。(session对象是存储在服务器上。) - 案例 - 在淘宝升级之前,选购进购物车的商品,即使没有登录,在关闭浏览器再打开后仍然存在。 - 将购物车中的商品编号放到cookie当中,cookie保存在硬盘文件当中。这样即使关闭浏览器。硬盘上的cookie还在。下一次再打开的时候,查看购物车的时候,会自动读取本地硬盘中存储的cookie,拿到商品编号,动态展示购物车中的商品。 - 注意:cookie如果清除掉,购物车中的商品就消失了。 - 十天内免登陆 - 用户输入正确的用户名和密码,并且同时选择十天内免登录。登录成功后。浏览器客户端会保存一个cookie,这个cookie中保存了用户名和密码等信息,这个cookie是保存在硬盘文件当中的,十天有效。在十天内用户再次访问的时候,浏览器自动提交cookie给服务器,服务器接收到cookie之后,获取用户名和密码,验证,通过之后,自动登录成功。 - 怎么让cookie失效? - 设置cookie期限 - 在客户端浏览器上手动清除cookie - 改密码 - HTTP协议中规定:任何一个cookie都是由name和value组成的。name和value都是字符串类型的。 - 当浏览器发送请求的时候,会自动携带该路径下的cookie数据给服务器。 - Servlet中使用Cookie - jakarta.servlet.http.Cookie; - 常用方法 ```java cookie.setMaxAge(60 * 60); // 设置cookie在一小时之后失效。 cookie.setPath(“/servlet13”); //表示只要是这个路径及其子路径,都会提交这个cookie给服务器 // 接收cookie Cookie[] cookies = request.getCookies(); // 这个方法可能返回null ``` - Cookie的有效时间 - 不设置则浏览器关闭时消失 - 设置为0:删除该Cookie和同名Cookie - 设置为>0,一定会在浏览器关闭时保存在硬盘中 - 设置为<0,和不设置一样 - Cookie的默认路径 - 假设现在发送的请求路径是“/servlet13/cookie/generate”生成的cookie,如果cookie没有设置path - 默认的path是:/servlet13/cookie 以及它的子路径。 - 也就是说,以后只要浏览器的请求路径是/servlet13/cookie 这个路径以及这个路径下的子路径,cookie都会被发送到服务器。 Last modification:April 4, 2022 © Allow specification reprint Like 6 如果觉得我的文章对你有用,请随意赞赏