webApp: 网站、完整的项目。【平台】
Servlet: 实现WebApp的工具,准确来说是一个框架,通过框架可以更方便的开发项目(后端API)。【工具】
Tomcat: 部署Servlet程序的。【商家】
它们之间的关系就是:平台给商家提高工具。
Servlet的难点是:配置比较多,且配置过程中的问题特别多。
配置过程中的问题特别多的原因是:
概念: Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
定位: Java Servlet用Java编写的服务器端程序(web application)。
作用: 其主要功能在于交互式地浏览和修改数据,生成动态Web内容。
理解: 狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,我们将Servlet理解为后者。
创建Servlet项目并成功发布运行的步骤如下所示:




<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"
metadata-complete="true">
<servlet>
<servlet-name>Helloservlet-name>
<servlet-class>HelloServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>Helloservlet-name>
<url-pattern>/hello-servleturl-pattern>
servlet-mapping>
web-app>
这里必须说一下访问流程,就是当我们前端去访问一个url地址的时候,它先是进去servlet项目去找web.xml文件中的servlet-mapping和我的url进行对比,看有没有一个叫/hello的,如果有就说明我们此时访问的地址是对的。
接下来根据url拿到servlet-mapping中的servlet-name,然后根据这个servlet-name去servlet中找和前面这个servlet-name一样的servlet-name,然后就可以得到servlet-class的名字,这个servlet-class对应的就是我们后端代码的地址的类名。
servlet项目具体访问流程如下图所示:

5. 创建普通类并且将它升级为Servlet后端类。
//普通类
//public class HelloServlet{
//}
//将它升级为Servlet后端类
//public class HelloServlet extends HttpServlet{
//}
//具体写的一个实现例子
public class HelloServlet extends HttpServlet{
//方法类型为GET的请求
//重写doGet方法,把里面的内容删空,替换成你自己要实现的内容
//实现内容:让服务器给客户端相应一个HelloServlet
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
//1.获得写入流
PrintWriter printWriter = response.getWriter();
printWriter.println("HelloServlet
");
}
}


注意:可能会出现页面乱码问题,如何解决呢?我们在写代码时需要设置页面编码和设置返回的数据类型即可解决此问题。

写一个前后端交互的计算器(这里目前仅实现加法运算)。
<html>
<head>
<meta charset="utf-8">
<title>我的计算器title>
head>
<body>
<form method="post" action="calc">
<div style="text-align: center;">
<h1>计算器<h1>
数字1:<input id="n1" name="number1" type="number"><p>p>
数字2:<input id="n2" name="number2" type="number"><p>
<input type="submit" value="提交">
p>
div>
form>
body>
html>
public class Calc extends HttpServlet{
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
//业务逻辑
//1.获得前端提交的参数
Integer num1 = Integer.parseInt(response.getParameter("number1"));
Integer num2 = Integer.parseInt(response.getParameter("number2"));
Integer total = num1 + num2;
PrintWriter printWriter = response.getWriter();
printWriter.println("计算的结果:"
+total+"");
}
}
<servlet>
<servlet-name>calcservlet-name>
<servlet-class>Calcservlet-class>
servlet>
<servlet-mapping>
<servlet-name>calcservlet-name>
<url-pattern>/calcurl-pattern>
servlet-mapping>

public class Calc extends HttpServlet{
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
//业务逻辑
//1.获得前端提交的参数
Integer num1 = Integer.parseInt(response.getParameter("number1"));
Integer num2 = Integer.parseInt(response.getParameter("number2"));
Integer total = num1 + num2;
PrintWriter printWriter = response.getWriter();
printWriter.println("计算的结果:"
+total+"");
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
this.doGet(request,response);
}
}
Servlet运行原理图如下所示:

当第四步完成之后就要去执行执行Servlet后端类了,Servlet后端类执行的过程有三个方法:

Servlet 程序中读取 HTTP Request头内容的基本方法使用示例如下所示:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class HelloServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse
response)throws ServletException,IOException{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
//获取请求方法类型
String method = request.getMethod();//返回请求的 HTTP 方法的名称
String encoding = request.getCharacterEncoding();//返回请求主体中使用的字符编码的名称
String url = request.getContextPath();//返回指示请求上下文的请求 URI 部分
String contentType = request.getContentType();//返回请求主体的 MIME 类型,如果不知道类型则返回 null
//输出获取参数信息
String title = "HTTP Function Test!";
String docType = "\n";
out.println(docType + "\n" +"" +title+ "\n" +""+" Method: "
+method+ "" +
" encoding: "
+encoding+ "" +" contentType: "
+contentType+ "" +""+"");
}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
doGet(request,response);
}
}
运行结果:

Servlet 程序中读取 HTTP Response头内容的基本方法使用示例如下所示:
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.GregorianCalendar;
// 扩展 HttpServlet 类
public class HelloServlet extends HttpServlet {
// 处理 GET 方法请求的方法
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
// 设置刷新自动加载时间为 1 秒
response.setIntHeader("Refresh", 1); //设置一个带有给定的名称和整数值的响应报头。
// 设置响应内容类型
response.setContentType("text/html");
// Get current time
Calendar calendar = new GregorianCalendar();//获取当前系统时间
String am_pm;
int hour = calendar.get(Calendar.HOUR);//获得时
int minute = calendar.get(Calendar.MINUTE);//获得分
int second = calendar.get(Calendar.SECOND);//获得秒
if(calendar.get(Calendar.AM_PM) == 0) //判定是否是上午和下午
am_pm = "AM";
else
am_pm = "PM";
String CT = hour+":"+ minute +":"+ second +" "+ am_pm;
PrintWriter out = response.getWriter();
String title = "auto refresh Header set";
String docType = "\n";
out.println(docType +"\n" +"" + title + "\n"+"\n" +""
+ title + "\n" +"current time: "
+ CT + "\n");}
// 处理 POST 方法请求的方法
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {
doGet(request, response);
}
}
运行结果:

或页面每秒进行一下刷新代码示例:
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
this.doPost(request, response);}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
//response.setHeader("Refresh","1");
response.setIntHeader("Refresh",1);
PrintWriter printWriter = response.getWriter();
printWriter.println(String.format("Data:%s"
</h1>,new Date()));
}
}
方法一:
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
this.doPost(request, response);}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
String name = request.getParameter("name");
String password = request.getParameter("pwd");
if(name!= null && pwd!= null && name.equals("root")&& pwd.equals("root")){
//用户名和密码输入正确,跳转到百度
response.setStatus(301);
response.setHeader("location","https://www.baidu.com/");
}else{
PrintWriter printWriter = response.getWriter();
printWriter.println("输入的用户名和密码错误!
");
}
}
}
方法二:
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
this.doPost(request, response);}
@Override
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
String name = request.getParameter("name");
String password = request.getParameter("pwd");
if(name!= null && pwd!= null && name.equals("root")&& pwd.equals("root")){
response.sendRedirect("https://www.baidu.com");
}else{
PrintWriter printWriter = response.getWriter();
printWriter.println("输入的用户名和密码错误!
");
}}
}
前言: http是一个无状态(短链接)的协议,所以客户端和服务器端在交互的时候不知道对方是谁。那么在某些需要记录状态的场景下我们就需要想一种机制来保存会话信息。比如说我们在做登录功能的时候是要保存会话信息的,我们登录一次之后,以后的页面(接口)在访问的时候是不需要登陆的,这叫会话(身份信息),这个时候靠谁来保存呢?
短链接: 客户端把请求发送给服务器端,服务器端响应给客户端,然后响应完成之后整个链接就没有了。
那么http在设计的时候为什么不设置成有状态(长链接)的协议呢?因为服务器端只有一份,而客户端可能会有多份。如果设置成长链接,当服务在处理你的请求的时候就不能处理其他人的请求了,那么服务器端链接的同时人数使用限制的,最大是65535。比如我们现在服务器上有65535条链接,当其他人进来的时候服务器就不能提供服务了,这是不允许的,所以当人数超过这个数的时候就不能用了。但如果设置成短链接,一秒钟响应给你之后,那么下一秒钟我们就可以服务另一个人了,所以http一定要设置成无状态(短链接)的协议。
但此时我们做登录功能的时候是要保存会话信息的,所以只能我们自己想办法来保存这个会话信息了。这个时候怎么来保存呢?才开始我们用的是Cookie进行保存会话信息的,那么cookie是怎么保存会话信息的呢?


总结: cookie工作原理:
Cookies 通常设置在 HTTP 头信息中。设置 Cookie 的http请求,会向 Servlet 会发送如下的头信息:
HTTP/1.1 200 OK
Date: Fri, 04 Feb 2000 21:03:38 GMT
Server: Apache/1.3.9
Set-Cookie: name=xyz; expires=Friday, 04-Feb-07 22:03:38 GMT;
path=/; domain=bit.com
Connection: close
Content-Type: text/html
如果用户的浏览器指向任何匹配该 Cookie 的路径和域的页面,它会重新发送Cookie 到服务器。浏览器的头信息可能如下所示:
GET / HTTP/1.0
Connection: Keep-Alive
User-Agent: Mozilla/4.6 (X11; I; Linux 2.2.6-15apmac ppc)
Host: zink.demon.co.uk:1126
Accept: image/gif, /
Accept-Encoding: gzip
Accept-Language: en
Accept-Charset: iso-8859-1,*,utf-8
Cookie: name=xyz
Servlet 就能够通过请求方法 request.getCookies() 访问 Cookie,该方法将返回一个 Cookie 对象的数组。
Servlet 中操作 Cookies 时有可使用的有用的方法列表。
这里以"提交表单,设置cookie"项目为例进行介绍。
具体操作:
DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
head>
<body>
<form action="hello-servlet" method="GET">
名字:<input type="text" name="first_name"><br />
姓氏:<input type="text" name="last_name" />
<input type="submit" value="提交" />
form>
body>
html>
// 导入必需的 java 库
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 java.io.IOException;
import java.io.PrintWriter;
// 扩展 HttpServlet 类
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
// 为名字和姓氏创建 Cookies
Cookie firstName = new Cookie("first_name",request.getParameter("first_name"));
Cookie lastName = new Cookie("last_name",request.getParameter("last_name"));
// 为两个 Cookies 设置过期日期为 24 小时后
firstName.setMaxAge(60*60*24);
lastName.setMaxAge(60*60*24);
// 在响应头中添加两个 Cookies
response.addCookie( firstName );
response.addCookie( lastName );
// 设置响应内容类型
response.setContentType("text/html");
// 设置响应的编码格式
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
String title = "设置 Cookies 实例";
String docType ="\n";
out.println(docType +"\n" +"" + title + "\n" +"\n" +""
+ title + "\n" +"\n"
+" - 名字:"
+ request.getParameter("first_name") + "\n" +" - 姓氏:"
+ request.getParameter("last_name") + "\n" +"\n" +"");
}
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
doGet(request,response);
}
}


// 导入必需的 java 库
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 java.io.IOException;
import java.io.PrintWriter;
// 扩展 HttpServlet 类
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
Cookie cookie = null;
Cookie[] cookies = null;
// 获取与该域相关的 Cookies 的数组
cookies = request.getCookies();
// 设置响应内容类型
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
String title = "Reading Cookies Example";
String docType ="\n";
out.println(docType +"\n" +"" + title + "\n" +"\n" );
if( cookies != null ){
out.println("查找 Cookies 名称和值
");
for (int i = 0; i < cookies.length; i++){
cookie = cookies[i];
out.print("名称:" + cookie.getName( ) + ",");
out.print("值:" + cookie.getValue( )+"
");
}
}
out.println("");
out.println("");
}
public void doPost(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
doGet(request,response);
}
}

Cookie cookie = null;
Cookie[] cookies = null;
// 获取与该域相关的 Cookies 的数组
cookies = request.getCookies();
// 设置响应内容类型
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
String title = "Delete Cookies Example";
String docType ="\n";
out.println(docType +"\n" +"" + title + "\n" +"\n" );
if( cookies != null ){
out.println("Cookies 名称和值
");
for (int i = 0; i < cookies.length; i++){
cookie = cookies[i];
if((cookie.getName( )).compareTo("first_name") == 0 ) {
cookie.setMaxAge(0); //将过期时间设置为0,删除指定cookie
response.addCookie(cookie);
out.print("已删除的 cookie:" +
cookie.getName( ) + "
");
}
out.print("名称:" + cookie.getName( ) + ",");
out.print("值:" + cookie.getValue( )+"
");
}
}
out.println("");
out.println("");
Cookie的缺点: 因为Cookie是存储在客户端的,所以它有被篡改的风险,所以现在我们只用cookie来存储一些不重要的信息。
为了解决上面的篡改问题,我们引入了session,session字面翻译过来就是会话的意思。
Session的作用和特点就是保存在服务器端,对于服务器我们是没有权限去修改的(除非程序员用代码去操作服务器),所以很好的解决了篡改问题。


HttpSession 对象:
HttpSession session = request.getSession();。Servlet 中操作 Session时 HttpSession 对象中有可用的方法列表。
这里以本例说明了如何使用 HttpSession 对象获取 session 会话创建时间和最后访问时间。如果不存在session 会话,我们将通过请求创建一个新的 session 会话。
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
public class HelloServlet extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{
// 如果不存在 session 会话,则创建一个 session 对象
HttpSession session = request.getSession(true);
// 获取 session 创建时间
Date createTime = new Date(session.getCreationTime());
// 获取该网页的最后一次访问时间
Date lastAccessTime = new Date(session.getLastAccessedTime());
String title = "欢迎回来";
String visitCountKey = new String("visitCount");
Integer visitCount = new Integer(0);
String userIDKey = new String("userID");
String userID = new String("abcd");
// 检查网页上是否是新的访问者
if(session.isNew()){
title = "欢迎来到我的网站";
session.setAttribute(userIDKey, userID);
}else{
visitCount = (Integer) session.getAttribute(visitCountKey);
visitCount++;
userID = (String)session.getAttribute(userIDKey);
}
session.setAttribute(visitCountKey,visitCount);
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
String docType = "\n";
out.println(docType + "\n" +"" +title+ "\n" +"\n" +""
+ title + "\n"+
" Session 信息
" +""
+"ID: " + session.getId() + "
"+"Create Time: " + createTime + "
"+"Time of Last Access: " + lastAccessTime + "
"+"User ID: " + userID + "
"+"Number of visits: " + visitCount + "
"+""+ "" +"");
}
}
Step 1: 发起请求,查看结果

Step 2: 分析请求结果


Step 3: 多次刷新

一般情况下,session是直接存储在指定服务器的内存中的,一般使用够了,但是在集群场景当中,可能需要通过session共享,来保证用户在不同的服务器上都可以得到认证。所以我们一般可以将用户的session信息统一保存在后端的数据库服务器中[比如,mysql/redis/memcache等],从而达到不同服务器间session的共享。
我们可以简单理解:将session保存在服务器数据库或者文件中的行为称之session持久化。

将用户敏感信息放到本地浏览器中,能解决一定的问题,但是又引进了新的安全问题,一旦cookie丢失,用户信息泄露,也很容易造成跨站攻击,所以有了另一种解决方法,将用户敏感信息保存至服务器,而服务器本身采用md5算法或相关算法生成唯一值(session id),将该值保存值客户端浏览器,随后,客户端的后续请求,浏览器都会自动携带该id,进而再在服务器端认证,进而达到状态保持的效果。
两者有什么区别呢:
HttpServletRequest 对文件上传的支持:
此前,Servlet 本身没有对文件上传提供直接的支持,一般需要使用第三方框架来实现,实现起来很麻烦。不过,Servlet 3.0 之后提供了这个功能,而且使用也非常简单。为此,HttpServletRequest 提供了两个方法用于从请求中解析出上传的文件:
Part getPart(String name) //获取请求中给定 name 的文件
Collection<Part> getParts() //获取所有的文件
其中,每一个文件用一个 javax.servlet.http.Part 对象来表示。该接口提供了处理文件的简易方法,比如 write()、delete() 等。至此,结合 HttpServletRequest 和 Part 来保存上传的文件变得非常简单。
Part img = request.getPart("img");
img.write("E:\\apache-tomcat-8.5.47\\webapps\\file_dir\\img.jpg");
另外,可以配合前面提到的 @MultipartConfig 注解来对上传操作进行一些自定义的配置,比如限制上传文件的大小,以及保存文件的路径等。其用法非常简单,故不在此赘述了。
需要注意的是,如果请求的 MIME 类型不是 multipart/form-data,则不能使用上面的两个方法,否则将抛异常。
例子:
前端页面:
DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>文件上传表单title>
head>
<body>
<h3>文件上传:h3>
请选择要上传的文件:<br />
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" size="50" />
<br />
<input type="submit" value="上传文件" />
form>
body>
html>
UploadServlet:
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
@MultipartConfig
public class UploadServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException,IOException{
//具体上传上来的文件放在什么地方,由自己决定
File path = new File("E:\\apache-tomcat-8.5.47\\webapps\\file_dir");
//获取文件,文件在html中的name是“file”
Part img = request.getPart("file");
//制作文件全路径
String filePath = path.getPath()+File.separator +
img.getSubmittedFileName();
//获取成功之后,写入指定路径
img.write(filePath);
//显示到标准输出
System.out.println("file Upload: " + filePath);
//同样的信息,显示给用户浏览器
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("file Upload: " + filePath);
}
public void doGet(HttpRequest request, HttpResponse response)
throws ServletException,IOException {
//上传文件,不能用GET方法
System.out.println("上传文件只能用POST方法!");
}
}
运行结果:


总结:

