1 生命周期
每个Servlet类从被服务器加载,到服务器关闭,都存在一个生命周期。
1.1 编码测试
WebRoot/index.html
<!DOCTYPE html>
<html>
<head>
<title>生命周期测试</title>
<meta charset="utf-8" />
</head>
<body>
<!--使用链接跳转是Get方法-->
<a href="servlet/TestLife">测试生命周期</a>
</body>
</html>
TestLife.java
package com.test.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestLife extends HttpServlet {
/**
* 构造方法
*/
public TestLife() {
System.out.println("我是Servlet的构造方法我是Servlet的构造方法我是Servlet的构造方法");
}
/**
* 销毁方法
*/
public void destroy() {
System.out.println("我是Servlet的销毁方法我是Servlet的销毁方法我是Servlet的销毁方法");
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
request.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
System.out.println("我是Servlet的doGet方法我是Servlet的doGet方法我是Servlet的doGet方法");
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
out.print(" This is ");
out.print(this.getClass());
out.println(", using the GET method");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
public void init() throws ServletException {
System.out.println("我是Servlet的初始化方法我是Servlet的初始化方法我是Servlet的初始化方法");
}
}
Servlet类是一个单实例多线程的Java程序(这里实例指的是Servlet类),它在这里由Tomcat服务器调度执行。
实例与线程 | 优缺点 |
---|---|
单实例单线程 | 每次只有一个实例,实例单线程地处理一个请求,效率低但线程安全,内存占用小 |
单实例多线程 | 每次只有一个实例,减少了内存的开销。实例开辟多个线程一次可以响应发来的多个请求,内存占用小,效率较高但易出现并发错误 |
多实例多线程 | 多个实例,实例开辟多个线程一次可以响应发来的多个请求,效率高,易出现并发错误,内存占用较大 |
-
当第一次点击链接发送请求时
之后多次发送请求,只有第三条语句执行 -
关闭服务器时
-
总结为执行顺序是:
1.构造方法(仅仅执行1次,执行于第一次接到请求时) 2.初始化方法(init仅仅执行1次,执行于第一次接到请求时,进行一些前期的准备工作) 3.service()(此方法根据提交的请求执行多次,根据提交过来的,方法类型get还是post, 调用doGet()或者doPost()。按文档说明,此方法不推荐我们覆盖, 而是直接在继承类中写doPost()/doGet(),发来请求时由父类已写好的service()调用即可) 4.doGet()||doPost()(此方法由父方法service()调用,执行多次,专门用来处理业务逻辑) 5.destroy()(此方法在tomcat关闭前执行,仅仅执行一次,一般用来释放一些资源等)
-
可以形象理解为:
要到一个学校(服务器)中找一个人,需要找到这个教学楼(Web容器,暂时理解为Tomcat),然后找到上课的班级(Tomcat中的工程),按照座次表(web.xml)找到班级的某个同学(某个Servlet类)。
1.2 Servlet的线程安全问题(了解)
既然Servlet是单实例多线程的,那一定会有并发错误的问题产生,如何使Servlet线程安全?
1.2.1 Servlet实现单实例多线程的原理
Servlet容器维护一个线程池,是工作者线程(执行代码的一组线程)的集合,同时还有一个调度线程(每个工作者线程都具有线程优先级,调度线程根据优先级调度它们执行)来管理工作者线程。
当容器收到一个Servlet请求,调度线程就从线程池中选出一个工作者线程,容器将请求传递给该工作者线程,然后工作者线程来执行Servlet类的service()。
当这个线程正在执行的时候,容器收到另外一个请求,调度线程同样从线程池中选出另一个工作者线程来服务新的请求。容器并不关心这个请求是否访问的是同一个Servlet类。
当多个请求同时访问同一个Servlet类时,这个Servlet的service()将在多线程中并发执行。并发执行必然会出现同步问题,也就是线程的安全问题。
解决这个线程安全问题涉及到数据结构和操作系统,数据结构帮助选择线程安全的数据类型,操作系统帮助找到线程安全的操作方法。
1.2.2 如何开发线程安全的Servlet类
-
变量的线程安全,这里的变量指字段和共享数据(如表单参数值)
a. 将参数变量本地化。多线程并不共享局部变量,所以我们要尽可能地在Servlet中使用局部变量。
b. 使用同步块Synchronized,防止可能异步调用的代码块,这意味着线程需要排队处理。但在使用同步块的时候要尽可能的缩小同步代码的范围,不要直接在sevice()和响应方法上使用同步,这样会严重影响性能。 -
属性的线程安全,ServletContext、HttpSession、ServletRequest对象中的属性
a. ServletContext(线程是不安全的)
ServletContext是可以多线程同时读/写属性的,线程是不安全的。要对属性的读写进行同步处理或者进行深度Clone()。 所以在Servlet上下文中尽可能少地保存会被修改(写)的数据,可以采取其他方式在多个Servlet中共享,比方我们可以使用单例模式来处理共享数据。b. HttpSession(线程是不安全的)
HttpSession对象在用户会话期间存在,且只能在处理属于同一个Session的请求的线程中被访问,因此Session对象的属性访问理论上是线程安全的。
当同一个用户打开多个同属于一个进程的浏览器窗口,在这些窗口的访问属于同一个Session,这时会出现多次请求,需要多个工作者线程来处理请求,可能造成同时多线程读写属性。 这时我们需要对属性的读写进行同步处理:使用同步块Synchronized和使用读/写器来解决。ServletRequest:(线程是安全的)
每一个请求,由不同的工作者线程来执行,都会创建有一个新的ServletRequest对象,所以ServletRequest对象只能在一个线程中被访问。ServletRequest是线程安全的。
注意:ServletRequest对象在service()的范围内是有效的,不要试图在service()结束后仍然保存请求对象的引用。 -
使用同步的集合类:
使用Vector代替ArrayList,使用Hashtable代替HashMap。或直接使用util包中的新特性,线程安全的集合。 -
不要在Servlet中创建自己的线程来完成某个功能。
Servlet本身就是多线程的,在Servlet中再创建线程,将导致执行情况复杂化,出现多线程安全问题。 -
在多个Servlet中对外部对象(比如文件)进行修改操作一定要加锁,做到互斥的访问。
-
javax.servlet.SingleThreadModel接口是一个标识接口,如果一个Servlet实现了这个接口,那Servlet容器将保证在一个时刻仅有一个线程可以在给定的Servlet实例的servic()中执行。将其他所有请求进行排队。
服务器可以使用多个实例来处理请求,代替单个实例的请求排队带来的效益问题。服务器创建一个Servlet类的多个Servlet实例组成的实例池,对于每个请求分配Servlet实例进行响应处理,之后放回到实例池中等待下此请求。这样就造成并发访问的问题。
此时,局部变量也是安全的,但对于全局变量和共享数据是不安全的,需要进行同步处理。而对于这样多实例的情况SingleThreadModel接口并不能解决并发访问问题。SingleThreadModel接口在servlet规范中已被废弃。
1.3 Servlet常用接口方法
测试常用的接口HttpServletRequest、ServletContext、ServletConfig的一些方法的作用。参照下面web.xml结构,ServletContext与ServletConfig的关系是ServletContext对应的是一个web.xml文件中设置的公有参数,ServletContext中可包含很多ServletConfig,用来设置初始化Servlet类的私有参数。
WebRoot/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>ServletDay2_life</display-name>
<!--
设置公有参数
此参数ServletContext当中拿取
-->
<!-- 整个工程中所有Servlet都可以根据这个key值拿取value值 -->
<context-param>
<param-name>我是公有参数</param-name>
<param-value>我是公有参数的值</param-value>
</context-param>
<context-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</context-param>
<servlet>
<servlet-name>TestLife</servlet-name>
<servlet-class>com.etoak.servlet.TestLife</servlet-class>
<!--
设置私有的初始化参数init-param,
param-name为初始化参数的名字
param-value为初始化参数的值
此参数只能从此Servlet实例(ServletConfig)当中拿取
-->
<init-param>
<!-- 设置键值 -->
<param-name>我是私有参数</param-name>
<!-- 设置值 -->
<param-value>我是私有参数的值</param-value>
</init-param>
<init-param>
<param-name>mycoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>TestLife</servlet-name>
<url-pattern>/servlet/TestLife</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
/servlet/TestLife
package com.test.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestLife extends HttpServlet {
private String code;
/**
* 构造方法
*/
public TestLife() {
System.out.println("我是Servlet的构造方法我是Servlet的构造方法我是Servlet的构造方法");
}
/**
* 销毁方法
*/
public void destroy() {
System.out.println("我是Servlet的销毁方法我是Servlet的销毁方法我是Servlet的销毁方法");
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//直接通过web.xml配置文件获取到编码,降低耦合度
response.setContentType("text/html;charset=" + code);
request.setCharacterEncoding(code);
/*
* HttpServletRequest
* 封装了处理所有请求的方法
*
* 与路径有关
* String getMethod() 返回请求的Http的请求方式(例如GET, POST)
* String getQueryString() 返回查询字符串,对Get方式有效,返回的是请求URI路径的根路径名
* 如http://localhost:8080/servlet/Login?a=b&c=d&e=f 得到a=b&c=d&e=f
* String getContextPath() 返回请求的项目名(根路径)
* String getRequestURI() 返回请求的除去host(域名或者ip)部分的路径
* StringBuffer getRequestURL() 返回请求的全路径
* String getServletPath() 返回请求的URL中调用servlet的部分(相当于web.xml中url-pattern标签中的值)
*
* 与字符和MIME类型有关
* void setCharacterEncoding(String env)
throws UnsupportedEncodingException 设置发来的请求中字符集编码解析方式
*
* 与主机信息有关
* int getLocalPort() 返回请求的URL中的端口号
*
* 与参数有关
* Enumeration<String> getParameterNames() 返回包含此请求中所包含参数的名称的String对象的枚举。
* 如果该请求没有参数,则此方法返回一个空的枚举。
* String getParameter(String name) 以String形式返回请求参数的值,如果该参数不存在,则返回null。
* String[] getParameterValues(String name) 返回包含给定请求参数拥有的所有值的 String 对象数组,
* 如果该参数不存在,则返回 null。
*
* */
System.out.println("getMethod():" + request.getMethod());
System.out.println("getQueryString():" + request.getQueryString());
System.out.println("getContextPath():" + request.getContextPath());
System.out.println("getRequestURI():" + request.getRequestURI());
System.out.println("getRequestURL():" + request.getRequestURL());
System.out.println("getServletPath():" + request.getServletPath());
System.out.println("getLocalPort():" + request.getLocalPort());
PrintWriter out = response.getWriter();
System.out.println("我是Servlet的doGet方法我是Servlet的doGet方法我是Servlet的doGet方法");
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>A Servlet</TITLE></HEAD>");
out.println(" <BODY>");
out.print(" This is ");
out.print(this.getClass());
out.println(", using the GET method");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
}
public void init() throws ServletException {
System.out.println("我是Servlet的初始化方法我是Servlet的初始化方法我是Servlet的初始化方法");
/*
* ServletConfig
* 接口,封装了所有servlet私有的参数设置
* 通过此接口的方法,可以拿取web.xml特定Servlet实例中的
* 一些参数,例如私有参数,拦截路径等等
* 常用方法:
* String getServletName() 返回配置在web.xml这个类对应的Servlet标签的名字
*
* ServletContext getServletContext() 返回调用者所在的ServletContext类的引用
*
* String getInitParameter(String key) 返回配置在web.xml这个类对应的Servlet标签中传入的key对应的私有参数的值,
* 如果没有私有参数,则返回null
*
* Enumeration<String> getInitParameterNames() 返回配置在web.xml这个类对应的Servlet标签的私有参数的名字,
* 使用String的枚举类型接收,如果没有私有参数,返回一个空枚举
* */
/*
* 调用父类写好的getServletConfig(),使用this即可
* 这个写法不推荐,多此一举
* TestLife tl = new TestLife();
* ServletConfig config = tl.getServletConfig();
*
* */
ServletConfig config = this.getServletConfig();
//返回配置在web.xml这个类对应的Servlet标签的私有参数的值
String value1 = config.getInitParameter("我是私有参数");
System.out.println("拿取的私有参数是:"+value1);
//私有参数的应用,为这个Servlet的请求和响应指定字符集,降低耦合度
//最好在公有参数中定义,这样所有Servlet类都可以使用,这里可以定义这个类特殊的字符集
//code = config.getInitParameter("mycoding");
//返回配置在web.xml这个类对应的Servlet标签的名字
String servletName = config.getServletName();
System.out.println("当前web.xml中这个Servlet类对应的Servlet标签的name是:" + servletName);
/*
* ServletContext
* 接口,封装了所有本工程所有Servlet类的参数设置,
* 通过此接口的方法,可以拿取整个工程中所有Servlet实例共享的一些参数,比如工程名
*
* String getRealPath(String path) 获取当前项目的绝对磁盘路径
*
* .getRealPath(""); -->Tomcat所在的文件夹下的\webapps\项目名
* .getRealPath("/"); -->Tomcat所在的文件夹下的\webapps\项目名\
* .getRealPath("/文件名"); -->Tomcat所在的文件夹下的\webapps\项目名\文件名
*
* String getInitParameter(String key) 返回Context(整个web.xml)传入的key对应的公有参数的值,
* 如果没有私有参数,则返回null
*
* Enumeration<String> getInitParameterNames() 返回Context(整个web.xml)公有参数的名字,
* 使用String的枚举类型接收,如果没有私有参数,返回一个空枚举
*
* */
//拿取ServletContext,以下两种方式拿取同样的上下文,config也是通过this拿取的
//对应同一个web.xml
ServletContext application = config.getServletContext();
ServletContext application1 = this.getServletContext();
//返回Context(整个web.xml)传入的key对应的公有参数的值
String value = application.getInitParameter("我是公有参数");
System.out.println("拿取的公有参数是:"+value);
//公有参数的应用,为所有这个工程下的Servlet类配置request、response的字符集,降低耦合度
code = application.getInitParameter("encoding");
String realPath = application.getRealPath("TestLife");
System.out.println("获取到的RealPath是" + realPath);
}
}
- 运行结果:
1.4 一个请求从客户端发出到响应回客户端经历了什么?
- 浏览器阶段:
S1.用户在浏览器上点击一个链接(此时发送一个请求)
S2.浏览器将请求格式化,并将其发送至服务器 - 服务器阶段:
S3.若是静态页面,web容器找到所请求页面即返回(转到S8);若是动态页面,请求指向一个Servlet
S4.容器创建两个对象:HttpServletRequest、HttpServletResponse,前者负责处理发来的请求并将所有请求参数都封装到request对象中,后者负责响应回客户端时的展现方式将要响应回客户端的信息封装到response对象
S5.容器根据请求中的URL找到正确的Servlet为这个请求创建或者分配一个线程,并把请求和响应对象传递给这个Servlet线程
S6.容器调用Servlet的service(),根据不同的请求类型,service()会调用doGet()或者doPost()
S7.doGet()或doPost()生成动态页面,并把这个页面变化为字符流写入响应对象里。
S8.线程结束,容器把响应对象格式化为一个Http响应,并把它返回给客户端(浏览器),然后删除请求和响应对象 - 浏览器阶段:
S9.浏览器接到Http响应并解析成为HTML页面,显示给用户
2 四种范围、Session传值、两种跳转方式
2.1 Servlet的四种范围及其局限性
所谓四种范围是指以下四个对象都可以通过setAttribute(String,Object)来封装信息,通过getAttribute(String)来拿取信息。如果可以成功拿取,则称之为范围有效,否则范围失效。
范围名称 | 局限性 |
---|---|
PageContext | 只要跳转封装的信息立刻失效,一般用不到。 |
HttpServletRequest | 如果是重定向则封装的信息立刻失效,因为重定向不是同一次请求。常存放一些时效性比较强的值。 |
HttpSession | 只要Session不销毁,则封装的信息可以通过getAttribute()拿取,常用于封装权限信息。 |
ServletContext | 只要Tomcat不关闭,封装的信息永远有效。setAttribute()参数中的名字,全局叫这个名字的值只有一份,当其他线程再设置了之后,之前设置的值就改了。 |
2.2 拿取用来封装权限信息的HttpSession
HttpSession是Servlet中两种会话跟踪机制之一,另一种是Cookie,后面学。
当用户书写request.getSession()代码时,会创建一个新的session或者拿取已经存在的session,通过session.setAttribute(String,Object)来封装权限信息,这个session默认存在1800s,超过这个时间会销毁。在此时间之内,如果用户再次提交请求,则session时间重置,所以理论上用户如果一直进行操作,则session一直有效,用户关闭浏览器会导致session立刻失效。
2.3 Servlet的两种跳转方式:请求转发(服务器跳转)和重定向(客户端跳转)★
服务器收到请求需要请求别的页面,这时使用到Servlet类的跳转。方式有两种,请求转发(request.getRequestDispatcher,服务器跳转)、重定向(response.sendRedirect,客户端跳转)。
-
请求转发(request.getRequestDispatcher,服务器跳转)
- 顾名思义,这是服务器的行为。请求由服务器转发给另外一个页面处理,如何转发,何时转发,转发几次,客户端是不知道的。
- request时效问题:请求转发时,从发送第一次到最后一次请求的过程中,WEB容器只创建一次request和response对象,新的页面继续处理同一个请求。
- 请求方式和地址栏信息: 请求方式也不会发生变化,地址栏信息也不会发生变化,request封装的参数继续有效(通过request.setAttribute存的值依然可以response.getAttribute取出)。这种方式也可以理解为服务器将request对象在页面之间传递。
- 路径问题: 请求转发是服务器某个工程内部的转发,转来转去都是在某个工程内部,所以不需要手动声明工程名。
- 过程: 客户浏览器发送HTTP请求–>WEB服务器接受此请求–>调用内部的一个方法在容器内部完成请求处理和转发动作–>将目标资源发送给客户
- 转发的路径必须是同一个WEB容器下的URL,其不能转向到其他的WEB路径上去
-
重定向(response.sendRedirect,客户端跳转)
- 客户端进行重定向,表单提交时常用。由Servlet类返回响应给浏览器,浏览器再次发出请求重定向到目的地。
- request时效问题: 重定向一次,就刷新一次request属性,此时与之前不再是同一次请求,所以request范围存储的参数失效。此时可以考虑使用sendRedirect()参数也就是URL中携带参数供下个页面获取,因为下个页面接收到的请求一定是GET。(具体可以看下面框框中的内容)
- 请求方式和地址栏信息: 不管跳转前是什么请求方式,跳转之后肯定是GET,因为是浏览器最终发送的,而浏览器只会发送GET请求。浏览器的地址栏显示的就是最终的跳转路径,地址栏信息会发生变化。
- 路径问题: 重定向是浏览器发来的,只知道发到某个服务器,但是不知道发到服务器的哪个工程,所以需要自己用代码声明。
- 过程: 客户浏览器发送HTTP请求–>WEB服务器接受后发送302状态码响应及对应新的location给客户浏览器–>客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址–>服务器根据此请求寻找资源并发送给客户
- location可以重定向到任意URL
因为该方法执行完之后就相当于一次HTTP request的结束,这时服务器会向客户端发送302状态码和新的URL,告诉客户端重新发送request请求到新的URL ,然后客户端照此执行,执行即新的请求响应流程开始,服务器再重新创建HttpServletRequest对象和HttpServletResponse对象,此时两个请求已不在一个线程,故之前的两个对象均无法使用。
2.3.1 参数中路径的写法
转发和重定向的URL,最前面加“/”为绝对路径,反之为相对路径。
-
绝对路径
-
请求转发的“/”表示“http://服务器ip:端口/工程名”
request.getRequestDispatcher("/index.jsp").forward(request, response);
生成的地址:http://localhost:8080/项目名/index.jsp
(若是Servlet类,则相当在工程名后面拼接url-pattern标签的内容) -
重定向的“/”表示“http://服务器ip:端口”
response.sendRedirect("/工程名/index.jsp")
生成的地址:web服务器本身地址+参数生成完整的URL 即:http://localhost:8080/工程名/index.jsp
链接、表单的绝对路径也需要从工程名开始。
-
请求转发是服务器某个某个内部的转发,转来转去都是在某个项目内部,所以不需要手动声明项目名。
重定向是浏览器发来的,只知道发到某个服务器,但是不知道发到服务器的哪个项目,所以需要自己用代码声明。
- 相对路径
请求转发相对路径,相对于原来请求URL的目录加传入的相对路径参数来生成完整的URL,如:
原来请求的URL为http://localhost:8080/项目名/
- 请求转发:request.getRequestDispatcher(“index.jsp”)
生成相对路径:http://localhost:8080/项目名/index.jsp - 重定向:不推荐重定向使用相对路径。
原来请求的URL为http://localhost:8080/项目名/xxx
要在这个URL的基础上生成http://localhost:8080/项目名/index.jsp
请求转发时需要先使用“../”request.getRequestDispatcher("../index.jsp")
若再向上一级,再加一个“../”
本章节部分摘自小文是蜀黍@CSDN的博文,感谢大佬!
2.4 编写测试
WebRoot/index.html
<!DOCTYPE html>
<html>
<head>
<title>用户登录</title>
<meta charset="utf-8" />
</head>
<body>
<div>
<h3>用户登录</h3>
<hr />
<form action="servlet/Login" method="post">
<label for="nameid">用户姓名:</label>
<input type="text" name="name" id="nameid"
style="width:138px" required placeholder="请输入用户名" />
<br />
<label for="passid">用户密码:</label>
<input type="password" name="pass" id="passid"
style="width:138px" required placeholder="请输入用户密码" />
<br />
<input type="submit" value="提交" />
<input type="reset" value="取消" />
</form>
</div>
</body>
</html>
servlet/Login.java
package com.test.servlet;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class Login extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置请求响应的字符集
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//拿取客户端页面表单发来请求中name对应的值
String name = request.getParameter("name");
String passwd = request.getParameter("passwd");
//拿取可以封装session参数的HttpSession和封装全局参数的ServletContext
HttpSession session = request.getSession();
ServletContext application = this.getServletContext();
//不写po、dao层,使用个测试数据先,下面是用户名密码验证正确时
if("Eric".trim().equals(name) && "aaa123".trim().equals(passwd)){
/*
* 分别使用HttpServletRequest HttpSession ServletContext
* 的三个对象(范围)使用相同的方法封装信息,
* 使用方法setAttribute(String,Object)
* */
request.setAttribute("key1", "使用HttpServletRequest存储的《江湖儿女》");
session.setAttribute("key2", "使用HttpSession存储的《邪不压正》");
application.setAttribute("key3", "使用ServletContext存储的《无名之辈》");
//将用户名和密码封装进session范围
session.setAttribute("name", name);
session.setAttribute("passwd", passwd);
//使用请求转发跳转到Servlet类 Show.java,请求一直是post
request.getRequestDispatcher("/servlet/Show").forward(request, response);
return;
}
//验证错误时写入的数据
request.setAttribute("key1", "使用HttpServletRequest存储的《逐梦演艺圈》");
session.setAttribute("key2", "使用HttpSession存储的《小时代》");
application.setAttribute("key3", "使用ServletContext存储的《敢问路在何方》");
//使用重定向跳转到Servlet类 Show.java,请求由post最后更换为get
response.sendRedirect("/ServletDay2_scope_my/servlet/Show");
}
}
servlet/Show.java
package com.test.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class Show extends HttpServlet {
//请求转发的post请求不变,走这个方法
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
//拿取ServletContext
ServletContext application = this.getServletContext();
//拿取HttpSession
HttpSession session = request.getSession();
//拿取跳转之前封装的信息
String value1 = (String)request.getAttribute("key1");
String value2 = (String)session.getAttribute("key2");
String value3 = (String)application.getAttribute("key3");
//拿取Login.java中可能存入的name/passwd
String name = (String)session.getAttribute("name");
String passwd = (String)session.getAttribute("passwd");
//证明用户已登录进来,进行的操作
if(name != null && passwd != null){
//已登录进来,就不需要session记录用户的用户名和密码了
//在这里强制删除session中记录的数据
session.removeAttribute("name");
session.removeAttribute("passwd");
//使用字符流写入页面要显示的东西
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>登录成功</TITLE></HEAD>");
out.println(" <BODY>");
out.println("欢迎您回来"+name+"<hr />");
out.println("<ul>");
out.println("<li>"+value1+"</li>");
out.println("<li>"+value2+"</li>");
out.println("<li>"+value3+"</li>");
out.println("</ul>");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
return;
}
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
out.println("<HTML>");
out.println(" <HEAD><TITLE>登录失败</TITLE></HEAD>");
out.println(" <BODY>");
out.print("数据库中查无此人,这里是游客游览区,以下为试看电影<hr/>");
out.print("<ul>");
out.print("<li>"+value1+"</li>");
out.print("<li>"+value2+"</li>");
out.print("<li>"+value3+"</li>");
out.print("</ul>");
out.println(" </BODY>");
out.println("</HTML>");
out.flush();
out.close();
}
//重定向后,浏览器发来的请求一定是get请求,走这个方法
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
WebRoot/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>ServletDay2_scope_my</display-name>
<servlet>
<servlet-name>Login</servlet-name>
<servlet-class>com.test.servlet.Login</servlet-class>
</servlet>
<servlet>
<servlet-name>Show</servlet-name>
<servlet-class>com.test.servlet.Show</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Login</servlet-name>
<url-pattern>/servlet/Login</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Show</servlet-name>
<url-pattern>/servlet/Show</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>
效果图
首页登录
姓名密码正确时,使用请求转发跳转,request存的参数能取到,浏览器中链接不变
姓名密码错误时,使用重定向跳转,request存的参数不能取到,浏览器中链接改变