Tomcat
Tomcat是一个符合JavaEE Web标准的最小Web容器,所有的JSP程序一定要有
Web容器
的支持才能运行,而且再给定的Web容器里面都会支持事务处理操作。
Tomcat简单地说就是一个运行Java的网络服务器,
底层是Socket的一个程序
,它也是JSP和Servlet的一个容器。
Servlet
Servlet是Server与Applet的缩写,是服务端小程序的意思。使用Java语言编写的服务端程序,可以生成动态的Web页面,Servlet主要运行在服务器端,并由服务器调用执行,是一种按照Servlet标准来开发的类。是Sun公司提供的一门用于开发动态Web资源的技术。(要实现Web开发,需要实现Servlet标准。)
Servlet本质上也是Java类,但要遵循Servlet的规范进行编写。没有main()方法,它的创建、使用、销毁都由Servlet容器进行管理(如Tomcat)。(写自己的类,不用main()方法,当被浏览器访问时会被自动调用。)
Servlet与Http协议是紧密联系的,它可以处理Http协议相关的所有内容。
提供了Servlet功能的服务器叫做Servlet容器,常见容器:Tomcat、Jetty、WebLogic Server、WebSphere、JBoss等等。
Servlet的实现
-
新建包,新建class类
继承HttpServlet类
,实现Servlet规范。 -
重写
HttpServlet类中的
service()方法
,service()方法在service类被访问时自动调用。作用:接受请求,响应结果。 -
在Servlet类上添加
注解
@WebServlet,设置Servlet类的访问路径。资源的对外访问路径:浏览器通过访问该路径从而访问到Servlet类。
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 java.io.IOException;
@WebServlet("/ser01")
//@WebServlet("/ser01") 同 @WebServlet(value = "/ser01") 同 @WebServlet(urlPatterns = "/ser01")
//@WebServlet(name = "abc",value = "/ser01")
//@WebServlet(name = "abc",urlPatterns = "/ser01")
//@WebServlet(value = {"/ser01","/ser02"})
public class s01 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("hello,servlet");
//响应数据
resp.getWriter().write("hello");
}
}
ps:
-
当Servlet类被访问时,service()方法由服务器自动调用。
-
设置Servlet类的访问路径时,在前面添加“/”。
不加会报错:java.lang.IllegalArgumentException:Invalid [ser01] in servlet mapping
-
在同一个项目下,Servlet类的对外访问路径值唯一。
-
访问Servlet时,路径格式如下:http://localhost:端口/项目路径/资源路径?参数名=参数值
项目路径:站点名,项目对外访问路径,在Tomcat中设置(Deployment属性中设置Application context)
资源路径:Servlet的对外访问路径,通过@WebServlet(“/…”)注解设置
Servlet工作流程
- 通过请求头获知浏览器访问的是哪个主机,再通过请求行获取访问的是哪一个Web应用,再通过请求行中的请求路径获知访问的是哪个资源,通过获取的资源路径在配置中匹配到真实的路径。
- 服务器会创建Servet对象(如果是第一次访问,创建Servlet实例,并调用init()方法进行初始化操作。),调用service()方法来处理请求和响应操作,调用完毕后返回服务器,由服务器将response缓冲区的数据取出,以http响应的格式发送给浏览器。
Servlet生命周期
Servlet没有main()方法,不能独立运行,它的运行完全由Servlet引擎来控制和调度。所谓生命周期,指的是Servlet容器何时创建Servlet实例,何时调度其方法进行请求的处理,何时销毁其实例的整个过程。
-
实例初始化
当请求到达容器时,容器查找该Servlet对象是否存在,若不存在,则会创建实例并进行初始化。
-
就绪、调用、服务阶段
有请求到达容器,容器调用Servlet对象的service()方法,处理请求的方法在整个生命周期中可以被多次调用;HttpServlet的service()方法,会依据请求方式来调用doGet()或者doPost()方法。但是这两个do方法默认情况下会抛出异常,需要子类重写。
-
销毁
当容器关闭时(应用程序停止时),会将程序中的Servlet实例进行销毁。
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 java.io.IOException; /* Servlet生命周期 1.初始化 init() 系统方法,服务器自动调用,只会执行一次,当第一次访问Servlet时触发。 2.就绪/调用 service() 系统方法,服务器自动调用,可调用多次,当有请求访问Servlet时会被调用。 3.销毁 destroy() 系统方法,服务器自动调用,只会执行一次,当服务器关闭时触发。 */ @WebServlet("/ser03") public class s03 extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); //调用doPost,代码写在doPost里 } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { } @Override public void init() throws ServletException { System.out.println("init"); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("service"); } @Override public void destroy() { System.out.println("destroy"); } }
Tomcat与Servlet工作原理时序图
HttpServletRequest对象
HttpServletRequest对象:主要作用是用来接收客户端发送过来的请求信息,例如:请求的参数、发送的头信息等都属于客户端发来的信息,service()方法中形参接收的是HttpServletRequest接口的实例化对象,表示该对象主要应用在Http协议上,该对象是由Tomcat封装好传递过来。
HttpServletRequest是ServletRequest的子接口,ServletRequest对象只有一个子接口,就是HttpServletRequest。现在主要用的协议是Http协议,但以后可能出现更多新的协议。若以后想要支持新协议,只需要直接继承ServletRequest接口即可。
在HttpServletRequest接口中,定义的方法很多,但都是围绕接收客户端参数的。HttpServletRequest对象直接在service()方法中由容器传来,我们只需取出对象中的数据进行分析、处理。
常用方法获得客户端浏览器信息
方法 | 返回值 |
---|---|
getRequestURL() | 返回客户端发出请求时的完整URL |
getRequestURI() | 返回请求行中的资源名部分(项目名称开始) |
getQueryString() | 返回请求行中的参数部分 |
getRemoteAddr() | 返回发出请求的客户机的IP地址 |
getRemoteHost() | 返回发出请求的客户机的完整主机名 |
getRemotePort() | 返回客户机所使用的网络端口号 |
getLocalAddr() | 返回WEB服务器的IP地址 |
getLocalName() | 返回WEB服务器的主机名 |
getMethod() | 返回客户端请求方式 |
getProtocol() | 返回HTTP版本号 |
getContextPath() | 返回webapp名字 |
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 java.io.IOException;
@WebServlet("/ser04")
public class s04 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取客户端请求的完整URL(从http开始,到?前面结束)
String url = req.getRequestURL().toString();
System.out.println("客户端请求的完整URL:"+url);
//获取客户端请求的部分URL(从站点名开始,到?前面结束)
String uri = req.getRequestURI().toString();
System.out.println("客户端请求的部分URL:"+uri);
//获取请求行中的参数部分
String queryString = req.getQueryString();
System.out.println("请求行中的参数部分:"+queryString);
//获取客户端的请求方式
String method = req.getMethod();
System.out.println("请求端的请求方式:"+method);
//获取HTTP版本号
String protocol = req.getProtocol();
System.out.println("HTTP版本号:"+protocol);
//获取webapp名字
String webapp = req.getContextPath();
System.out.println("webapp名字:"+webapp);
}
}
获取请求参数
方法 | 返回值 |
---|---|
getParameter(“参数名”) | 返回指定名称的参数(返回字符串) |
getParameterValues(“参数名”) | 返回指定名称参数的所有值(返回数组) |
参数名:表单元素的
name属性值
、ajax的data属性中值的键、超链接参数的键
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 java.io.IOException;
@WebServlet("/ser04")
public class s04 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//http://localhost:8080/s01/ser04?uname=zs&upwd=123&hobby=sing&hobby=dance
//通过参数名获取参数值,返回字符串
String uname = req.getParameter("uname");
String upwd = req.getParameter("upwd");
System.out.println("姓名:"+ uname);
System.out.println("密码:"+ upwd);
//通过参数名获取所有的参数值(复选框),返回数组
String[] hobby = req.getParameterValues("hobby");
System.out.println(hobby.length);
}
}
请求乱码处理
request接收客户端的参数有默认的语言编码,在解析过程中默认使用的编码方式为ISO-8859-1(此编码不支持中文),所以解析时一定会出现乱码,需要设置request中的编码方式,告诉服务器以何种方式解析数据,或者在接收到乱码数据以后,再通过相应的编码方式还原。
方式一:
req.setCharacterEncoding(“UTF-8”);
这种方式
只针对POST有效
,必须在接收所有的数据之前设定。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action = "ser05" method = post>
<p>姓名:<input type="text" name="uname"></p>
<p><button>登录</button></p>
</form>
</body>
</html>
@WebServlet("/ser05")
public class s05 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String uname = req.getParameter("uname");
System.out.println(uname);
}
}
方式二:
new String(req.getParameter(name).getBytes(“ISO-8859-1”),“UTF-8”);
借助了String对象的方法,该种方式对任何请求都有效,是通用的。
Tomcat8 以后的GET方式请求不会出现乱码。
@WebServlet("/ser05")
public class s05 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uname = new String(req.getParameter("uname").
getBytes("ISO-8859-1"),"UTF-8");
System.out.println(uname);
}
}
请求转发
请求转发是一种
服务器的行为
,当客户端请求到达后,服务器进行转发,此时会将请求对象进行保存,地址栏中的
URL地址不会改变
,得到响应后,服务端再将响应发送给客户端,从始至终
只有一个请求发出
。request对象可以共享。
达到多个资源协同响应的效果的实现方式:
req.getRequestDispatcher(url).forward(req,resp);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action = "ser05" method = post>
<p>姓名:<input type="text" name="uname"></p>
<p><button>登录</button></p>
</form>
</body>
</html>
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 java.io.IOException;
@WebServlet("/ser05")
public class s05 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String uname = req.getParameter("uname");
System.out.println(uname);
req.getRequestDispatcher("denglu.jsp").forward(req,resp);
}
}
<html>
<head>
<title>欢迎登录</title>
</head>
<body>
欢迎登录
<%
//转发到的页面与请求转发的页面共享数据
String uname = request.getParameter("uname");
out.println(uname);
%>
</body>
</html>
request作用域
通过request对象可以在一个请求中传递数据,
只在一次请求中有效
,
只在服务器转发有效
。
设置域对象内容:req.setAttribute(String name,String value);
获取域对象内容:req.getAttribute(String name);
删除域对象内容:req.removeAttribute(String name);
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 java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/ser06")
public class s06 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = "zhangsan";
String pwd = "admin";
List<String> hobby = new ArrayList<>();
hobby.add("sing");
hobby.add("dance");
req.setAttribute("uname", name);
req.setAttribute("upwd", pwd);
req.setAttribute("uhobby", hobby);
req.getRequestDispatcher("chaxun.jsp").forward(req,resp);
}
}
<html>
<head>
<title>Title</title>
</head>
<body>
查询到信息:
<%
//获取作用域
String username = (String) request.getAttribute("uname");
String userpwd = (String) request.getAttribute("upwd");
List<String> userhobby = (List<String>) request.getAttribute("uhobby");
out.print("姓名:"+username);
out.print("密码:"+userpwd);
out.print("爱好:"+userhobby.get(0)+","+userhobby.get(1));
%>
</body>
</html>
HttpServletResponse对象
Web服务器接收到客户端的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象和代表响应的response对象。
获取客户端数据,需要通过request对象;向客户端输出数据,需要通过response对象。
HttpServletResponse的主要功能用于服务器对客户端的请求进行响应,将Web服务器处理后的结果返回给客户端。service()方法中形参接收的是HttpServletResponse接口的实例化对象,这个对象封装了向客户端发送数据、发送响应头、发送响应状态码的方法。
响应数据
接收到客户端请求后,可以通过HttpServletResponse对象直接进行响应,响应时需要获取输出流。
有两种形式:
getWriter()
获取字符流(只能响应回字符)
PrintWriter writer = resp.getWriter();
writer.write("string");
writer.close();
getOutputStream()
获取字节流(能响应一切数据)
ServletOutputStream out = resp.getOutputStream();
out.write("string".getBytes("charsetName"));
out.close();
ps:
这两者不能同时使用
。报错:java.lang.IllegalStateException: getWriter() has already been called for this response
设置响应类型
resp.setHeader(“content-type”,“text/html”);
resp.setHeader("content-type","text/html");
PrintWriter writer = resp.getWriter();
writer.write("<h2>hello1</h2>");
writer.close();
响应乱码问题
在响应中,如果响应的内容中含有中文,则有可能出现乱码。这是因为服务器响应的数据也会经过网络传输,服务端有一种编码方式,客户端也有一种编码方式,当两端使用的编码方式不同时则出现乱码。
getWriter()的字符乱码
对于getWriter()获取到的字符流,响应中文必定出现乱码。因为服务端在进行编码时默认使用ISO-8859-1格式的编码,这种编码方式并不支持中文。要解决这种乱码只能在
服务端
告知服务器使用一种能够支持中文的编码格式,比如我们通常用的”UTF-8“。
resp.setCharacterEncoding("UTF-8");
要保证数据正确显示,还需要指定
客户端
的解码方式。
resp.setHeader("content-type","text/html;charset=UTF-8");
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 java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/ser07")
public class s07 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置服务端编码方式
resp.setCharacterEncoding("UTF-8");
//设置客户端编码方式
resp.setHeader("content-type","text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
writer.write("<h2>你好</h2>");
writer.close();
}
}
或者
同时设置客户端与服务端
的编码。
resp.setContentType("text/html;charset=UTF-8");
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//同时设置客户端与服务端的编码
resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
writer.write("<h2>你好</h2>");
writer.close();
}
两端指定编码后,乱码就解决了。要
保证发送端和接收端的编码一致
。
getOutputStream()字节乱码
对于getOutputStream()方式获取到的字节流,响应中文时,本身传输的就是字节,所以此时可能出现乱码,也可能正确显示。当服务端给的字节恰好和客户端使用的编码方式一致时则文本正确显示。否则出现乱码。无论如何都应该准确掌握服务器和客户端使用的是哪种编码格式,以确保数据正确显示。
指定客户端和服务端使用的编码方式一致
。
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置服务端编码方式
resp.setCharacterEncoding("UTF-8");
//设置客户端编码方式
resp.setHeader("content-type","text/html;charset=UTF-8");
ServletOutputStream out = resp.getOutputStream();
out.write("<h2>你好</h2>".getBytes("UTF-8"));
out.close();
}
}
或者
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//同时设置服务端与客户端
resp.setContentType("text/html;charset=UTF-8");
ServletOutputStream out = resp.getOutputStream();
out.write("<h2>你好</h2>".getBytes("UTF-8"));
out.close();
}
}
重定向
重定向是一种服务器指导客户端的行为,客户端发出第一个请求,被服务器接收处理后,服务器会进行响应,在响应的同时,服务器会给客户端一个新的地址(下次请求的地址
resp.sendRedirect(url)
;),当客户端接收到响应后,会立刻马上自动根据服务器给的新地址发起第二个请求,服务器接收请求并作出相应,重定向完成。
重定向当中
有两个请求
存在,并且属于
客户端行为
。
重定向当中
地址栏发生变化
。
重定向当中
request对象不共享
。
//重定向跳转到index.jsp
resp.sendRedirect("index.jsp");
第一次请求获得的响应码为302,并且含有一个location头信息,并且地址栏最终看到的地址是和第一次请求地址不同的。
请求转发和重定向的区别
请求转发 req.getRequestDispatcher(url).forward(req,resp) | 重定向 resp.sendRedirect() |
---|---|
一次请求,数据在request作用域中共享 | 两次请求,request作用域数据不共享 |
服务端行为 | 客户端行为 |
地址栏不发生变化 | 地址栏发生变化 |
绝对地址定位到站点后(只能跳转到项目下的资源) | 绝对地址可写到http://(可以跳转到项目外资源) |
Cookie对象
Cookie是
浏览器提供的一种技术
,通过服务器的程序能将一些只需保存在客户端,或者在客户端进行处理的数据,放在本地的计算机上,不需要通过网络传输,因而提高网页处理的效率,并且能够减少服务器的负载,但是由于Cookie是服务端保存在客户端的信息,所以其安全性也是很差的。例如常见的记住密码则可以通过Cookie来实现。
有一个专门操作Cookie的类 javax.servlet.http.cookie,随着服务端的响应发送给客户端,保存在浏览器,当下次再访问服务器时把Cookie再带回服务器。
Cookie的格式:键值对用“=“链接,多个键值对间通过”;“隔开。
Cookie的创建和发送
通过**new Cookie(“name”,“value”);**来创建一个Cookie对象,要想将Cookie随响应发送到客户端,需要先添加到response对象中,response.addCookie(cookie);此时该cookie对象随着响应发送到了客户端。在浏览器上可以看见。
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/cook01")
public class cookie01 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie("uname","admin");
resp.addCookie(cookie);
}
}
Cookie的获取
服务器只提供了一个**Cookie[] cookies = req.getCookies();**方法用来获取客户端回传的所有cookie组成的一个数组,如果需要获取单个cookie则需要通过遍历,getName()获取cookie的名称,getValue()获取cookie的值。
@WebServlet("/cook02")
public class cookie02 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取Cookie数组
Cookie[] cookies = req.getCookies();
//数组不为空则遍历数组
if(cookies != null && cookies.length > 0){
for(Cookie cookie : cookies){
System.out.println(cookie.getName());
System.out.println(cookie.getValue());
//获取指定cookie对象
if("uname".equals(cookie.getName())){
System.out.println(cookie.getValue());
}
}
}
}
}
Cookie设置到期时间
到期时间用来指定该cookie何时失效,默认为当前浏览器关闭即失效。可以手动设定cookie的有效时间(通过到期时间计算),通过setMaxAge(int time);方法设定cookie的最大有效时间,以秒为单位。
到期时间的取值:
-
负整数
若为负数,表示不存储该cookie。
cookie的maxAge属性的默认值是-1,表示只在浏览器内存中存活,一旦关闭浏览器窗口,那么cookie就会消失。
-
正整数
若为大于0的整数,表示存储的秒数。
表示cookie对象可存活指定的秒数。当生命大于0时,浏览器会把cookie保存到硬盘上,就算关闭浏览器,就算重启客户端电脑,cookie也会存活相应的时间。
-
0
若为0,表示删除cookie。
cookie生命等于0是一个特殊的值,它表示cookie被作废。如果原来浏览器已经保存了这个cookie,那么可以通过cookie的setMaxAge(0)来删除这个cookie,无论是在浏览器内存中,还是在客户端硬盘上都会删除这个cookie。
@WebServlet("/cook03")
public class cookie03 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie("uname","admin");
cookie.setMaxAge(-1); //负整数,浏览器关闭失效,默认
resp.addCookie(cookie);
Cookie cookie2 = new Cookie("uname2","admin2");
cookie2.setMaxAge(5); //正整数,存活5秒
resp.addCookie(cookie2);
Cookie cookie3 = new Cookie("uname3","admin3");
cookie3.setMaxAge(0); //0,删除cookie
resp.addCookie(cookie3);
}
}
cookie的name不能重复,如果重复会覆盖之前的cookie。所以通过cookie的setMaxAge(0)来删除这个cookie。
Cookie cookie4 = new Cookie("uname",null);
cookie4.setMaxAge(0); //0,删除cookie
resp.addCookie(cookie4);
Cookie的注意点
-
Cookie保存在当前浏览器中
在一般的站点中常常有记住用户名的操作,该操作只是将信息保存在本机上,换电脑以后这些信息就无效了。而且Cookie还不能跨浏览器。
-
Cookie存中文问题
Cookie中不能出现中文。如果有中文则通过**URL.Encoder.encode()
来进行编码,获取时通过
URLDecoder.decode()**来进行编码。String name = "姓名"; String value = "张三"; //通过URL.Encoder.encode()来进行编码 name = URLEncoder.encode(name); value = URLEncoder.encode(value); //创建Cookie对象 Cookie cookie = new Cookie(name,value); //发送Cookie对象 resp.addCookie(cookie);
//获取时通过URLDecoder.decode()来进行编码 URLDecoder.decode(cookie.getName()); URLDecoder.decode(cookie.getValue());
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String name = "姓名"; String value = "张三"; //通过URL.Encoder.encode()来进行编码 name = URLEncoder.encode(name); value = URLEncoder.encode(value); //创建Cookie对象 Cookie cookie = new Cookie(name,value); //发送Cookie对象 resp.addCookie(cookie); //获取时通过URLDecoder.decode()来进行编码 System.out.println(URLDecoder.decode(cookie.getName())); System.out.println(URLDecoder.decode(cookie.getValue())); } }
-
同名Cookie问题
在同域名(domain)同路径(path)下,如果服务器端发送重复的Cookie那么会覆盖原有的Cookie。
-
浏览器存放Cookie的数量
不同的浏览器对Cookie也有限定,Cookie的存储是有上限的。Cookie是存储在客户端(浏览器)的,而且一般是由服务器端创建和设定。后期结合Session来实现会话跟踪。
Cookie的路径
Cookie的
setPath();
设置cookie的路径,这个路径直接
决定服务器的请求是否会从浏览器中加载某些cookie
。
setPath();默认是当前项目的站点名。
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//当前项目站点名为 /s01
//当前服务器域名为 Localhost:8080
//1.当前服务器下任何项目的任意资源都可获取Cookie对象
Cookie cookie01 = new Cookie("a","A");
cookie01.setPath("/");
resp.addCookie(cookie01);
//2.当前项目下的资源可获取Cookie对象(默认不设置Cookie的path)
Cookie cookie02 = new Cookie("b","B");
cookie02.setPath("/s01");
resp.addCookie(cookie02);
//3.指定项目下的资源可获取Cookie对象
Cookie cookie03 = new Cookie("c","C");
cookie03.setPath("/s05");
resp.addCookie(cookie03);
//4.指定目录下的资源可获取Cookie对象
Cookie cookie04 = new Cookie("d","D");
cookie04.setPath("/s01/index.jsp");
resp.addCookie(cookie04);
}
}
总结:当访问路径包含cookie的path属性值时,能获取到该cookie。
HttpSession对象
HttpSession对象是javax.servlet.http.HttpSession的实例
,该接口并不像HttpServletRequest或HttpServletResponse存在一个父接口,该接口只是一个纯粹的接口,
没有父接口
。因为session本身就
属于HTTP协议的范畴
。
对于服务器而言,每一个连接到它的客户端都是一个session,
servlet容器使用此接口创建HTTP客户端和HTTP服务器之间的会话
。会话将保留指定的时间段,跨多个连接或来自用户的页面请求。一个会话通常对应于一个用户,该用户可能多次访问一个站点。可以通过该接口查看和操作有关某个会话的信息,比如会话标识符、创建时间和最后一次访问时间。在整个session中,最重要的就是属性的操作。
session无论客户端还是服务端都可以感知到,若重新打开一个新的浏览器,则无法取得之前设置的session,因为
session的底层依赖于cookie
,每一个
session只保存在当前的浏览器中
,并在相关的页面取得。
Session的作用就是为了标识一次会话,或者说确认一个用户,并且在一次会话(一个用户的多次请求)期间共享数据。我们可以通过req.getSession();方法,来获取当前会话的session对象。
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;
import java.io.IOException;
@WebServlet("/se01")
public class session01 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//Session表示一次会话,会话中可以有多次请求,会话保留指定时间段。
//无论是客户端还是服务端,都能感知到session对象,服务器和浏览器都会影响session对象。
//创建或获取session对象:当session存在时,表示获取;当不存在时,表示创建。
HttpSession session = req.getSession();
//获取会话标识符
System.out.println("会话标识符:"+session.getId());
//是否是新的session对象(false,因为打开过index.jsp)
System.out.println("是否是新的会话:"+session.isNew());
//会话开始时间
System.out.println("会话开始时间"+session.getCreationTime());
//最后一次访问时间
System.out.println("最后一次访问时间"+session.getLastAccessedTime());
}
}
标识符JESSIONID
Session为了标识一次会话,此次会话应有一个唯一的标志,这个标志就是sessionId。
每当一次请求到达服务器,如果开启了会话(访问了session),服务器第一步会查看是否从客户端回传一个名为JSESSIONID的cookie,如果没有则认为这是一次新的会话,会创建一个新的session对象,并用唯一的sessionId为此次会话做一个标志。如果有JSESSIONID这个cookie回传,服务器则会根据JSESSIONID这个值去查看是否含有id为JSESSION值的session对象,如果没有则认为这是一个新的会话,重新创建一个新的session对象,并标志此次会话;如果找到了相应的session对象,则认为是之前标志过的一次会话,返回该session对象,数据达到共享。
这里提到一个叫做JSESSIONID的cookie,这是一个比较特殊的cookie,当用户请求服务器时,如果访问了session,则服务器会创建一个名为JSESSIONID,值为获取到的session(无论是获取到的还是新创建的)的sessionId的cookie对象,并添加到response对象中,响应给客户端,有效时间为关闭浏览器之前。
所以Session的底层依赖Cookie来实现。
Session域对象
Session用来表示一次会话,在一次会话中数据是可以共享的,这时session作为域对象存在,可以通过**setAttribute(name,value)
方法向域对象中添加数据,通过
getAttribute(name)
从域对象中获取数据,通过
removeAttribute(name)**从域对象中移除数据。
// 获取Seesion对象
HttpSession session = req.session();
//设置session对象
session.setAttribute("uname","admin");
//获取指定名称的session域对象
String uname = (String) req.getAttribute("uname");
//移除指定名称的session域对象
session.removeAttribute("uname";)
request作用域和session作用域的区别
:
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;
import java.io.IOException;
@WebServlet("/se02")
public class session02 extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
//request作用域
req.setAttribute("reqMsg","request作用域");
//session作用域
session.setAttribute("seMsg","session作用域");
//请求转发
req.getRequestDispatcher("test01.jsp").forward(req,resp);
//重定向
resp.sendRedirect("test01.jsp");
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
request作用域:<%=request.getAttribute("reqMsg")%>
session作用域:<%=session.getAttribute("seMsg")%>
</body>
</html>
访问http://localhost:8080/s01/se02先保存数据,再打开一个新页面,直接访问http://localhost:8080/s01/test01.jsp,reqMsg无法获取,seMsg可以获取。
访问http://localhost:8080/s01/se02时,如果是请求转发,reMsg和seMsg都可以获取到。如果是重定向,reqMsg无法获取,seMsg可以获取。重定向是两次请求,两个request对象,第一个有数据的request已经失效了,显示的是第二个request对象。
request作用域:只在一次请求中有效。只在请求转发跳转中有效。
session作用域:在一次会话中有效,会话中可以包含多次请求。在请求转发和重定向中均有效。
session作用域的范围比request作用域大。
Session对象的销毁
默认时间到期
当客户端第一次请求servlet并且操作session时,session对象生成。Tomcat中session默认的存活时间为30min,在不操作界面的时间里,一旦有操作,session会重新计时。
可以在Tomcat中的conf目录下的web.xml文件中进行修改。
<!-- session 默认的最大不活动时间。单位:分钟。 -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
自己设定到期时间
可以在程序中自己设定session的生命周期。通过
session.setMaxInactiveInterval(int)
来设定session的最大不活动时间,单位为秒。
//获取session对象
HttpSession session = req.getSession();
session.setMaxInactiveInterval(15); //15秒
也可以通过**getMaxInactiveInterval()**方法来查看当前session对象的最大不活动时间。
//获取session的最大不活动时间
int time = session.getMaxInactiveInterval();
手动销毁
//手动销毁session
session.invalidate();
关闭浏览器和关闭服务器都会销毁session对象。
ServletContext对象
每一个Web应用都
有且仅有一个
ServletContext对象,又称Application对象,从名称中可知,该对象是与应用程序相关的。在Web容器启动的时候,会为每一个Web应用程序创建一个对应的ServletContext对象。
ServletContext对象有两大作用。第一,作为域对象用来共享数据,此时数据在整个应用程序中共享;第二,该对象中保存了当前应用程序的相关信息,例如可以通过getServletInfo()方法获取当前服务器信息,getRealPath(String path)获取资源的真实路径等。
ServletContext对象的获取
-
通过request对象获取:
ServletContext servletContext = req.getServletContext();
-
通过session对象获取
ServletContext servletContext = req.getSession().getServletContext();
-
通过servletConfig对象获取,在Servlet标准中提供了ServletConfig方法
ServletConfig servletConfig = getServletConfig(); ServletContext servletContext = servletConfig.getServletContext();
-
直接获取,Servlet类中提供了直接获取ServletContext对象的方法
ServletContext servletContext = getServletContext();
常用方法:
//获取项目存放的真实路径
String realPath = req.getServletContext().getRealPath("/");
//获取当前服务器的版本信息
String serverInfo = req.getServletContext().getServerInfo();
ServletContext域对象
在整个应用程序中有效,服务器关闭失效。
文件上传和下载
文件上传
文件上传涉及到前台页面的编写和后台服务器端代码的编写,前台发送文件,后台接收并保存文件。
前台页面
在做文件上传的时候,会有一个上传文件的界面,首先需要一个表单,并且表单的请求方式为POST;其次form表单的enctype必须设为”multipart/form-data”,即
enctype=“multipart/form-data”
,意思是设置表单的类型为文件上传表单。默认情况下这个表单类型是”application/x-www-form-urlencoded”,不能用于文件上传,只有使用了multipart/form-data才能完整地传递文件数据。
</body>
<!--
文件上传表单(二进制表单):
表单类型:enctype="application/x-www-form-urlencoded" (默认)
二进制表单:enctype="multipart/form-data" (需要文件上传时设置)
提交方式:
文件上传需要设置method="post"
表单元素:
文件域<input type="file" name="myfile"/>
需要设置表单元素的name属性
提交路径:
处理上传的文件的servlet的对外访问路径
-->
<form action="uploadServlet" enctype="multipart/form-data" method="post">
<p>姓名:<input type="text" name="uname"></p>
<p>头像:<input type="file" name="myfile"></p>
<p><button type="submit">提交</button></p>
</form>
</body>
后台实现
使用注解**@MultipartConfig**将一个Servlet标识为支持文件上传。Servlet将multipart/form-data的POST请求封装成Part,通过Part对上传的文件进行操作。
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
@WebServlet("/upload")
@MultipartConfig //文件上传必须设置该注解,否则所有参数都无法获取
public class uploadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//设置请求的编码格式
req.setCharacterEncoding("UTF-8");
//获取普通文本框
String uname = req.getParameter("uname");
System.out.println("用户名"+uname);
//获取Part对象
Part part = req.getPart("myfile"); //表单元素文件域的name属性值
//得到上传文件的文件名
String fileName = part.getSubmittedFileName();
//设置上传文件要存放的路径
String uploadPath = req.getServletContext().getRealPath("/upload/");
//上传文件
part.write(uploadPath + fileName);
}
}
文件下载
文件下载,即将服务器上的资源下载(拷贝)到本地,可以通过两种方式下载,第一种是通过超链接本身的特性来下载,第二种是通过代码下载。
超链接下载
在HTML或JSP页面中使用a标签时,原意是希望能够进行跳转,但当超链接遇到浏览器不识别的资源时会自动下载;当遇见浏览器能直接显示的资源,浏览器会默认显示出来,比如txt、png、jpg等。
也可以通过
download属性
规定浏览器进行下载。但有些浏览器不支持。
默认下载
<!-- 当超链接遇到浏览器不识别的资源时,会自动下载-->
<a href = "test.zip">超链接下载</a>
指定download下载
<!-- 当超链接遇到浏览器识别的资源时,默认不会下载,通过download属性进行下载 -->
<a href = "test.txt" download>超链接下载</a>
download属性可以不写任何信息,会自动使用默认文件名,如果设置了download属性的值,则使用设置的值作为文件名。当用户打开浏览器点击链接的时候就会下载文件。
<body>
<!--超链接下载
a标签在遇到浏览器能识别的资源时,会直接打开资源;遇到浏览器不能识别的资源时会进行下载。
download属性
可以设置a标签的download属性,让超链接执行下载操作。
如果设置了download的属性值,则下载的文件名为对应的属性值;
如果未设置,则下载的文件名为文件原本名。
-->
<h3>超链接下载</h3>
<!-- 浏览器能够识别的资源 -->
<a href = "test.txt">TXT文本</a>
<a href = "Icon.jpg">JPG图片</a>
<!-- 浏览器不能够识别的资源 -->
<a href = "test.zip">压缩包</a>
<!-- download -->
<a href = "test.txt" download>TXT文本</a>
<a href = "Icon.jpg" download="test.jpg">JPG图片</a>
</body>
后台实现下载
-
需要通过response.setContentType方法设置Content-type头字段的值,为浏览器无法使用某种方式或激活某个程序来处理的MIME类型,例如”application/octet-stream”或”application/x-msdownload”等。
-
需要通过response.setHeader方法设置Content-Disposition头的值为”attachment;filename=文件名”。
-
读取下载文件,调用response.getOutputStream方法向客户端写入附件内容。
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 下载步骤
* 1.得到需要下载的文件名
* 2.得到下载的文件存放路径
* 3.通过路径与文件名得到file对象
* 4.判断file对象是否存在,且是否为标准文件
* 5.设置响应类型(浏览器无法使用某种方式激活某个程序来处理的MIME类)
* 6.设置响应头(设置下载文件名等)
* 7.得到下载的文件的输入流
* 8.得到字节输出流
* 9.循环输出
* 10.关闭流
*
*/
@WebServlet("/DownloadServlet")
public class DownloadServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//1.得到需要下载的文件名
String fileName = req.getParameter("fileName");
//2.得到下载的文件存放路径
String filePath = req.getServletContext().getRealPath("/upload/");
//3.通过路径与文件名得到file对象
File file = new File(filePath+fileName);
//4.判断file对象是否存在,且是否为标准文件
if(file.exists()&&file.isFile()){
//5.设置响应类型(浏览器无法使用某种方式激活某个程序来处理的MIME类)
resp.setContentType("application/x-msdownload");
//6.设置响应头(设置下载文件名等)
resp.setHeader("Content-Disposition","attachment;filename="+fileName);
//7.得到下载的文件的输入流
InputStream is = new FileInputStream(file);
//8.得到字节输出流
ServletOutputStream os = resp.getOutputStream();
//9.循环输出
int len = 0;
byte[] bytes = new byte[1024];
while((len = is.read(bytes)) != -1){
os.write(bytes,0,len);
}
//10.关闭流
os.close();
is.close();
}else{
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("需要下载的文件不存在");
resp.getWriter().close();
}
}
}