文章目录
导论(九个内置对象)
之前使用的对象大部分是我们自己使用new关键字或者是反射创建的,而以下的9个内置对象是Tomcat容器会自动创建的,我们直接使用即可。
名称 | 类型 | 描述 |
---|---|---|
Request | HttpServletRequest | 表示一次用户请求 |
Session | HttpSession | 表示一个用户信息 |
Application | ServletContext | 表示服务器的上下文环境 |
Response | HttpServletResponse | 表示一次响应 |
Config | ServletConfig | 可以获取配置文件的一些信息 |
PageContext | jsp.PageContext | 表示jsp页面上下文环境 |
Page | Object | 表示一个页面对象 |
Out | JspWriter | 可以使用其输出信息 |
Exception | Throwable | 表示异常信息 |
一、Request内置对象
(一)什么是Request内置对象
1.内置对象就是容器已经创建好的,我们可以直接使用的对象
2.在容器中如果接收到一个用户的请求,会自动创建一个对象来处理客户端发送的请求
3.这个对象就是内置对象,该对象的类型的类型是HttpServletRequest,名称是Request
4.在调用service方法时容器会自动传递该对象给方法
(二)Request对象的基本方法
//方法
Cookie[] getCookies();取得客户端传递的cookie信息
String gerHeader(String var1);取得请求头信息,根据请求头名称取得对应的值
Enumeration<String> getHeaderNames();按照枚举的方式取得所有的请求头信息
String getMethod();取得请求的方式
String getPathInfo();取得额外路径(emp之后的路径,/*)
String getContextPath();取得当前项目的根路径
Strng getQueryString();取得url地址问号后面的内容(参数)
String getRequestURI();取得uri地址
String getServletPath();取得Servlet的映射路径(在web.xml文件中配置的url-pattern)
HttpSession getSession();取得session内置对象
//测试部分方法
System.out.println("uri地址:"+req.getRequestURI());///ServletProject/emp/wefw
//Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
System.out.println("浏览器信息:"+req.getHeader("User-Agent"));
System.out.println("额外地址:"+req.getPathInfo());///wefw
System.out.println("servlet的映射路径:"+req.getServletPath());///emp
System.out.println("获取Servlet的认证名"+req.getAuthType());//null
System.out.println("请求body使用的编码方式的名字"+req.getCharacterEncoding());//null
System.out.println(req.getRemoteUser());//null
System.out.println("主机名称"+req.getRemoteHost());//主机名称0:0:0:0:0:0:0:1
(三)Request的其他方法
1.需求:取得表单提交的参数
req.getParameter(“参数名”)
//index.html文件form表单
<body>
<form action="emp" method="post">
<fieldset>
<legend>请登录</legend>
用户名:<input type="text" name="username"><br><br>
密 码:<input type="password" name="pwd"><br><br>
<input type="submit" value="登录">
</fieldset>
</form>
</body>
//EmpServlet,doPost()
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String pwd = req.getParameter("pwd");
System.out.println("输入的用户名:"+username+",密码:"+pwd);
}
运行结果
2.保存和取得属性
req.setAttribute(String name,Object o);
req.getAttribute(String name);
后面会在servlet中将数据保存之后跳转到jsp页面中取得数据显示
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//实例化一个Emp类对象
Emp emp = new Emp(1001, "smith", "driver", 778, 4000.0, new Date(), 1000.0, 10);
//将emp对象保存到Request内置对象中
req.setAttribute("obj", emp);
//取得Request内置对象的参数
System.out.println(req.getAttribute("obj"));
}
运行结果
3.取得额外路径
req.getPathInfo();
==用处:==可以根据额外路径来调用具体的方法。比如登录和注销都要在一个servlet中实现,此时servlet中只有doPost和doGet方法,剋根据额外路径来区分处理的是哪一种业务
//<servlet-mapping>元素中,将之前的url-pattern换成/emp/*
<url-pattern>/emp/*</url-pattern>
//EmpServlet类,将映射路径配置为/emp/*后,无论*是什么内容,都可以获取到/*的额外路径
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("额外路径为:"+req.getPathInfo());
}
运行结果
4.实现伪登录和注销
form表单action属性值,a标签href属性值
//index.html文件,浏览器输入localhost/项目名/index.html(文件名)
<body>
<!-- action属性的值是表单数据要提交的路径,可获取到额外路径/login -->
<form action="emp/login" method="post">
<fieldset>
<legend>请登录</legend>
用户名:<input type="text" name="username"><br><br>
密 码:<input type="password" name="pwd"><br><br>
<input type="submit" value="登录">
<!-- href属性的值是要跳转的链接,可获取到额外路径/logout -->
<a href="emp/logout">注销</a>
</fieldset>
</form>
</body>
//EmpServlet.java
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//即使请求是get方式,也可以运行到doPost的代码,貌似上面index.html中的<a>标签跳转链接就是这种情况
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String pathInfo = req.getPathInfo();
if(pathInfo.equals("/login")) {
this.login(req, resp);
} else if(pathInfo.equals("/logout")) {
this.logout(req, resp);
}
}
public void login(HttpServletRequest req,HttpServletResponse resp) {
String name = req.getParameter("username");
String pwd = req.getParameter("pwd");
if(name.equals("smith")&&pwd.equals("1234")) {
System.out.println("登录成功!");
} else {
System.out.println("用户名密码不正确!");
}
}
public void logout(HttpServletRequest req,HttpServletResponse resp) {
System.out.println("注销成功!");
}
运行结果
5.取得所有请求头的信息
req.getHeaderNames();
referer取得这个头名称信息,可以使用以实现防盗链等操作
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取到包含所有头名称的枚举类对象
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
System.out.println(headerName+":"+req.getHeader(headerName));
}
}
运行结果
(四)服务器端跳转(服务器端转发/重定向)
1.实现原理
(1)用户发送请求到服务器
(2)服务器会将请求交给对应的控制器(Servlet)处理,在控制器中调用业务层方法取得数据
(3)之后将取得数据保存到request内置对象
(4)然后将跳转到页面将保存在Request内置对象中的数据再次取出来之后显示到页面(用户看到信息)
2.服务器端跳转的特点
(1)实现的方式是调用RequestDispatcher类的forward()方法实现
(2)服务器端转发浏览器的地址栏信息不会发生改变
(3)使用服务器端转发之后,可以自爱jsp页面中取得保存在Request内置对象中的属性(后面还有一些内置对象中的属性也可以取得)
(4)使用${保存的属性名,对象属性名}的方式取得对应的值(叫做EL表达式)
3.具体实现代码???(浏览器中没有获取到数据)
public class EmpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//浏览器页面未显示数据,设置了编码也没用
req.setCharacterEncoding("utf-8");
//调用业务层方法取得雇员的信息
Emp emp = new Emp(1001, "smith", "driver", 7788, 4000.0, new Date(), 1000.0, 10);
//将取得的对象保存在Request内置对象中
req.setAttribute("obj", emp);
//取得实现服务器端重定向的对象
RequestDispatcher dispatcher = req.getRequestDispatcher("/emp.jsp");
//实现页面跳转(使用服务器重定向
dispatcher.forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
}
<!-- emp.jsp -->
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>显示雇员的信息</h1>
<table border="1">
<tr>
<td>编号</td><td>姓名</td><td>工作</td><td>上级编号</td><td>薪资</td><td>入职日期</td><td>佣金</td><td>部门编号</td>
</tr>
<!-- 下面这行不清楚为什么就是不显示数据 -->
<tr>
<td>${obj.empno}</td><td>${obj.ename}</td><td>${obj.job}</td><td>${obj.mgr}</td><td>${obj.sal}</td><td>${obj.hiredate}</td><td>${obj.comm}</td><td>${obj.deptno}</td>
</tr>
</table>
</body>
</html>
运行结果
二、Respons内置对象
(一)特点
1.Request内置对象:处理用户的请求
2.Response内置对象:处理对用户的响应
3.类型:java.servlet.http.HttpServletResponse
4.在调用service方法的时候容器会传递过来
(二)重要方法
public void addCookie(Cookie cookie);向客户端浏览器添加一个cookie信息
public void setMaxAge(时间毫秒);设置cookie存活时间
public void sendRedirect(java.lang.String location);该方法可以实现客户端跳转
public vlid setStatus(int sc);设置响应的状态码
public java.io.PrintWriter getWriter();取得一个向客户端输出信息的打印流对象,可以使用该对象实现信息的输出
public void setContentType(java.lang.String type);可以设置响应的MIME类型和字符编码
MIME用于设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展文件被访问时,浏览器会自动使用指定的应用程序来打开,常见的MIME有:
超文本标记语言文本 text/html
普通文本 .txt text/plain
RTF文本 .rtf application/rtf
GIF图形 .gif image/gif
JPEG图形 .jpeg,.jpg image/jpeg
GZIP文件 .gz application/x-gzip
TAR文件 .tar application/x-tar
(三)需求
1.向客户端输出信息
resp.getWriter().print(字符串);
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.向客户端输出信息
//使用Response内置对象取得输出流对象
PrintWriter out = resp.getWriter();
//向客户端输出信息
out.print("这是服务器端返回的数据");
out.close();
}
运行结果,出现中文乱码,原因是输出的字符串的编码和浏览器的解析编码不一致导致的,此时可以设置为utf-8
2.设置响应的字符编码
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置响应的编码
resp.setContentType("text/html;charset=utf-8");
//1.向客户端输出信息
//使用Response内置对象取得输出流对象
PrintWriter out = resp.getWriter();
//向客户端输出信息
out.print("这是服务器端返回的数据");
out.close();
}
运行结果
3.观察服务端默认的cookie信息
(1)一般情况下默认第一次访问浏览器的时候会添加一个cookie信息到浏览器
(2)这个cookie信息就是客户端和服务器端互相辨认的一个唯一标识
(3)在客户第一次访问服务器的时候,服务器端会创建一个标记,这个标记在容器(服务器端)中保存一份
(4)之后再将这个标识做一个副本保存到浏览器端,第二次客户再次访问的时候会带上cookie信息,之后服务器端取得就可以验证了
4.创建自己的cookie、设置存活时间
resp.addCookie(Cookie对象);
c1.setMaxAge(时间);
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//创建cookie信息
Cookie c1 = new Cookie("c1", "1234");
//为cookie设置存活时间,未设置存活时间的cookie一般是在浏览器关闭后销毁,设置了存活时间则按照具体的设置时间
c1.setMaxAge(60*60*24);
Cookie c2 = new Cookie("c2", "abcd");
//把cookie保存到浏览器
resp.addCookie(c1);
resp.addCookie(c2);
}
运行结果:左边为服务器默认创建的cookie信息,右边为自己创建的c1,并设置了存活时间
5.在服务器端取得cookie信息
req.getCookies();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//在服务器端取得所有的cookie信息
Cookie[] cookies = req.getCookies();
for (Cookie c : cookies) {
System.out.println(c.getName()+":"+c.getValue());
}
}
运行结果
(四)客户端重定向
1.概念
(1)服务器端重定向:客户端发送一次请求之后,在服务器做了多次转发,但是最终对于客户端来说就是发送一次请求【req.getRequestDispatcher(转发路径).forward(req,resp)】
(2)客户端转发:每次进行的转发都是客户端的一个新的请求
(3)实现客户端转发的方式有很多,比如页面的超链接转发、表单提交、js中使用window.location.href实现的转发都是客户端转发,但是以上的转发都是在客户端直接实现的
(4)如果在servlet中实现客户端转发,需要使用Response对象的方法【sendRedirect(转发的路径)】
2.服务器端重定向和客户端重定向区别
客户端转发
(1)浏览器地址栏信息改变,不再是提交表单的路径
(2)客户端转发就是发送了一次新的请求,在下面的案例中登录共发送了2次请求(login.html–>index.html)
(3)客户端转发之后保存在Request内置对象中的属性不存在了(因为是一次新的请求,而Request只能表示当次请求)
服务器端转发
(1)浏览器地址栏信息没有发生改变
(2)转发欢迎页面(jsp页面)之后依然可以取得保存在Request内置对象中的属性
(3)对于客户端来说只发送了一次请求
3.客户端转发
在浏览器输入localhost/ServletProject/login.html,并输入账号密码
<!-- login.html文件 -->
<body>
<form action="emp/login" method="post">
<fieldset>
<legend>请登录</legend>
用户名:<input type="text" name="username"><br><br>
密 码:<input type="password" name="pwd"><br><br>
<input type="submit" value="登录">
<!-- href属性的值是要跳转的链接,可获取到额外路径/logout -->
<a href="emp/logout">注销</a>
</fieldset>
</form>
</body>
<!-- index.html文件 -->
<body>
<h1>欢迎来到首页!</h1>
</body>
//EmpServlet.java
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String pathInfo = req.getPathInfo();
if(pathInfo.equals("/login")) {
this.login(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
public void login(HttpServletRequest req,HttpServletResponse resp) throws IOException, ServletException {
String name = req.getParameter("username");
String pwd = req.getParameter("pwd");
if(name.equals("smith")&&pwd.equals("1234")) {
//用户名密码正确登录成功,则客户端跳转到首页index.html
resp.sendRedirect("/ServletProject/index.html");
} else {
//用户名密码错误,则返回登录页面再次登录(使用服务器端转发)
req.getRequestDispatcher("/login.html").forward(req, resp);
}
}
运行结果:smith,1234输入之后登录成功客户端跳转到index.html
三、Session内置对象
(一)为什么有Session内置对象
1.Request内置对象中的属性只是在当次请求中有效(经过客户端跳转之后就没效)
2.也就是说Request只代表当次请求的对象,如果要让客户端跳转之后保存的属性还有效,则可以使用session内置对象,因为该对象表示的是一个用户,即使客户端跳转了Session保存的属性还是有效的
(二)概念和方法
1.Session内对象的类型javax.servlet.http.HttpSession
2.isNew()之所以能够判断是否是新用户,是因为浏览器发送请求的时候会将cookie带到服务器端,之后使用cookie中JSESSIONID和服务端的比较,如果匹配上则不是新的用户,否则就是一个新用户
public void setAttribute(String name,Object value);保存属性
public Object getAttribute(String name);根据属性名取得值(只能取得用setAttribute()保存的数据值)
public void removeValue(String name);根据属性名称删除对应的值,只能删除使用
setAttribute()保存的数据值
public boolean isNew();判断当前访问的用户是否是第一次访问
public void invalidate();销毁当前Session,一般用来实现用户注销功能
public String getId();取得Session的编号,其实这个编号和浏览器中名字叫做JSESSIONID的cookie的值是一样的
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取到Session内置对象
HttpSession session = req.getSession();
//取得Session的编号
System.out.println(session.getId());
//判断是否是新用户
boolean flag = session.isNew();
if(flag) {
System.out.println("是新用户");
} else {
System.out.println("是老用户");
}
}
(三)表单登录验证
index.jsp和login.jsp表单代码如下
<!-- login.jsp -->
<body>
<form action="emp/login" method="post">
<fieldset>
<legend>登录页面</legend>
用户名<input type="text" name="username"><br><br>
密† 码<input type="password" name="pwd"><br><br>
<input type="submit" value="提交">
</fieldset>
<span>${msg}</span>
</form>
</body>
<!-- index.jsp -->
<body>
<h1>欢迎${name}来到首页!</h1>
<a href="emp/logout">注销”€</a>
</body>
1.客户端跳转之后对象保存的属性是否有效
(1)req.setAttribute()设置的属性,index.jsp的EL表达式${name}无法获取到值。因为登录成功之后使用了客户端跳转,Request对象只代表当次请求的对象,客户端跳转之后就无效了
(2)而使用Session对象保存到属性,即使客户端跳转了也还是有效
(3)Session内置对象表示的是一个用户,只要用户不关闭浏览器,那么Session一直存在(默认时间是30分钟,可以在Tomcat安装路径下,web.xml文件中搜索session可设置超时时间)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String pathInfo = req.getPathInfo();
if(pathInfo.equals("/login")) {
this.login(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
public void login(HttpServletRequest req,HttpServletResponse resp) throws IOException, ServletException {
String name = req.getParameter("username");
String pwd = req.getParameter("pwd");
//为什么要下面的req.setAttribute();
// req.setAttribute("name", "smith");
if(name.equals("smith")&&pwd.equals("1234")) {
//登录成功应该跳转到欢迎页或者首页(使用客户端重定向)
resp.sendRedirect("/ServletProject/index.jsp");
} else {
//用户名密码错误则重新返回登录页面再次登录(使用服务器端转发)
req.setAttribute("msg", "用户名密码不正确!");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
2.判断是否登录
(1)在进行一些数据操作比如说removeById()删除操作时,在操作之前要先判断是否登录,如果没有登录,需要跳转到登录页面登录之后才能删除数据
(2)登录时已经将登录的姓名保存到了Session内置对象的name属性了,要判断是否登录,只需要判断Session对象的name属性是否为空,如果不为空,则说明已经登录了
(3)注销直接req.getSession().invalidate()就可以删除Session对象了为什么要删除Session对象?删除了什么数据会发生改变?
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String pathInfo = req.getPathInfo();
if(pathInfo.equals("/login")) {
this.login(req, resp);
} else if(pathInfo.equals("/logout")) {
this.logout(req, resp);
} else if(pathInfo.equals("/remove")) {
this.removeById(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
public void login(HttpServletRequest req,HttpServletResponse resp) throws IOException, ServletException {
String name = req.getParameter("username");
String pwd = req.getParameter("pwd");
req.getSession().setAttribute("name", name);
if(name.equals("smith")&&pwd.equals("1234")) {
//登录成功应该跳转到欢迎页或者首页(使用客户端重定向)
resp.sendRedirect("/ServletProject/index.jsp");
} else {
//用户名密码错误则重新返回登录页面再次登录(使用服务器端转发)
req.setAttribute("msg", "用户名密码不正确!");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
public void logout(HttpServletRequest req,HttpServletResponse resp) {
req.getSession().invalidate();
System.out.println("注销成功");
}
public void removeById(HttpServletRequest req,HttpServletResponse resp) throws ServletException, IOException {
//需要先判断用户是否登录,如果登录了直接操作
if (req.getSession().getAttribute("name")!=null) {
System.out.println("调用业务层代码删除数据");
} else {
System.out.println("请登录再进行操作!");
//跳转到登陆页面(服务器端跳转)
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
(四)Session工作机制
1.当第一次调用getSession()就创建了Session对象,同时会为该对象分配一个id, 这个id将会保存在Session对象中,并且会再复制一个副本以cookie的方式保存在浏览器
2.以后再次调用getSession()方法的时候,会将客户端传递的cookie进行遍历判断,如果有JSESSIONID并且能和服务器端的id匹配则不再生成新的Session内置对象,而是直接返回该对象
3.如果直接访一个Servlet,在Servlet中不调用getSession()方法是不会生成Session内置对象的
4.还有一种情况会产生Session内置对象,访问jsp页面的时候会产生该对象。之所以访问JSP页面能创建Session对象,是因为JSP其实也是一个特殊的Servlet,而且这个特殊的Servlet默认调用了getSession方法
5.当你访问一个JSP页面的时候会将JSP转换成一个*.java源码文件,之后再将该类转换为*.class文件,其实转换后的*.class文件就是一个特殊的Servlet,而且在该Servlet中调用了getSession方法
获取Session对象和Cookie值都是用的Request对象来获取
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//调用getSession方法 创建Session对象,并为该对象分配一个ID
req.getSession();
//获取浏览器cookie
Cookie[] cs = req.getCookies();
if(cs!=null) {
for (Cookie c : cs) {
//打印cookie的名称和JSESSIONID
System.out.println(c.getName()+":"+c.getValue());
}
}
}
四、Application内置对象
(一)为什么要有Application对象
1.Request对象保存的属性只是在当次请求有效,经过客户端跳转之后就无效了,保存在Session内置对象中的属性只是当前用户有效,关闭当前浏览器就失效
2.如果要让关闭浏览器后属性还有效则该属性应该保存在一个更大内置对象中,该对象就是Application内置对象,这是一个表示服务器范围的内置对象
3.该内置对象是多个用户共享的内置对象,比如说监听当前上线用户的人数就需要使用到该对象
4.application对象的类型是javax.servlet.ServletContext
(二)特点
1.保存在application对象中的属性,关闭了浏览器还依然有效,使用其他浏览器打开也能访问到保存在application内置对象中的属性
2.application内置对象除了能统计在线人数之外还有一个主要的功能就是取得项目的真实路径,方便文件的上传
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//取得application内置对象
ServletContext context = req.getServletContext();
//取得项目的部署路径(真实路径)
String realPath = context.getRealPath("/");
System.out.println(realPath);
//在该内置对象中保存属性
context.setAttribute("count", "在线人数为10");
//客户端跳转
resp.sendRedirect("/ServletProject/index.jsp");
}
//index.jsp
<body>
<h1>${count}</h1>
</body>
运行结果,真实路径
五、Request状态监听器
(一)监听器
1.监听器就是对内置对象的状态或者属性进行监听并且做出反应的特殊的Servlet,暂时也需要在web.xml文件中队监听器进行相关的配置
2.状态监听器:内置对象的有两种状态变化(产生与销毁),当产生内置对象和销毁内置对象的时候进行监听
3.属性监听器:当内置对象中增加、删除、修改一个属性的时候进行监听
(二)监听Request状态(产生与销毁)
1.要实现对Request状态的监听,需要定义一个监听器类并且实现一个接口(javax.servlet.ServletRequestListener)
2.同时需要在web.xml文件中增加配置,需要对实现配置"包名+监听器类名(不需要.java)"
3.监听Request状态DEMO
//Request监听器类,实现了ServletRequestListener接口
public class RequestListener implements ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("创建了一个Request内置对象");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("销毁了一个Request内置对象");
}
}
//EmpServlet类,继承了HttpServlet类,即Servlet容器
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("调用了Request监听器的方法");
}
//web.xml,配置监听器类
<listener>
<listener-class>com.sun.listener.RequestListener</listener-class>
</listener>
运行结果:浏览器中输入localhost/ServletProject/emp/sefwe后运行,即实现了对Request对象状态监听的测试。该监听结果证明了当用户发送请求的时候,Servlet容器会自动创建一个Request对象来处理来自客户端的请求,请求完毕之后(容器对客户端做出了响应)则Request对象立即销毁
4.使用Request监听器观察客户端转发和服务器端转发
(1)Request监听器——服务器端转发
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//服务器端转发
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
运行结果:使用服务器端转发,即使转发到login.jsp,总共也只创建了一个Request内置对象,所以本质就发送了一个请求
(2)Request监听器——客户端转发
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//客户端转发
resp.sendRedirect("/ServletProject/login.jsp");
}
运行结果:使用客户端转发到login.jsp页面,总共创建了2个Request内置对象,意味着发送了两次请求
六、Session状态和属性监听器
(一)监听Session状态
1.要实现对Session状态的监听,需定义一个监听器类并且实现一个接口(javax.servlet.http.HttpSessionListener)
2.web.xml对重新配置,“包名+监听器类名(不需要.java)”
3.监听Session状态DEMO
(1)Session监听器类,实现了HttpSessionListener接口
public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("创建了session:"+se.getSession());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("销毁了session:"+se.getSession());
}
}
//web.xml监听器配置
<listener>
<listener-class>com.sun.listener.SessionListener</listener-class>
</listener>
(2)EmpServlet类:下面的DEMO没有创建Session对象,原因是只有当第一次调用getSession()时创建Session对象,或者是访问jsp页面时(默认调用getSession()方法)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("访问了Servlet方法");
}
运行结果:只调用了doGet方法,未实现对Session状态的监听,意味着没有创建Session对象
(3)创建和销毁Session对象
创建Session对象:运行结果显示已创建Session对象,但是Session对象没有立即销毁,因为Session对象表示的是一个用户。只要用户不关闭浏览器那么Session一直存在(默认时间是30分钟,可设置成其他)
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//创建Session对象
req.getSession();
}
运行结果:创建了Session对象,未调用销毁方法,因此未销毁
销毁Session对象:成功销毁Session对象。运行结果还证明:Session对象代表的是一个用户,只要不关闭浏览器,Session对象会一直存在(默认30分钟),只有当第一次调用getSession()方法才会创建Session对象,在同一个浏览器(创建Session对象未关闭过,30分钟内),再调用几次getSession()方法都刽生成新的Session对象,而是根据和服务器端id匹配的JSESSIONID直接返回第一次调用创建的对象
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//创建Session对象
req.getSession();
//销毁Session对象
if(req.getPathInfo().equals("/logout")) {
this.logout(req, resp);
}
}
public void logout(HttpServletRequest req,HttpServletResponse resp) {
req.getSession().invalidate();
System.out.println("注销成功");
}
运行结果:销毁了Session对象,即使doGet方法又再次调用了创建Session对象的方法,但是并没有再次创建Session对象
(二)监听Session属性
1.要实现对Session属性的监听,需定义一个监听类并实现接口(javax.servlet.http.HttpSessionAttributeListener)
2.别忘了在web.xml重新配置监听器,并且重启动服务
3.监听Session属性DEMO
//Session属性监听器类,实现了HttpSessionAttributeListener接口
public class SessionAttributeListener implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
System.out.println("session新增一个属性"+se.getName()+":"+se.getValue());
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
System.out.println("session删除一个属性"+se.getName()+":"+se.getValue());
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
System.out.println("session替换一个属性"+se.getName()+":"+se.getValue());
}
}
//EmpServlet类,继承了HttpServlet类
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//创建Session对象
HttpSession se = req.getSession();
//设置Session属性
se.setAttribute("name", "smith");
se.setAttribute("name", "tom");
se.removeAttribute("name");
}
运行结果:Session实现了新增、替换和删除一个属性
七、Application状态和属性监听器
(一)监听Application状态
1.要实现对Application状态的监听,同样需要定义一个监听器类并且实现接口(ServletContextListener)
2.别忘了对web.xml进行配置
3.监听Application状态DEMO
当服务器启动或者重新加载的时候会初始化对象,服务器关闭时容器销毁Application对象
//Application状态监听器,实现了ServletContextListener接口
public class ApplicationListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent arg0) {
System.out.println("销毁了一个application对象");
}
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("创建了一个application对象");
}
}
运行结果:服务器启动或者重新加载的时候会初始化对象,服务器停止时销毁对象
(二)监听Application属性
1.定义一个监听类并实现ServletContextAttributeListener接口
2.别忘了web.xml进行相应的配置
3.监听Application属性DEMO
可以结合Session内置对象的监听实现在线人数的统计等等功能
//监听器类,实现了ServletContextAttributeListener接口
public class ApplicationAttributeListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent event) {
System.out.println("application对象新增了一个属性"+event.getName()+":"+event.getValue());
}
@Override
public void attributeRemoved(ServletContextAttributeEvent event) {
System.out.println("application对象移除了一个属性"+event.getName()+":"+event.getValue());
}
@Override
public void attributeReplaced(ServletContextAttributeEvent event) {
System.out.println("application对象替换了一个属性"+event.getName()+":"+event.getValue());
}
}
//EmpServlet,继承了HttpServlet类
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getServletContext().setAttribute("name", "smith");
req.getServletContext().setAttribute("name", "tom");
req.getServletContext().removeAttribute("name");
}
运行结果
八、Servlet的单例模式(???)
(一)多次使用同样的路径发送请求
1.以下案例测试时,使用localhost/ServletProject/emp/*(*内容都是随便输入的)这个网址,在一个页面刷新多次,一个浏览器多个页面,甚至多个浏览器中都进行了测试,测试的结果如下图
2.因为每次发送请求的时候容器都会去根据你的路径,查找对应的Servlet对请求进行处理。每次请求的this路径的结果都是一致的,这个this路径的打印结果就是Servlet的地址信息,都相同,意味着多次使用同样的路径发送请求,每次访问的Servlet都是同一个。也就是说Servlet在容器中是单例模式存在的,只会创建一个
3.而每次发送请求打印的线程名称都不一致,代表了每此请求的处理线程都是不一样的,如果是多个用户发送请求,那就多条线程在处理,会存在线程安全的问题
public class EmpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(this);//打印Servlet对象地址
System.out.println(Thread.currentThread().getName()+"正在访问");//打印当前线程的名称
}
}
运行结果:Servlet在容器中时单例模式存在的,只会创建一个。另外,每次请求的处理线程都是新的线程
(二)多个用户访问同一个Servlet存在线程安全问题
1.每次用户发送了请求到容器,容器会从线程池中分配一个线程给这个请求,请求完毕之后会将线程再次回收到池中,如果是多个用户发送请求访问Servlet,这就涉及到了多个线程同时访问一个Servlet,这样就会存在安全风险,如果避免这样的风险呢?
2.第一种方式:使用Servlet的单线程创建模式
(实现SingleThreadModel)这个方法已过时,建议弃用:使用线程休眠的方法,一次只能有一个线程访问同一个Servlet
public class EmpServlet extends HttpServlet implements SingleThreadModel{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//新的休眠方法,单位是秒
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程正在使用Servlet");
}
}
运行结果:每个线程按照设定的休眠时间,一次只允许一个线程(用户)访问同一个Servlet,其他的要排队
3.方式二:在方法中使用同步代码块
使用了同步代码块后,同一时间只能一个线程访问同步代码块的内容
public class EmpServlet extends HttpServlet implements SingleThreadModel{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"线程正在使用Servlet");
}
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
与方式一一样:每次只能一个线程访问Servlet,其他线程需要排队等待
4.方式三:避开在Servlet中定义实例变量(最可取)?
避开在Servlet中定义实例变量,如果需要使用到变量就在方法中定义为局部变量,此时就不存在数据共享的问题。这种方法是最可取的,因为其他两种方法都会导致访问速度变慢,因为要线程同步(排队等待)
九、实现在线人数的监听DEMO
(一)log.jsp表单
<body>
<h1>在线人数为:${count==null?0:count}</h1>
<p>${users==null?[]:users}</p>
<form action="emp/login" method="get">
<fieldset>
<legend>请登录</legend>
用户名:<input type="text" name="username">
<input type="submit" value="登录">
</fieldset>
</form>
<a href="emp/logout">注销</a>
</body>
(二)Servlet小程序
public class EmpServlet extends HttpServlet{
//创建Set对象来保存登录的用户名
Set<String> users = new HashSet<String>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String path = req.getPathInfo();
if(path.equals("/login")) {
this.login(req, resp);
} else if (path.equals("/logout")){
this.logout(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doGet(req, resp);
}
public void login(HttpServletRequest req,HttpServletResponse resp){
try {
//设置编码为utf-8,以防出现字符乱码
req.setCharacterEncoding("utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//如果Session对象中没有username属性,或属性没有值,意味着该用户无登录未注销的情况
if(req.getSession().getAttribute("username")==null) {
//获取到表单登录请求中的名称为username的值
String username = req.getParameter("username");
//如果用户登录名输入不为空,才执行保存登录名的操作
if(!username.equals("")) {
//将获取到的用户名username保存到Session对象的属性中
req.getSession().setAttribute("username", username);
//并将获取到的用户名保存在set集合里面
users.add(username);
//将保存用户名的set集合保存到Application对象的users属性中
req.getServletContext().setAttribute("users", users);
//将保存用户名的set集合的长度保存到Application对象的count属性中
req.getServletContext().setAttribute("count", users.size());
try {
//登录并且保存完全后,客户端跳转到log.jsp页面
resp.sendRedirect("/ServletProject/log.jsp");
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
//如果该用户Session的username属性已经已经有值,即已经登录还没有注销Session,那进行客户端跳转到log.jsp,除非注销否则无法二次登录
try {
resp.sendRedirect("/ServletProject/log.jsp");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void logout(HttpServletRequest req,HttpServletResponse resp) {
//若用户选择注销操作,则将该用户的姓名从set集合中去除,用户姓名可以根据Session对象的username属性来获取
users.remove(req.getSession().getAttribute("username"));
//用户选择注销,需要将该用户的Session信息注销
req.getSession().invalidate();
//更新Application对象的users属性和count属性
req.getServletContext().setAttribute("users", users);
req.getServletContext().setAttribute("count", users.size());
//注销后,用客户端跳转登录页面
try {
resp.sendRedirect("/ServletProject/log.jsp");
} catch (IOException e) {
e.printStackTrace();
}
}
}