使用IDEA创建一个Maven项目
创建好的Maven如下
通过上图我们可以了解到一些目录结构,这是Maven项目的标准结构,其中
Maven项目创建完成之后,就i会自动生成一个pom.xml文件,我们需要在这个文件中引入Servlet API依赖的jar包
一个项目中可以有多个依赖,每个以来都是一个
Web项目对于目录结构还有自己的要求只有Maven的标准目录还是不够的,需要再创建以下目录进行配置
Archetype Created Web Application
以下编写一个让相应返回一个自定义的字符串代码:
HttpServlet 这个类来自于pom.xml中引入的Servlet API依赖的jar包
doGet是HttpServlet类中的方法,此处是在子类中重写了父类的doGet
通过doGet的源码我们可以大致的了解,它的作用就是根据收到的请求通过响应返回一个405或者400,那么我们可以重写这个方法,根据收到的请求执行自己的业务逻辑,把结果构造成响应对象
resp.getWriter()会获取到一个流对象,通过这个流对象就可以写入一些数据,写入的数据会被构造成一个HTTP相应的body部分,Tomcat会把整个响应转换成字符串,通过Socket写回给浏览器
上述注解表示Tomcat收到请求中,URL的Servlet Path路径为/test的请求才会调用TestServlet这个类的代码,注解中的字符串表示着URL的Servlet Path
到这里程序的编写已经完成!但是你可能会议或上述代码不是通过main方法作为入口,这是因为main方法包含在Tomcat中,我们写的程序并不能单独执行,而是需要Tomcat才能执行起来(Tomcat的伪代码我们会具体的分析这个问题)
在程序编写好之后,就可以使用Maven进行打包
- packaging标签中用于设置打包类型(如果不进行修改,默认的类型是jar包,jar包是普通Java程序打包的结果,里面包含一i写.class文件;而部署在Tomcat中的压缩包一般为war包,war包里面是Java Web程序,里面除了.class文件以外还包含HTML、CSS、JavaScript、图片等...)
- finaName标签中用于设置打包好后的名字(包名很重要,它对应着请求中的URL的Context Path)
接下来我们就可以进行程序部署
此时通过浏览器访问 http://127.0.0.1:8080/testServlet/test 就可以看到程序实现的结果了
注意:URL的路径分为两部分 Context Path 和 Servlet Path
为了简化上述操作流程,其实是有一些更加简单的方式:
- name:这一栏可以随意填写
- Tomcat Servlet:表示Tomcat所在的目录
- Deployment Directory:表示项目发布的目录
- Context Path:表示项目路径,默认值就是项目名称
- Servlet Port:表示服务端口
- Admin Port:表示管理端口
- VM options:表示JVM参数
在servlet代码中,我们并没有写main方法,那么对应的doGet方法是如何被调用的呢?响应又是如何返回给服务器的呢?
我们自己实现的Servlet实在Tomcat基础上运行的,下图显示了Web应用程序中的位置
当浏览器给服务器发送请求的时候,Tomcat作为HTTP服务器,就可以接收到这个请求,Tomcat的工作就是解析HTTP请求,并把请求交给Servlet的代码来进行进一步的处理,Servlet的代码根据请求计算生成响应对象,Tomcat再把这个响应对象构造成HTTP响应,返回给浏览器,并且Servlet的代码也经常会和数据库进行数据的传递。
下面是通过Tomcat的伪代码的形式来描述Tomcat 初始化和处理请求两部分核心逻辑
Tomcat初始化流程
class Tomcat {
// 用来存储所有的 Servlet 对象
private List instanceList = new ArrayList<>();
public void start() {
// 根据约定,读取 WEB-INF/web.xml 配置文件
// 并解析被 @WebServlet 注解修饰的类
// 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
Class[] allServletClasses = ...;
// 这里要做的的是实例化出所有的 Servlet 对象出来;
for (Class cls : allServletClasses) {
// 这里是利用 java 中的反射特性做的
// 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
// 方式全部在 WEB-INF/classes 文件夹下存放的,所以 tomcat 内部是
// 实现了一个自定义的类加载器(ClassLoader),用来负责这部分工作。
Servlet ins = cls.newInstance();
instanceList.add(ins);
}
// 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次
for (Servlet ins : instanceList) {
ins.init();
}
// 启动一个 HTTP 服务器,并用线程池的方式分别处理每一个 Request
ServerSocket serverSocket = new ServerSocket(8080);
// 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
ExecuteService pool = Executors.newFixedThreadPool(100);
while (true) {
Socket socket = ServerSocket.accept();
// 每个请求都是用一个线程独立支持,这里体现了 Servlet 是运行在多线程环境下的
pool.execute(new Runnable() {
doHttpRequest(socket);
});
}
// 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次
for (Servlet ins : instanceList) {
ins.destroy();
}
}
public static void main(String[] args) {
new Tomcat().start();
}
}
- Tomcat的代码内置了main方法,当我们启动Tomcat的时候,就是从Tomcat的main方法开始执行的
- 被@WebServlet注释修饰的类就会在Tomcat启动的时候就会被获取到,并且集中管理
- Tomcat通过反射这样的语法机制来创建被@WebServlet注释修饰的类的实例
- 这些实例被创建完之后,就会调用其中的init方法进行初始化
- 这些实例被销毁之前,就会调用其中的destory方法进行收尾工作
- Tomcat内部也是通过Socket API进行网络通信
- Tomcat为了能够同时处理多个HTTP请求,采取了多线程的方式实现,因此Servlet是运行在多线程环境下的
class Tomcat {
void doHttpRequest(Socket socket) {
// 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析和响应构建
HttpServletRequest req = HttpServletRequest.parse(socket);
HttpServletRequest resp = HttpServletRequest.build(socket);
// 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内容
// 直接使用 IO 进行内容输出
if (file.exists()) {
// 返回静态内容
return;
}
// 走到这里的逻辑都是动态内容了
// 找到要处理本次请求的 Servlet 对象
Servlet ins = findInstance(req.getURL());
// 调用 Servlet 对象的 service 方法
// 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
try {
ins.service(req, resp);
} catch (Exception e) {
// 返回 500 页面,表示服务器内部错误
}
}
}
- Tomcat从socket中读到的HTTP请求是一个字符串,然后Tomcat会按照HTTP协议的格式解析成一个HttpServletRequest对象
- Tomcat会根据URL中的Path判定这个请求是请求一个静态资源还是动态资源,如果是静态资源,直接找到对应的文件,把文件的容通过Socket返回;如果是动态资源,才会执行Servlet相关逻辑
- Tomcat会根据URL中的Context Path和Servlet Path确定要调用哪个Servlet实例的service方法
- 通过service方法,就会进一步调用我们重写的doGet或者doPost方法
class Servlet {
public void service(HttpServletRequest req, HttpServletResponse resp) {
String method = req.getMethod();
if (method.equals("GET")) {
doGet(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} else if (method.equals("PUT")) {
doPut(req, resp);
} else if (method.equals("DELETE")) {
doDelete(req, resp);
}
......
}
}
- Servlet的service方法内部会根据当前的请求方式,决定调用其中的某个do...方法
- 在调用do...方法的时候,就会触发多态机制,从而执行到我们自己写好的子类do...方法
对于Servlet主要介绍三个类,分别是HttpServlet、HttpServletRequest和HttpServletResponse
其中HttpSerletRequest和HttpServletResponse是Servlet规范中规定的两个接口,HttpServlet中并灭有实现这两个接口的成员变量,它们知识HttpServlet的service和do...等方法的参数,这两个接口类的实例化是在Servlet容器中实现的。
核心方法
方法名称 | 调用时机 |
init |
在HttpServlet实例化之后被调用一次 |
destory | 在HttpServlet实例不再使用的时候调用一次 |
service | 收到HTTP请求的时候调用 |
doGet | 收到GET请求的时候调用(由service方法调用) |
doPost | 收到POST请求的时候调用(由Service方法调用) |
doPut/doDelete/doOptions... | 收到其他请求的时候调用(由Service方法调用) |
Servlet的生命周期:Servlet的生命周期就是Servlet对象从创建到销毁的过程,下面就来介绍其生命周期的过程
注意:init和service能够保证在各自的合适时机被Tomcat调用,但是destory不一定,它是否能够被调用取决于Tomcat是如何结束的
处理GET请求示例:
@WebServlet("/get")
public class TestServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/plain");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write("响应了一个Get请求");
}
}
处理POST请求示例:
由于通过浏览器URL发送的请求是GET方法的请求,因此我们需要通过其他方式来发送一个POST请求用于处理。发送POST请求的方式有Ajax、form表单或者Socket API 构造,如果单纯用于测试就比较麻烦,所以我们可以用一个软件postman,是一个强大的API调试、Http请求的工具
@WebServlet("/post")
public class TestServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("post");
}
}
核心方法
方法 | 作用 |
String getProtocol() | 返回协议的名称和版本号 |
String getMethod() |
返回请求的HTTP方法名称 |
String getRequesURL() |
返回请求的URL,不带查询字符串 |
String getRequestURI() | 返回URL的一部分,不带协名,端口号,查询字符串 |
String getContextPath() | 返回指示请求URL中的Context Path 部分 |
String getServletPath() | 返回首行中的Servlet Path 部分 |
String getQueryString() | 返回首行后面的查询字符串 |
Enumeration getParameterNames() | 返回一个String 对象的枚举,包括在该请求中的参数名称 |
String getParameter(String name) | 以字符串形式返回请求参数的值,如果参数不存在则返回null |
String[] getParameterValues(String name) | 返回一个字符串对象的数组,包括所有给定的请求参数,如果参数不存在返回null |
Enumeration getHeaderNames() | 返回一个枚举,包括该请求中所有的头名 |
String getHeader(String name) | 以字符串形式返回指定的请求头的值 |
String getCharacterEncoding() | 返回请求正文中使用的字符编码的名称 |
String getContentType() | 返回请求正文的 MIME 类型,如果不知道类型则返回 null |
int getContentLength() | 以字节位单位返回请求正文的长度,并提供输入流,如果长度未知则返回-1 |
InputStream getInputStream() | 用于读取请求的正文内容,返回一个 InputStream 对象 |
@WebServlet("/showRequest")
public class TestServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//此处返回一个HTMl,在HTML中显示 HttpRequestServlet 类中的核心方法
//把这些API 的返回结果 通过 StringBuilder进行拼接
resp.setContentType("text/html;charset=utf-8");
StringBuilder html = new StringBuilder();
html.append(req.getMethod());
html.append(" ");
html.append(req.getRequestURL());
html.append("?");
html.append(req.getQueryString());
html.append(" ");
html.append(req.getProtocol());
html.append("");
Enumeration headerNames = req.getHeaderNames();
while(headerNames.hasMoreElements()){
String headName = headerNames.nextElement();
String header = req.getHeader(headName);
html.append(headName);
html.append(": ");
html.append(header);
html.append("");
}
html.append("");
resp.getWriter().write(html.toString());
}
}
如果body的内容格式是x-www-form-urlencoded(这是form表单提交的数据格式,此时body的格式就类似于 query string (是键值对的结构,键值对之间使用&分割,键与值之间使用=进行分割),使用getParameter(获取键值对)进行处理
- 此处是要获取body的数据,由于GET方法一般没有body,这里使用POST方法演示
- 约定body的数据格式是:x-www-form-urlencoded
- 约定body的数据内容是:username=123&password=111
@WebServlet("/postParameter")
public class TestServlet extends HttpServlet{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
String username = req.getParameter("username");
String password = req.getParameter("password");
resp.getWriter().write("username=" + username + "" +"passwd=" + password);
}
}
如果body的内容格式是json,首先将整个body都读取出来,再借助第三方库方法按照json的格式来进行解析,Java标准库没有内置对于json解析的方法
- 此处是要获取body的数据,由于GET方法一般没有body,这里使用POST方法进行演示
- 约定body的数据格式为:json
- 约定body的数据内容为:
{
username:123,
password:111
}
jacjkson中的核心类是ObjectMapper,通过这个类的readValue(Stringcontent,Class
valueType)方法,就可以将json字符串转化为一个类的对象(第一个参数是json字符串,第二个参数是类对象),ObjectMapper会遍历定义的类中的每一个成员的名称,去json字符串中的key中查找,如果找到了就将对应的值返回给该成员
/自定义的将json字符串转化的类
class UserInfo{
public String username;
public String password;
}
@WebServlet("/jsonParameter")
public class TestServlet extends HttpServlet{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
//将整个body中的数据读取出来
String body = readBody(req);
//按照 json、格式进行解析
ObjectMapper objectMapper = new ObjectMapper();
UserInfo userInfo = objectMapper.readValue(body,UserInfo.class);
resp.getWriter().write("username=" + userInfo.username + "" + "passwd=" + userInfo.password);
}
private String readBody(HttpServletRequest req) throws IOException {
int contextLength = req.getContentLength();
//准备一个字节数组,来存放body内容
byte[] buffer = new byte[contextLength];
//获取·到InputStream对象
InputStream inputStream = req.getInputStream();
inputStream.read(buffer);
//将存放在body内容的字节数组转换成字符串
return new String(buffer,"utf-8");
}
}
核心方法
方法 | 作用 |
void setStatus(int sc) | 为该响应设置状态码 |
void setHeader(String name,String value ) | 设置一个带有给定的名称和值的header,如果name已经存在,则会覆盖该值 |
void addHeader(String name,String value) | 添加一个带有给定的名称和值的header,如果name已经存在,不覆盖旧值,而是添加一个新的键值对 |
void setContentType(String type) | 设置被发送到客户端的响应的内容类型 |
void setCharacterEncoding(String charset) | 设置被发送到客户端的响应的字符编码,例如utf-8 |
void sendRedirect(String location) | 设置Location字段,实现重定向 |
PrintWriter getWriter() | 用于往body中写入文本格式的数据 |
OutputStream getOutputStream() | 用于往body中写入二进制格式数据 |
示例一:通过代码,构造出不同的响应状态码
@WebServlet("/status")
public class TestServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int status = 404;
resp.setStatus(status);
resp.getWriter().write("status"+status);
}
}
Fiddler抓包结果:
@WebServlet("/autoRefresh")
public class TestServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//给响应一个Refresh的header,每隔一秒刷新一次
resp.setHeader("Refresh","1");
//返回一个当前的时间,用来显示刷新效果
resp.getWriter().write("timestamp: "+System.currentTimeMillis());
}
}
方法一:在响应报头设置状态码和Location来实现重定向
@WebServlet("/redirect")
public class TestServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//将状态码设置为3XX
resp.setStatus(302);
//设置一个Location
resp.setHeader("Location","https://blog.csdn.net/loss_rose777?type=blog");
}
}
方法二:直接使用sendRedirect()方法来实现重定向
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect("https://blog.csdn.net/weixin_51367845?spm=1000.2115.3001.5343");
}
}
下面是表白墙的内容展示
下面是程序代码:
Document
表白墙
输入后点击提交,会将信息显示在表格中
谁:
对谁:
说:
1、创建一个Servlet项目
2、将之前写好的纯前端的表白前代码拷贝到webapp目录下
3、约定好前后端交互接口,该程序只需要约定两个接口
- 约定请求:方法时GET,请求路径是/message
- 约定响应:版本号为HTTP/1.1,状态码是200 OK,采用的是JSON格式
- JSON具体请求格式:
[ { from:"", to:"", message:"" } ]
- 约定请求:方法为POST,请求路径为/message
- 约定响应:版本号为HTTP/1.1,状态码为 200 OK,提交成功响应页面显示“提交成功”
4、创建一个MessageServlet类。@WebServlet注解为 /message,对应着约定的请求路径,通过上方的约定完成服务器代码
5、更改前端代码
后端代码实现:
import com.fasterxml.jackson.databind.ObjectMapper;
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;
//这个类表示一条消息的详细信息
class Message{
public String from;
public String to;
public String message;
}
@WebServlet("/message")
public class messageServlet extends HttpServlet {
//通过这个数组来表示所有消息
private List messages = new ArrayList<>();
//获取服务器所有消息操作
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf-8");
//获取到消息列表
//此处需要将message数组中的数据转换成json格式返回给浏览器
ObjectMapper objectMapper = new ObjectMapper();
//通过ObjectMapper 的 writeValueAsString() 方法就可以将一个对象转换成一个json字符串
String jsonString = objectMapper.writeValueAsString(messages);
resp.getWriter().write(jsonString);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
ObjectMapper objectMapper = new ObjectMapper();
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
messages.add(message);
resp.getWriter().write("提交成功!");
}
}
前端代码实现:
表白墙
表白墙
输入后点击提交,会将信息显示在表格中
谁:
对谁:
说:
上述代码,我们实现了,前端与后端的交互,以及服务器的部署,但是我么可以保证在页面刷新的时候不会将数据清楚,但是当我们服务器关闭的时候,数据还是会消失,为了解决这个问题,就需要让数据能够持久化存储。
持久化存储:是把数据保存到可以永久保存的存储设备中(如硬盘),是一种将程序数据在持久状态和瞬时状态间转换的机制
持久化存储机制包括:JDBC和文件IO
1、建立数据库
drop database if exits messagewall;
create database messagewall;
use messagewall;
drop table if exits message;
create table message (
`from` varchar(50),
`to` varchar(50),
`message` varchar(1024)
);
2、在pom.xml中引入mysql的jar包
3、将代码与数据库进行连接(主要是将list数组删除,添加一个save和load方法)
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysql.cj.jdbc.MysqlDataSource;
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.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
//这个类表示一条消息的详细信息
class Message{
public String from;
public String to;
public String message;
@Override
public String toString() {
return "Message{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", message='" + message + '\'' +
'}';
}
}
@WebServlet("/message")
public class messageServlet extends HttpServlet {
//通过这个数组来表示所有消息
//private List messages = new ArrayList<>();
//获取服务器所有消息操作
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf-8");
//获取到消息列表
//此处需要将message数组中的数据转换成json格式返回给浏览器
ObjectMapper objectMapper = new ObjectMapper();
List messages = null;
try {
messages = load();
} catch (SQLException e) {
throw new RuntimeException(e);
}
//通过ObjectMapper 的 writeValueAsString() 方法就可以将一个对象转换成一个json字符串
String jsonString = objectMapper.writeValueAsString(messages);
resp.getWriter().write(jsonString);
}
//这个方法用来往数据库中存一条数据
private List load() throws SQLException {
List messages = new ArrayList<>();
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/message?characterEncoding=utf-8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("");
Connection connection = dataSource.getConnection();
String sql = "select* from message";
PreparedStatement statement = connection.prepareStatement(sql);
ResultSet resultSet = statement.executeQuery();
while(resultSet.next()){
Message message = new Message();
message.from = resultSet.getString("from");
message.to = resultSet.getString("to");
message.message = resultSet.getString("message");
messages.add(message);
}
resultSet.close();
statement.close();
connection.close();
return messages;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
ObjectMapper objectMapper = new ObjectMapper();
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
try {
save(message);
} catch (SQLException e) {
throw new RuntimeException(e);
}
System.out.println("消息提交成功! message=" + message);
resp.getWriter().write("提交成功!");
}
private void save(Message message) throws SQLException {
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/message?characterEncoding=utf-8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("");
Connection connection = dataSource.getConnection();
String sql = "insert into message value(?,?,?)";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1,message.from);
statement.setString(2, message.to);
statement.setString(3, message.message);
statement.executeUpdate();
statement.close();
connection.close();
}
}
Cookie介绍我们已经在上一篇Http中说的很详细了Cookie介绍
在了解Cookie之后,我们发现Cookie是不能够用于存储和用户直接相关的信息的,一是Cookie的存储空间有限,二是发送请求时占用的带宽很多,三是不安全。即这些数据不适合保存到客户端,保险粗在服务器是最合适的,通过会话(Session)的方式就能够保存这些数据。
基本介绍:
在计算机中,尤其是网络应用中,Session称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息,当用户在应用程序的Web页面跳转时,存储在Session对象中变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来程序的Web页时,如果该用户还没有会话,则Web服务器将自动会创建一个Session对象,当会话过期或放弃后,服务器会终止该会话。Session对象最常见的一个用法就是存储用户首选项,例如,如果用户知名不喜欢查看图形的时候,就可以将该信息储存在Session对象中,注意会话状态仅在支持Cookie的浏览器中保存。
会话本质:
会话本质就是一个哈希表,其中存储一些键值对结构,key叫做sessionnId,是一个不随机的,不重复的,唯一的字符串,value就是要保存的身份信息,通过HttpSession对象来保存,key和value都是Servlet自动创建的。
每个用户登录都会产生一个会话,服务器会以哈希表的方式将这些会话管理起来
一个会话的详细数据通过一个HttpSession对象来存储,并且HttpSession对象中存储的数据也是键值对结构,key和value都是程序员自定义的
接着Cookie不适合用于存储用户相关的直接信息来讲,由于客户端不适合存储这些数据,服务器这边可以通过Session会话方式来进行保存。下面将会以用户的登录流程来介绍Session会话机制
Session会话机制的好处
注意:Servlet的session默认是保存在服务器内存中的,如果重启服务器Session数据会消失
HttpServletRequest类中相关的方法
方法 | 作用 |
HttpSession getSession(参数) | 在服务器中获取会话,参数如果是true,当不存在会话时,会创建一个会话(包括生成一个新的sessionId和HttpSession对象),并通过Set-Cookie将sessionId返回给客户端;参数如果是false,当不存在会话时会返回null。如果存在sessionIOd且合法,就根据这个sessionId找到对应的HttpSession对象并返回 |
Cookie[] getCookies() | 返回一个数组,包含客户端发送请求的所有Cookie对象,会自动把Cookie中的格式解析成键值对 |
HttpServletresponse类中的方法
方法 | 作用 |
void addCookie(Cookie cookie) | 把指定的cookie添加到响应中 |
HttpSession类中相关方法
方法 | 作用 |
Object getAttribute(String name) | 该方法返回在Session会话中会有指定名称的对象,如果没有指定名称的对象,返回null |
void setAttribute(String name,Object value) | 该方法使用指定名称绑定一个对象到该Session会话中 |
boolean isNew() | 判断当前会话是否是先创建的 |
Cookie类中的相关方法
方法 | 作用 |
String getName() | 该方法返回的Cookie名称(这个值是Set-Cookie字段设置给浏览器的,创建之后不会改变) |
String getValue() | 该方法获取与Cookie关联的值 |
void setValue(String newValue) |
该方法设置与Cookie关联的值 |
接下来将使用上述的Session和Cookie相关的方法来实现一个用户登录功能,并且可以记录访问页面次数
登录功能实现思路:
登录功能实现流程:
Document
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("/login")
public class loginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
//1、从请求中获取到用户名和密码
String username = req.getParameter("username");
String password = req.getParameter("password");
//2、对账号密码进行校验
if(username==null||username.isEmpty()||password==null||password.isEmpty()){
resp.getWriter().write("账号或密码不可以为空!");
return;
}
//3、判断是否登录成功(假设用户名为 admin,密码为 1234。不过账号密码应该用数据库存储,这里只是用来测试)
if(!username.equals("admin") || !password.equals("1234")){
resp.getWriter().write("账号或密码错误!
");
return;
}
//4、登录成功,创建一个会话,用来记录当前用户信息
HttpSession session = req.getSession(true);
//通过这个操作,程序员就可以给session增加自定义信息,如访问次数
session.setAttribute("visitCount",0);
//5、把登录成功的结果返回给客户端(这里的反馈不是简单的提示登陆成功,而是直接跳转到指定页面)
resp.sendRedirect("index");
}
}
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("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf8");
//只有登陆成功参数才是true,这里是拿参数,所以填false
HttpSession session = req.getSession(false);
//判断当前用户是否登录
if(session == null){
//返回登录界面
resp.sendRedirect("login.html");
return;
}
//表示用户登录过,获取会话中的访问次数
Integer visitCount = (Integer) session.getAttribute("visitCount");
visitCount+=1;
session.setAttribute("visitCount",visitCount);
resp.getWriter().write("visitCount = " + visitCount + "
");
}
}
实现效果如下:
上传文件是日常开发中的一类常见的需求,在Servlet中也进行支持
HttpServletrequest类中的核心方法
方法 | 作用 |
Part getPart(String name) | 获取请求中给定name的文件 |
Collection |
获取所有的文件 |
Part类中的相关方法
方法 | 作用 |
String getSubmittedFileName() | 获取提交的文件名 |
String getContentType() | 获取提交文件类型 |
long getSize() | 获取文件大小,单位为字节 |
void write(String path) | 把提交的文件数据写入磁盘文件 |
1、写一个前端页面,用于上传文件
Document
2、写一个Servlet用于处理上传的文件
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;
@MultipartConfig
@WebServlet("/upload")
public class fileServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
//通过getPart方法获取到前端传来的文件
Part part = req.getPart("MyFile");
//获取文件名
String fileName = part.getSubmittedFileName();
System.out.println("文件名:"+fileName);
//获取提交的文件类型
String fileType = part.getContentType();
System.out.println("文件类型:"+fileType);
//获取文件的大小
long fileSize = part.getSize();
System.out.println("文件大小为:"+fileSize);
part.write("D:\\test\\text.mp4");
resp.getWriter().write("上传成功!");
}
}