随着Web应用业务需求的增多,动态Web资源的开发变得越来越重要。目前,很多公司都提供了开发动态Web资源的相关技术,其中,比较常见的有ASP、PHP、JSP和Servlet等。基于Java的动态Web资源开发,Sun公司提供了Servlet和JSP两种技术。
Servlet: server applet: 运行在服务器端的小程序。Java提供了Servlet接口,此接口的实现类可以运行在服务器上。Servlet 主要用于处理客户端传来的 HTTP 请求,并返回一个响应。
Servlet由Servlet容器提供,所谓的Servlet容器是指提供了Servlet 功能的服务器,Servlet容器将Servlet动态地加载到服务器上。与HTTP 协议相关的Servlet使用HTTP请求和HTTP响应与客户端进行交互。因此,Servlet容器支持所有HTTP协议的请求和响应。Servlet应用程序的体系结构如图所示。

Servlet技术特点:

创建JavaEE项目
定义一个类,实现Servlet接口。
在web.xml中配置:
<servlet>
<servlet-name>demo1</servlet-name>
<servlet-class>ccom.example.tomcat.ServletDemo1</servlet-class>
</serblet>
<servlet-mapping>
<servlet-name>demo1</servlet-name>
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
执行原理:

被创建时:执行init方法,只执行一次。
默认情况下,第一次被访问时,创建Servlet。
可以修改Servlet的创建时机,在<Servlet>里修改<load-on-startup>的值。
<servlet>
<servlet-name>demo2</servlet-name>
<servlet-class>com.example.tomcat.ServletDemo2</servlet-class>
<!-- 指定Servlet的创建时机
1.第一次被访问时创建
<load-on-startup>的值为负数
2.在服务器启动时创建
<load-on-startup>0~9999</load-on-startup>数字小的先
-->
<load-on-startup>5</load-on-startup>
</servlet>
Servlet的init方法只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的。
提供服务:执行service方法。
每次访问Servlet时,都执行一次service。
被销毁:destroy方法
在服务器正常关闭时,Servlet被销毁,才会执行destroy方法。
Servlet3.0:支持注解配置,不需要写麻烦的web.xml了
创建JavaEE项目,Servlet版本选3.0以上,可以不创建web.xml
定义一个类,实现Servlet接口。
在类上使用@WebServlet注解,如下配置
@WebServlet("资源路径")
urlpartten:访问该Servlet的虚拟路径
WebServlet的源码
public @interface WebServlet {
String name() default ""; //相当于<Servlet-name>
String[] value() default {};//代表urlPattern
String[] urlPatterns() default {};//同上
int loadOnStartup() default -1;//相当于<load-on-sta
WebInitParam[] initParams() default {};
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
接口Servlet-->抽象类GenericServlet-->抽象类HttpServlet
Servlet接口的实现类要重写5个方法,其实一般只需要使用service方法,如果重写5个方法就很麻烦。Java提供了两个抽象类方便我们的使用。
生命周期
出生:请求第一次到达Servlet时,对象就创建出来,并且初始化成功。只出生一次,就放到内存中。
活着:服务器提供服务的整个过程中,该对象一直存在,每次只是执行service方法。
死亡:当服务停止时,或者服务器宕机时,对象消亡。
通过分析Servlet的生命周期我们发现,它的实例化和初始化只会在请求第一次到达Servlet时执行,而销毁只会在Tomcat服务器停止时执行,由此我们得出一个结论,Servlet对象只会创建一次,销毁一次。所以,Servlet对象只有一个实例。如果一个对象实例在应用中是唯一的存在,那么我们就说它是单实例的,即运用了单例模式。

线程安全
因为Servlet是单例,单例对象的类成员只会随类实例化时初始化一次,之后的操作都是改变,而不会重新初始化。所以可能存在线程安全性问题。所以在Servlet中定义类成员要慎重。如果类成员是共用的,并且只会在初始化时赋值,其余时间都是获取的话,那么是没问题。如果类成员并非共用,或者每次使用都有可能对其赋值,那么就要考虑线程安全问题了,把它定义到doGet或者doPost方法里面去就可以了。
不用全局变量即可。
单独的Servlet还不能提供服务,还需要依赖几个变量:ServletRequest用来封装客户端请求,ServletResponse用来向客户端发送响应,ServletConfig保存Servlet的配置信息,ServletContext保存整个web项目的信息。

每个Servlet都一个自己的ServletConfig,保存了此Servlet的配置信息。它在初始化阶段读取了web.xml中为Servlet准备的初始化配置,并把配置信息传递给Servlet,所以生命周期与Servlet相同。
ServletContext对象,它是应用上下文对象。每一个应用有且只有一个ServletContext对象。它可以实现让应用中所有Servlet间的数据共享。 通常网站下方的 copyright 和备案号都是通过ServletContext对象保存的。
生命周期
出生: 应用一加载,该对象就被创建出来了。一个应用只有一个实例对象。(Servlet和ServletContext都是单例的)
活着:只要应用一直提供服务,该对象就一直存在。
死亡:应用被卸载(或者服务器挂了),该对象消亡。
域对象
域对象的概念,它指的是对象有作用域。域对象可以实现数据共享。不同作用范围的域对象,共享数据的能力不一样。
在Servlet规范中,一共有4个域对象。ServletContext是web应用中最大的作用域,叫application域。每个应用只有一个application域。它可以实现整个应用间的数据共享功能。
Session会话域
request请求域
pageContext页面域:jsp特有
ServletContext 是应用上下文对象。每一个应用中只有一个 ServletContext 对象。
作用:可以获得应用的全局初始化参数和达到 Servlet 之间的数据共享。
生命周期:应用一加载则创建,应用被停止则销毁。

ServletContext既然被称之为应用上下文对象,所以它的配置是针对整个应用的配置,而非某个特定Servlet的配置。它的配置被称为应用的初始化参数配置。
配置的方式,需要在<web-app>标签中使用<context-param>来配置初始化参数。具体代码如下:
<!--配置应用初始化参数-->
<context-param>
<!--用于获取初始化参数的key-->
<param-name>servletContextInfo</param-name>
<!--初始化参数的值-->
<param-value>This is application scope</param-value>
</context-param>
<!--每个应用初始化参数都需要用到context-param标签-->
<context-param>
<param-name>globalEncoding</param-name>
<param-value>UTF-8</param-value>
</context-param>
public class ServletContextDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/**
获取ServletContext对象
1. 通过request对象获取:request.getServletContext();
2. 通过Servlet对象获取:this.getServletContext();
*/
ServletContext context = getServletContext();
//获取全局配置的globalEncoding
String value = context.getInitParameter("globalEncoding");
System.out.println(value);
//获取应用的访问虚拟目录
String contextPath = context.getContextPath();
System.out.println(contextPath);
//文件有两个路径,一个是idea的工作空间路径,一个是部署在服务器上的路径
//根据虚拟目录获取应用部署的磁盘绝对路径
//获取webapp/b.txt文件的绝对路径
String b = context.getRealPath("/b.txt");
System.out.println(b);
//获取webapp/WEB-INF/c.txt文件的绝对路径
String c = context.getRealPath("/WEB-INF/c.txt");
System.out.println(c);
//获取java类目录下的a.txt文件的绝对路径
String a = context.getRealPath("/WEB-INF/classes/a.txt");
System.out.println(a);
//向域对象中存储数据
context.setAttribute("username","zhangsan");
//移除域对象中username的数据
//context.removeAttribute("username");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
Servlet最主要的作用就是处理客户端请求,并向客户端做出响应。为此,针对Servlet的每次请求,Web服务器在调用service()方法之前,都会创建两个对象,分别是HttpServletRequest和 HttpServletResponse。其中,HttpServletRequest 用于封装 HTTP 请求消息,简称 request对象。HttpServletResponse 用于封装 HTTP 响应消息,简称 response 对象。
request对象和response对象的原理

request对象继承体系结构
ServletRequest(interface) —>HttpServletRequest(interface)—>org.apache.catalina.connector.RequestFacade(class from tomcat)
request功能:
获取请求消息数据
获取请求行数据
GET /day14/demo1?name=zhangsan HTTP/1.1
方法:
获取请求方式 :String getMethod() GET
获取虚拟目录:String getContextPath() /day14。ServletContext也有这个方法。
获取Servlet路径:String getServletPath() /demo1
获取get方式请求参数:String getQueryString() name=zhangsan
获取请求URI:String getRequestURI(): /day14/demo1
StringBuffer getRequestURL(): http://localhost/day14/demo1
URL:统一资源定位符
URI:统一资源标识符
获取协议及版本:String getProtocol() HTTP/1.1
获取客户机的IP地址:String getRemoteAddr()
获取ServletContext:ServletContext getServletContext()
@WebServlet("/RequestDemo1")
public class RequesDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1. 获取请求方式 String getMethod()
System.out.println(req.getMethod());
// 2. 获取虚拟目录: String getContextPath()
System.out.println(req.getContextPath());
//3. 获取Servlet路径: String getServletPath()
System.out.println(req.getServletPath());
//4. 获取get方式请求参数 String getQueryString()
System.out.println(req.getQueryString());
//5. 获取请求URI:/day14/demo1
// String getRequestURI(): /day14/demo1
// StringBuffer getRequestURL(): http://localhost/day14/demo1
System.out.println(req.getRequestURI());
System.out.println(req.getRequestURL());
//6. 获取协议及版本: String getProtocol()
System.out.println(req.getProtocol());
//7. 获取客户机的IP地址: String getRemoteAddr()
System.out.println(req.getRemoteAddr());
}
}
获取请求头数据

String getHeader(String name):通过请求头的名称获取请求头的值
常用的头:user-agent; referer
@WebServlet("/rd3")
public class RequestDemo3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String ua = req.getHeader("user-agent");
if(ua.contains("Chrome")){
System.out.println("谷歌来了..");
}else if(ua.contains("Firefox")){
System.out.println("火狐来了..");
}
}
}
Enumeration<String> getHeaderNames():获取所有的请求头名称
@WebServlet("/rd2")
public class RequestDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取所有请求头名称
Enumeration<String> headerNames = req.getHeaderNames();
//2.遍历
while(headerNames.hasMoreElements()){//Enumeration接口见jdk文档
//根据名称获取请求头的值
String name = headerNames.nextElement();
String value = req.getHeader(name);
System.out.println(name+"------"+value);
}
}
}
获取请求参数
如果是post请求方式,可以使用流对象获取请求体。步骤:
获取流对象
BufferedReader getReader() 获取字符输入流,只能获取文本流行
ServletInputStream getInputStream() 获取字节输入流,可以操作所有类型数据,用于文件上传
再从流对象中拿数据
@WebServlet("/rd5")
public class RequestDemo5 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求体
//1.获取字符流
BufferedReader reader = req.getReader();
//2.读取数据
String line = null;
while ((line = reader.readLine())!=null){
System.out.println(line);
}
}
}
通用方式:不论post还是get都能用
String getParameter(String name): 根据参数名称获取参数值
String[] getParametersValues(String name): 根据参数名称获取参数值的数组,一般用于复选框
Enumeration<String> getParameterNames(): 获取所有请求的参数名称
Map<String,String[]> getParameterMap():获取所有参数名和值的map集合
上面的name和value其实就是html标签体的name属性值和value属性值
中文乱码问题:
get方式:tomcat8已经解决中文乱码方式
post方式:会乱码,在获取参数前加一个代码:req.setCharacterEncoding("utf-8")
请求转发:一个Servlet接收请求然后发送给另一个Servlet处理
当一个Web资源收到客户端的请求后,如果希望服务器通知另外一个资源去处理请求,可以通过RequestDispatcher接口的实例对象来实现。在ServletRequest接口中定义了一个获取RequestDispatcher对象的方法。

获取了RequestDispatcher对象后,可以通过两个相关的方法通知其他的Servlet处理请求。

请求转发步骤
通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path);
使用RequestDispatcher对象来转发:forward(ServeletRequest req, ServletResponse resp);
@WebServlet("/rd7")
public class RequestDemo7 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("demo7被访问了。。。");
//获取转发对象并转发
// RequestDispatcher requestDispatcher = req.getRequestDispatcher("/rd8");
// requestDispatcher.forward(req,resp);
req.getRequestDispatcher("/rd8").forward(req,resp);
}
}
特点
指的是使用include()方法将Servlet请求转发给其他Web资源进行处理,与请求转发不同的是,在请求包含返回的响应消息中,既包含了当前Servlet的响应消息,也包含了其他Web资源所作出的响应消息。
/**
* 请求包含
* 它是把两个Servlet的响应内容合并输出。
* 注意:这种包含是动态包含。
* 动态包含的特点:各编译各的,只是最后合并输出。
*/
public class RequestDemo8 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("I am request demo8 ");
//1.拿到请求调度对象
RequestDispatcher rd = request.getRequestDispatcher("/RequestDemo9");
//2.实现包含的操作
rd.include(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
/**
* 被包含者
*/
public class RequestDemo9 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("include request demo 9 ");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
功能:设置响应消息
设置响应行:
设置状态码:setStatus(int sc) 。正常情况下,Web 服务器会默认产生一个状态码为200的状态行。
错误信息状态码:setError(int sc)。用于发送表示错误信息的状态码,例如,404状态码表示找不到客户端请求的资源。
sendError(int code, String message)。此重载方法除了发送状态码外,还可以增加一条用于提示说明的文本信息,该文本信息将出现在发送给客户端的正文内容中。
设置响应头:

addHeader()、setHeader()、addInt Header()、setIntHeader()方法都是用于设置各种头字段的,而setContentType()、setLocale()和setCharacterEncoding()方法用于设置字符编码,这些设置字符编码的方法可以有效解决乱码问题。
resp.setContentType("text/html");
resp.getWriter().print("<h1>hello</h1>");//会解析标签
resp.setContentType("text/plain");
resp.getWriter().print("<h1>hello</h1>");//纯文本,不会解析标签
//MIME类型:text/plain 纯文本、text/html HTML文档、text/xml
//application/x-msdownload 需要下载的资源
//image/jpeg image/gif ... 图片资源
设置响应体
由于在HTTP响应消息中,大量的数据都是通过响应消息体传递的,因此,ServletResponse遵循以IO流传递大量数据的设计理念。在发送响应消息体时,定义了两个与输出流相关的方法

代码实现
重定向的特点:redirect
转发的特点:forword
路径写法:
路径需不需要加虚拟目录
@WebServlet("/respDemo1")
public class RespDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter pw = resp.getWriter();
pw.println("<h1>hello world</h1>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
中文乱码问题:
在 HTTP 协议中,定义了一个Refresh头字段,它可以通知浏览器在指定的时间内自动刷新并跳转到其他页面。
@WebServlet("/refreshDemo")
public class RefreshDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//2秒后刷新并跳转百度
resp.setHeader("refresh","2;https://www.baidu.com");
}
@WebServlet("/refreshDemo")
public class RefreshDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//每3秒刷新一次本页面,常见于购物网站显示库存
resp.setHeader("refresh","3");
}
public class ResponseDemo3 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int width = 200;
int height = 35;
/**
* 实现步骤:
* 1.创建图像内存对象
* 2.拿到画笔
* 3.设置颜色,画矩形边框
* 4.设置颜色,填充矩形
* 5.设置颜色,画干扰线
* 6.设置颜色,画验证码
* 7.把内存图像输出到浏览器上
*/
//创建内存图像
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);//参数:宽度,高度 (指的都是像素),使用的格式(RGB)
Graphics g = image.getGraphics();//画笔就一根
//设置颜色
g.setColor(Color.BLUE);
//画边框
g.drawRect(0, 0, width, height);
//设置颜色
g.setColor(Color.GRAY);
//填充矩形
g.fillRect(1, 1, width-2, height-2);
//设置颜色
g.setColor(Color.WHITE);
//拿随机数对象
Random r = new Random();
//画干扰线 10条
for(int i=0;i<10;i++){
g.drawLine(r.nextInt(width), r.nextInt(height),r.nextInt(width), r.nextInt(height));
}
//设置颜色
g.setColor(Color.RED);
//改变字体大小
Font font = new Font("宋体", Font.BOLD,30);//参数:1字体名称。2.字体样式 3.字体大小
g.setFont(font);//设置字体
//画验证码 4个
int x = 35;//第一个数的横坐标是35像素
for(int i=0;i<4;i++){
//r.nextInt(10)+""这种写法效率是十分低的
g.drawString(String.valueOf(r.nextInt(10)), x, 25);
x+=35;
}
//输出到浏览器上
//参数: 1.内存对象。2.输出的图片格式。3.使用的输出流
ImageIO.write(image, "jpg", response.getOutputStream());
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
/**
* 设置缓存时间
* 使用缓存的一般都是静态资源
* 动态资源一般不能缓存。
* 我们现在目前只掌握了Servlet,所以用Servlet做演示
* @author 黑马程序员
* @Company http://www.itheima.com
*
*/
public class ResponseDemo4 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String str = "设置缓存时间";
/*
* 设置缓存时间,其实就是设置响应消息头:Expires 但是值是一个毫秒数。
* 使用的是
* response.setDateHeader();
*
* 缓存1小时,是在当前时间的毫秒数上加上1小时之后的毫秒值
*/
response.setDateHeader("Expires",System.currentTimeMillis()+1*60*60*1000);
response.setContentType("text/html;charset=UTF-8");
response.getOutputStream().write(str.getBytes());
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
public class ResponseDemo8 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/*
* 文件下载的思路:
* 1.获取文件路径
* 2.把文件读到字节输入流中
* 3.告知浏览器,以下载的方式打开(告知浏览器下载文件的MIME类型)
* 4.使用响应对象的字节输出流输出到浏览器上
*/
//1.获取文件路径(绝对路径)
ServletContext context = this.getServletContext();
String filePath = context.getRealPath("/uploads/6.jpg");//通过文件的虚拟路径,获取文件的绝对路径
//2.通过文件路径构建一个字节输入流
InputStream in = new FileInputStream(filePath);
//3.设置响应消息头
response.setHeader("Content-Type", "application/octet-stream");//注意下载的时候,设置响应正文的MIME类型,用application/octet-stream
response.setHeader("Content-Disposition", "attachment;filename=1.jpg");//告知浏览器以下载的方式打开
//4.使用响应对象的字节输出流输出
OutputStream out = response.getOutputStream();
int len = 0;
byte[] by = new byte[1024];
while((len = in.read(by)) != -1){
out.write(by, 0, len);
}
in.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
html页面设置:<meta charset="UTF-8">
get方式不用设置,tomcat8自动解决,post需要设置。request设置:req.setCharacterEncoding("UTF-8");
response设置:resp.setContentType("text/html;charset=UTF-8");
在jsp页面中也要设置:<%@page ContentType="text/html;charset=UTF-8"
web.xml除了可以配置Servlet,还有下面的常用配置
修改web应用默认首页
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>login.html</welcome-file>
<welcome-file>login.htm</welcome-file>
<welcome-file>login.jsp</welcome-file>
<welcome-file>error.html</welcome-file>
</welcome-file-list>
<!-- 可以设置多个默认首页,第一个不能访问就找第二个-->
Servlet通配符映射
<servlet>
<servlet-name>patternServlet</servlet-name>
<servlet-class>com.example.servlet1.patternServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>patternServlet</servlet-name>
<url-pattern>/pattern/*</url-pattern>
</servlet-mapping>
<!-- 通过上面的设置,只要在浏览器输入 虚拟目录/pattern/任意字符 就可以访问了。比如1号员工可以输入/pattern/1 来访问。-->
设置全局参数
<!-- 所谓的全局参数其实就是 ServletContext 中保存的信息 -->
<context-param>
<param-name>copyright</param-name>
<param-value>2004-2020 sogou.com 京ICP备11001839号-1</param-value>
</context-param>
<context-param>
<param-name>title</param-name>
<param-value>搜狗搜索</param-value>
</context-param>
设置404、500状态码的默认页面
tomcat默认的错误页面不友好,我们需要手动写几个错误页面,然后在web.xml中配置。这样当发生了错误时就会跳转到我们写好的错误页面。
404错误页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
404 NOT FOUND 请检查你的网址
</body>
</html>
web.xml配置
<error-page>
<error-code>404</error-code>
<location>/error/404.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.html</location>
</error-page>
可以通过 idea 的 export 将项目导出成一个 war 包,然后将此 war 包复制到 tomcat 的 webapp 目录下。然后双击 startup.bat 启动 tomcat 服务器,该项目就会自动部署。
原文:https://www.cnblogs.com/yellowchives/p/15202812.html