前言
把文题中的三个概念放到一起做总结,完全是出于很长一段时间博主对它们的理解是非常混乱的,所以就毫无逻辑的把它们总结到一起啦。
1、Servlet
1.1servlet的运行原理
在浏览器地址栏中输入一个网址并确认,浏览器会向服务器发送一个HTTP请求,服务器端接收这个请求,并对请求作相应的回应处理。并将回应处理结果返回给浏览器,浏览器再把回应的内容显示出来,这种基于请求-响应的模式就是典型的web应用程序的访问过程。
我们知道:在计算机网络中,两台主机之间数据的交互需要遵循一系列的应用层,传输层,网络层,链路层以及物理层的相关协议。HTTP协议作为一种应用层协议 ,规定了因特网上数据传送的规则,因此要实现两台主机之间的交互,两台主机都必须实现该协议。
而使用较多的免费版的Tomcat就可以看作是实现了服务器端HTTP协议的应用,而浏览器则是实现了客户端HTTP协议的应用。在Java Web项目中,服务器端具体的响应处理则是通过Servlet实现。
servlet是什么?从程序语言的角度看,Servlet是实现了javax.servlet.Servlet接口的类,该接口针对不同HTTP请求规定了特定的方法来处理,只要实现了这些方法,客户端访问web程序的时候,web服务器就会调用相应方法完成业务处理(具体怎么被调用,这就需要对web服务器的比较了解,暂且搁置(暂时还没有计划到这块的内容囧))。在实际应用中,JDK中javax.servlet.*和javax.servlet.http.*包提供了对Servlet的支持,javax.servlet.http.HttpServlet类就实现了Servlet接口的所有方法,可以直接继承该类重写需要的方法,可以大大提供编程效率。
在java web程序中,servlet的基本配置如下:
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">
<--为servlet命名-->
<servlet>
<servlet-name>uploadServlet</servlet-name>
<servlet-class>com.xin.servlet.UploadServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<--为servlet定制URL-->
<servlet-mapping>
<servlet-name>UploadServlet</servlet-name>
<url-pattern>/src/com/xin/servlet/*</url-pattern>
</servlet-mapping>
<--指定欢迎页面-->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
<servlet’>配置相应的Servlet类;<servlet-mapping’>配置servlet的映射路径,也即是客户端相应路径的请求才会交给该servlet处理;servlet程序要被web服务器正常调用,必须继承相应的类,例如HttpServlet;应用中通常是扩展HttpServlet类,该类声明了一些处理客户端请求的对应方法,根据客户端的请求类型(POST、GET、HEAD等),编写相应的处理方法,在博主的小项目中,客户端使用的是post请求提交表单,因此在servlet中的编写了方法:doPost();servlet的规范规定了对应客户端的请求方法必须是do+请求类型(例如doGet);
1.2 Servlet生命周期
servlet会在服务器启动或者说是第一次请求servlet的时候开始生命周期,在服务器结束的时候结束生命周期,也就是说在整个应用进程中始终只存在一个servlet实例,当多个客户端并发请求时,服务器会启动多个线程来执行servlet中的service方法,而Servlet中的init()与destroy()方法只会分别在Servlet被加载和销毁时执行一次。
1.3 servlet的线程安全性
通过上文中对servlet生命周期的描述可以知道,servlet不是线程安全的,如果在自定义的Servlet中定义属性,在被多个线程写入时会出现数据不同步的问题。
servlet,Session,Cookie生命周期的讨论
Session
session可以说是由web服务器生成和管理的,用户初次访问服务器时会被创建,可以通过以下方式 获取到session对象: request.getSession()
Session虽然保存在服务器中,对客户端浏览器来说貌似无要求,但是由于HTTP是一种无状态协议(简单来讲就是同一客户请求同一应用的不同URL时不能被服务器知晓是一个用户所为),不能够依据HTTP连接来判断是不是为同一用户,而Session的作用就是为了实现用户的浏览追踪,那么如何实现呢?
在用户初次请求服务时,服务器自动向浏览器发送一个名为JSESSIONId的cookie,其值为session的id,可以通过HttpSession的getId()获取。
Cookie
Cookie相当于是用户初次访问服务器时,服务器为用户颁发的通行证,客户端保存这个通行证,在二次访问中,客户端会将请求的网址连同这个通行证一起提交给服务器,服务器就可以检查该Cookie来辨别用户。在Seesion中,用户首次请求服务后被颁发一个会话ID(SessionID),作为其后面继续访问的服务的跟踪ID,服务器可以借此判断用户身份,添加或修改关于用户的相关信息。
以下是关于一个文件上传的小demo,主要通过该demo演示servlet处理请求的过程以及cookie与session的基本工作原理。
index页面:
<%@ 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=ISO-8859-1">
<title>首页</title>
</head>
<body>
<div style="width:100%;hight:50px;margin-bottom: 10px;background-color: blue;color: yellow" align="center">文件上传功能测试</div>
<div style="width:100%;">
<div style="width:50%;height:100px"align="left">
<form action="<%= request.getContextPath() %>/src/com/xin/servlet" method="post" enctype="multipart/form-data">
文件上传:<input type="file" name="file1" /></br>
文本域:<input type="text" name="description1"/>
<button type="submit">提交</button>
</form>
</div>
<div style="width:50%;height:300px">
<div style="margin-top: 20px;margin-bottom: 20px">服务器返回上传时间,保存在Session中的特性:<%=session.getAttribute("uploadTime") %></div>
<div style="margin-bottom: 20px">服务器返回输入的文本域信息,保存在Session中的特性:<%=session.getAttribute("文本域") %></div>
<div>服务器返回上传文件名称,保存在Cookie中的属性:</br>
<%
Cookie[] cookies=request.getCookies();
if(cookies!=null&&cookies.length!=0){
for(Cookie cookie:cookies){
%>
<p>Cookie的名称:<%=cookie.getName() %></p>
<p>Cookie的取值:<%=cookie.getValue() %></p>
<% } }%>
</div>
</div>
</div>
</body>
</html>
servlet处理请求:`
package com.xin.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
/**
*
* @author XueXin
*
*/
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* 处理页面的post请求
*/
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException{
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if(isMultipart) {
DiskFileItemFactory fac = new DiskFileItemFactory();
//创建一个新的文件上传处理程序
ServletFileUpload upload =new ServletFileUpload (fac);
//获取会话对象,
HttpSession session=request.getSession();
//List<HttpSession> sessions=new LinkedList<HttpSession>();
//会话跟踪的演示
session.setAttribute("uploadTime",new Date());
String description1=null;
try {
//解析浏览器请求,
List <FileItem> list = upload.parseRequest(request);
for (Iterator<FileItem> iterator = list.iterator(); iterator.hasNext();) {
FileItem fileItem = (FileItem) iterator.next();
//如果是文本域
if (fileItem.isFormField()&&"description1".equals(fileItem.getFieldName())){
description1=new String(fileItem.getString().getBytes("ISO-8859-1"), "UTF-8");
session.setAttribute("文本域",description1);
}else if("file1".equals(fileItem.getFieldName())){
//如果是文档文件
File file1=exportData(fileItem);
//添加cookie,验证在客户端获取到cookie;
Cookie cookie=new Cookie("fileName", file1.getName());
response.addCookie(cookie);
}
}
//请求转发,转发到不同的页面看看,session的追踪效果;
RequestDispatcher dispatcher=request.getRequestDispatcher("/WEB-INF/views/welcome.jsp");
dispatcher.forward(request, response);
} catch (FileUploadException e) {
e.printStackTrace();
}
}
}
/**
*@author XueXin
*@parameter
*@return file
*@date 2018年9月5日
*@function 读取上传文件并保存在本地,IO模型的解析;
*/
public File exportData(FileItem fileItem) throws IOException {
//存储下载文件
File downLoadFile=new File(new String(fileItem.getName().getBytes("UTF-8")),"UTF-8");
//下载文件的存储位置
File file1=new File(this.getServletContext().getRealPath("exportData"),downLoadFile.getName());
//创建目录
file1.getParentFile().mkdirs();
//创建文件
file1.createNewFile();
InputStream inputStream=fileItem.getInputStream();
OutputStream outputStream=new FileOutputStream(file1);
try {
byte[] buffer=new byte[1024];
int len=0;
//从输入流读取数据
while((len=inputStream.read(buffer))>-1) {
//向输出流写入数据
outputStream.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
outputStream.close();
inputStream.close();
}
return file1;
}
}
跳转页面:
<%@ 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=ISO-8859-1">
<title>转发页面</title>
</head>
<body>
<div style="margin-top: 20px;margin-bottom: 20px">服务器返回上传时间,保存在Session中的时间信息:<%=session.getAttribute("uploadTime") %></div>
<div style="margin-bottom: 20px">服务器返回输入的文本域信息,保存在Session中的文本域信息:<%=session.getAttribute("文本域") %></div>
<div>服务器返回上传文件名称,保存在Cookie中的属性:</br>
<%
Cookie[] cookies=request.getCookies();
if(cookies.length!=0){
for(Cookie cookie:cookies){
%>
<p>Cookie的名称:<%=cookie.getName() %></p>
<p>Cookie的取值:<%=cookie.getValue() %></p>
<% } }%>
</div>
</body>
</html>
图片是关于项目运行之后的具体前台页面效果,在demo中,用户首次访问服务时,并没有经过servlet的处理,但是服务器自动为用户颁发了了个会话跟踪ID也即是JSESSIONID;
上传文件b.txt,在文本域中输入message1提交之后,经过servlet中doPost()方法的处理,session中添加了message1属性值和文件上传时间: