一、初识Servlet
1.1 Servlet概念
Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。 简单来说,Servlet是服务器端的一段程序(代码、功能实现),可交互式的处理客户端发送到服务器的请求,并完成操作响应。并支持动态网页技术。JavaWeb程序开发的基础,JavaEE规范(一套接口)的一个组成部分。它是由服务器厂商实现的。
1.2 Servlet的核心作用
- 接受客户端请求,完成操作任务
- 动态生成网页(页面数据可变)
- 将包含操作结果的动态网页响应给客户端
1.3 Servlet核心目录结构
—web :存放需要部署的网站项目
——WEB-INF :核心内容,分别是以下内容
———classes :存放.class文件(XxxServlet.class)
———lib :储存所需jar包
———web.xml :web配置文件
——index.html/index.jsp.index.css/images等
见idea目录结构如下图: (因为idea会自动处理部署的文件并打包成war包的形式储存在out文件中,所以我们在使用IDEA时不用自己创建classes文件)
1.3 IDEA工具内创建核心目录结构
因为我们使用的是idea,如果去项目目录创建该Servlet目录结构过于繁琐,所以我们可以使用idea在工具内创建目录结构(可以在配置tomact时提前创建好都是OK的!)
1.4 Servlet的开发步骤
1.4.1 开发步骤
- 搭建开发环境,并创建Servlet核心目录结构
- 实现javax.serlvet.Servlet接口,覆盖5个方法(我在1.4.3里的每个方法上做了详细的注释)
- 在核心的servlet()方法中书写输出语句,验证访问结果
- 将编译后的.class文件放置在WEB-INF/classes中
- web.xml文件中添加配置信息
Java web工程下的web就是工程的发布文件夹,发布时会把该文件夹发布到idea项目下的out文件夹里artifacts。开发时classes文件存放路径为out文件夹下production文件夹下
鉴于我们使用的是IDEA集成开发工具,会自动处理文件放在idea并放在production文件夹内;而Eclipse不同,他会自动处理文件并放在tomact容器中的指定文件夹内,所以开发步骤作为了解,但是也可以自己动手试试!(后面我贴了一张图,可以去看一下!至于关于war包的情况不了解,可以去翻看tomact教科书系列文章)
在使用DOS命令行去编译执行Web项目时,我们需要将Servlet相关的jar包 完整路径\lib\servlet-api.jar 配置到CLASSPATH中。)
如果配置了环境还出现引用包的问题,那我们就可以选择带包编译或者不带包编译了。如下:
带包编译:javac -d . -classpath D:\tomcat\apache-tomcat-8.5.45\lib\servlet-api.jar MyServlet.java
不带包编译: javac -classpath D:\apache-tomcat-7.0.42\lib\servlet-api.jar MyServlet.java
1.4.2 web.xml配置文件添加配置信息
解释放在了配置信息中,你们看注释即可理解!
<?xml version="1.0" encoding="UTF-8"?>
<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_4_0.xsd"
version="4.0">
<!--下面两个标签,写在web-app标签内-->
<!--Servlet配置-->
<servlet>
<!--Servlet的全称类名,通过名称找到对应的Servlet,因为的配置文件中可能存在很多Servlet,他需要一个可识别的名称标签-->
<servlet-name>myservlet</servlet-name>
<!--访问实际的类,这里需要写全限定名-->
<servlet-class>com.mylifes1110.java.MyServlet</servlet-class>
</servlet>
<!--映射配置 -->
<servlet-mapping>
<!--同上,Servlet名称-->
<servlet-name>myservlet</servlet-name>
<!--URL路径访问名称,比如:localhost:8080/firstservlet/test(这里访问就需要在地址栏上假如test)-->
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app>
1.4.3 我们的第一个Servlet(MyServlet代码)
实现javax.serlvet.Servlet接口,覆盖5个方法,标有详细注释
package com.mylifes1110.java;
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
/**
* Servlet
* 实现Servlet接口中的所有方法
*/
public class MyServlet implements Servlet {
/**
* 实例化(调用构造器,可以省略,但是要知道)
*/
public MyServlet() {
//默认无参构造
}
/**
* 初始化方法
*
* @param servletConfig 包含 servlet 的配置和初始化参数的 ServletConfig 对象
* @throws ServletException 如果发生妨碍 servlet 正常操作的异常
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//Servlet初始化工作
}
/**
* 获取Servlet配置信息
*
* @return 返回 ServletConfig 对象,该对象包含此 servlet 的初始化和启动参数。返回的 ServletConfig 对象是传递给 init 方法的对象。
*/
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 提供服务
* <p>
* 由 servlet 容器调用,以允许 servlet 响应某个请求。
* 此方法仅在 servlet 的 init() 方法成功完成之后调用。
* 应该为抛出或发送错误的 servlet 设置响应的状态代码。
*
* @param servletRequest 包含客户端请求的 ServletRequest 对象
* @param servletResponse 包含 servlet 的响应的 ServletResponse 对象
* @throws ServletException 如果发生妨碍 servlet 正常操作的异常
* @throws IOException 果发生输入或输出异常
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//请求相关内容 ServletRequest ; 相应相关内容 ServletResponse
/**
* 在控制台内打印输出
*/
System.out.println("这是我的第一个Servlet!");
/**
* 利用输出流输出系统时间,在浏览器中显示
*/
PrintWriter printWriter = servletResponse.getWriter();
Date date = new Date();
printWriter.println(date);
printWriter.close();
/**
* 利用流输出信息在浏览器内显示
* 解决浏览器显示乱码问题
*/
servletResponse.setContentType("text/html;charset=utf-8");
servletResponse.getWriter().println("这是我的第一个Servlet");
}
/**
* 返回有关 servlet 的信息,比如作者、版本和版权。
* <p>
* 此方法返回的字符串应该是纯文本,不应该是任何种类的标记(比如 HTML、XML,等等)。
*
* @return 包含 servlet 信息的 String
*/
@Override
public String getServletInfo() {
return null;
}
/**
* 销毁(清除所有资源)
* <p>
* 由 servlet 容器调用,指示将从服务中取出该 servlet。
* 此方法仅在 servlet 的 service 方法已退出或者在过了超时期之后调用一次。
* 在调用此方法之后,servlet 容器不会再对此 servlet 调用 service 方法。
* 此方法为 servlet 提供了一个清除持有的所有资源(比如内存、文件句柄和线程)的机会,并确保任何持久状态都与内存中该 servlet 的当前状态保持同步。
*/
@Override
public void destroy() {
}
}
地址栏输入:http://localhost:8080/firstservlet/test (firstservlet是项目资源名称、test是Servlet名称)
1.4.4 解决浏览器输出显示乱码问题
/**
* 利用流输出信息在浏览器内显示
* 解决浏览器显示乱码问题
*/
servletResponse.setContentType("text/html;charset=utf-8");
servletResponse.getWriter().println("这是我的第一个Servlet");
1.4.5 解决idea控制台乱码
如果还解决不了,参考文章有多种解决方法:https://blog.csdn.net/weixin_44170221/article/details/105478788
-Dfile.encoding=utf-8
1.5 Web中路径问题
1.5.1 路径分类
1.5.2 路径分析
绝对路径 :用在不同网站之间跳转,比如:http://www.baidu.com.image/sky.png
相对路径 :用在同一网站中,比如:image/1.jpg,仅限静态资源,如果页面比较多,并且使用框架,会出现混乱
根路径 :根指定就是主机名(服务器)
比如:/servletdemo/loginservlet,如果在浏览器中,/ 表示主机名http://localhost:8080/
比如:/loginservlet,如果在服务器中,/ 表示项目路径/servletdemo
二、Servlet的使用
2.1 Servlet核心接口和类
关于使用Servlet有三种方法: 实现Servlet接口、继承GenericServlet 抽象类、继承HttpServlet抽象类
2.1.1 实现Servlet接口(繁琐)
关于实现Servlet接口覆盖其所有方法,其实在上面的1.4.3章节中已经写过了,但是我们现在再写一遍!
package com.mylifes1110.java;
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
/**
* Servlet
* 实现Servlet接口中的所有方法
*/
public class MyServlet implements Servlet {
/**
* 初始化方法
*
* @param servletConfig 包含 servlet 的配置和初始化参数的 ServletConfig 对象
* @throws ServletException 如果发生妨碍 servlet 正常操作的异常
*/
@Override
public void init(ServletConfig servletConfig) throws ServletException {
//Servlet初始化工作
}
/**
* 获取Servlet配置信息
*
* @return 返回 ServletConfig 对象,该对象包含此 servlet 的初始化和启动参数。返回的 ServletConfig 对象是传递给 init 方法的对象。
*/
@Override
public ServletConfig getServletConfig() {
return null;
}
/**
* 提供服务
* <p>
* 由 servlet 容器调用,以允许 servlet 响应某个请求。
* 此方法仅在 servlet 的 init() 方法成功完成之后调用。
* 应该为抛出或发送错误的 servlet 设置响应的状态代码。
*
* @param servletRequest 包含客户端请求的 ServletRequest 对象
* @param servletResponse 包含 servlet 的响应的 ServletResponse 对象
* @throws ServletException 如果发生妨碍 servlet 正常操作的异常
* @throws IOException 果发生输入或输出异常
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
//请求相关内容 ServletRequest ; 相应相关内容 ServletResponse
/**
* 在控制台内打印输出
*/
System.out.println("这是我的第一个Servlet!");
/**
* 利用输出流输出系统时间,在浏览器中显示
*/
PrintWriter printWriter = servletResponse.getWriter();
Date date = new Date();
printWriter.println(date);
printWriter.close();
/**
* 利用流输出信息在浏览器内显示
* 解决浏览器显示乱码问题
*/
servletResponse.setContentType("text/html;charset=utf-8");
servletResponse.getWriter().println("这是我的第一个Servlet");
}
/**
* 返回有关 servlet 的信息,比如作者、版本和版权。
* <p>
* 此方法返回的字符串应该是纯文本,不应该是任何种类的标记(比如 HTML、XML,等等)。
*
* @return 包含 servlet 信息的 String
*/
@Override
public String getServletInfo() {
return null;
}
/**
* 销毁(清除所有资源)
* <p>
* 由 servlet 容器调用,指示将从服务中取出该 servlet。
* 此方法仅在 servlet 的 service 方法已退出或者在过了超时期之后调用一次。
* 在调用此方法之后,servlet 容器不会再对此 servlet 调用 service 方法。
* 此方法为 servlet 提供了一个清除持有的所有资源(比如内存、文件句柄和线程)的机会,并确保任何持久状态都与内存中该 servlet 的当前状态保持同步。
*/
@Override
public void destroy() {
}
}
我们看到了实现Servlet接口的方法来使用Servlet。其中我们可以看到5个方法,分别是初始化、获取配置、提供服务、返回信息以及销毁。而我们有没有发现5个方法中有对我们来说有不是必须全部写在里面的方法呢?或者是可以优化,把某个方法封装来实现复用呢?其实有的,5个方法中初始化和销毁我们可以封装一下,实现多个Servlet之间复用。而获取配置、返回信息感觉是没有必要的。所以我们引入了GenericServlet抽象类。那么继承这个抽象类有什么好处呢?会比我们实现Servlet接口更简单化吗?继续向下看吧那就!
2.1.2 继承GenericServlet 抽象类(无协议)
GenericServlet的内部也实现了Servlet接口,并重写了初始化、获取配置、返回信息、销毁方法,有没有发现少了一个提供服务呢?它的做法很聪明,重写了我们想要简化的那四个方法并返回了默认没有的值(空值等),这就相当于我们实现了Servlet接口,并没有给他们编写方法体代码而已。那么少的那个提供服务,它做了什么呢?它的做法是把提供服务的方法用abstract修饰成了抽象类,所以我们继承这个类必须覆盖该抽象方法。
以上所述,GenericServlet 使编写 Servlet 变得更容易。它提供生命周期方法 init 和 destroy 的简单实现,要编写一般的 Servlet,只需重写抽象 service 方法提供服务即可。 (那么这个代码就简化了太多了,看代码吧!)
package com.mylifes1110.java;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* 继承GenericServlet抽象类并重写service方法提供服务
*/
public class MyServlet2 extends GenericServlet {
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("提供服务!");
}
}
那么我说的你们不信的话,我给你们再贴一下源码,你们可以看看,或者觉得透着些。
destroy销毁方法
getServletConfig获取配置方法(this.config在最上面声明是这样声明空值的:private transient ServletConfig config;)
getServletInfo返回信息方法
init初始化方法
然而主角一般都是最后登场,那就是service提供服务方法被写为抽象方法
这样来,我们不需要那4个方法时,就可以直接覆盖service方法去提供服务,假如需要某一个方法时,我们可以直接选择性的重写父类方法即可!然而小伙伴们有没有想过,GenericServlet只是定义了一般的、与协议无关的servlet,而编写基于Web上的HTTP协议下的servlet,所以GenericServlet与协议无关是不安全的,鉴于目前HTTP和HTTPS的广泛使用,市面上很少有Web不使用HTTP协议的。那么这个问题来的这么突然,我们怎么办呢?有没有更好的使用Servlet的方法又安全、又简单化呢?答案很明显,在上面我介绍了三种使用Servlet的方法,现在只介绍了两种,那么答案显而易见就是第三种方法了。于是,我们强大的HttpServlet抽象类登场了!(我发现主角总是最后登场的,哈哈!)
2.1.3 继承HttpServlet抽象类(有协议,推荐使用)
HttpServlet抽象类的内部继承了GenericServlet抽象类(证明HttpServlet有了上面我们说到的那四个方法),并扩展提供了适用于Web站点的HTTP协议的几个方法,也就是说HttpServlet的子类至少必须重写一个方法,该方法通常是如下这几个方法:
doGet
,如果 servlet 支持 HTTP GET 请求doPost
,用于 HTTP POST 请求doPut
,用于 HTTP PUT 请求doDelete
,用于 HTTP DELETE 请求init
和destroy
,用于管理 servlet 的生命周期内保存的资源getServletInfo
,servlet 使用它提供有关其自身的信息
package com.mylifes1110.java;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 继承HttpServlet抽象类
*/
public class MyServlet3 extends HttpServlet {
/**
* doGet HTTP中Get请求
* doPost HTTP中Post请求
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
/**
* 当接受参数时,我们不使用Get请求,就在内部调用doPost方法,这样传来参数后,就避免了使用Get请求方式(默认使用一个请求方式)
*/
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println("这是我的第一个Servlet!");
}
}
那我们也看一下源码是怎么实现的!
看到这里,我们上面说到,为什么没有理由去重写service方法呢,在这里我们去做解释!看源码!
你会发现这个service方法参数是父类请求、响应的参数。而传入参数后做了强转为子类参数进行使用(因为父类参数是不可以使用的,我们使用的是关于Http的参数),又调用了this.service方法,我们会想这不是自己调用自己陷进去一种死循环调用吗?然而源码内还提供了一个传入子类参数的service方法。如下:
在上面的那个service方法中this.service(request, response); 这里我们调用的是哪一个service方法呢?然后这里我们就会想到了JavaSE中的多态,一个传入父类参数的service,还有一个传入子类参数的service方法。至于选择哪一个,就看我们传入的参数了。它会精确匹配参数类型而选择传入哪一个service方法中。而这个传入子类参数的service方法内做了什么呢?看我标红的段落,传入请求参数后,调用Method方法,如果调用方法后得到的是GET,那就会调用doGet方法,而doGet方法就是上面我们说到的HTTP中的Get请求!举一反三,其他的Post、Delete等都是如此!
也许就有人会问Method方法是什么,那我正好就粘出来,你们就明白了!(返回一个字符串!)
2.2 Servlet的两种配置方式
Servlet的两种配置方式为web.xml配置方式和注解配置方式
2.2.1 web.xml配置方式(支持所有版本)
容器在进行url-pattern配置的时候是遵循一定的匹配原则的
url-pattern定义匹配规则,取值说明如下表:
url-pattern配置名称 | 配置说明 | 取值说明 |
---|---|---|
/具体名称 | 精确匹配 | 只有url路径是具体名称的时候才会触发Servlet |
*.xxx | 后缀匹配 | 只要是以xxx结尾的就匹配成功并触发Servlet |
/a/b/* | 目录匹配 | 访问时必须书写/a/b/*路径,*的话随便输入(必须以"/“开头,以”*"结尾) |
/* | 通配符匹配 | 匹配所有请求,包含服务器的所有资源 |
/ | 通配符匹配 | 匹配所有请求,包含服务器的所有资源,不包括 .jsp |
load-on-startup
- 元素标记容器是否应该在web应用程序启动的时候就加载触发Servlet初始化
- 它的值必须是一个整数,表示Servlet被加载的先后顺序
- 如果该元素的值是负数或者没有设置,则容器会当Servlet被请求时在加载
- 如果值为正整数或者0时,表示容器在应用程序启动时就加载并触发了Servlet的初始化。(值越小,Servlet的优先级越高,越先被加载。值相同时,容器自己选择顺序加载)
<?xml version="1.0" encoding="UTF-8"?>
<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_4_0.xsd"
version="4.0">
<!--Servlet配置-->
<servlet>
<!--Servlet属性名称-->
<servlet-name>myservlet</servlet-name>
<!--全限定名路径-->
<servlet-class>com.mylifes1110.java.MyServlet</servlet-class>
<!--启动时被加载并初始化-->
<load-on-startup>0</load-on-startup>
</servlet>
<!--映射配置-->
<servlet-mapping>
<!--Servlet属性名称-->
<servlet-name>myservlet</servlet-name>
<!--精确匹配-->
<url-pattern>/test</url-pattern>
<!--后缀匹配-->
<url-pattern>*.myservlet</url-pattern>
<!--通配符匹配-->
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
2.2.2 注解类WebServlet配置方式(支持3.0版本以后)
@WebServlet有4个属性我们目前可以使用分别是name、value、urlPatterns和loadOnStartup
注解属性 | 类型 | 属性说明 |
---|---|---|
name | String | 指定Servlet 的 name 属性,等价于 <servlet-name> 。如果没有显式指定,则该 Servlet 的取值即为类的全限定名 |
value | String[] | 配置url路径(idea为我们设置路径提供了两个属性,根据自己习惯去用) |
urlPatterns | String[] | 配置url路径,与value作用相同,不能同时使用 |
loadOnStartup | int | 配置Servlet的创建的时机, 如果是0或者正数启动程序时,则创建,如果是负数,则访问时创建 |
initParams | WebInitParam[] | 指定一组 Servlet 初始化参数,等价于<init-param> 标签。 |
asyncSupported | boolean | 声明 Servlet 是否支持异步操作模式,等价于<async-supported> 标签 |
description | String | 该 Servlet 的描述信息,等价于 <description> 标签 |
displayName | String | 该 Servlet 的显示名,通常配合工具使用,等价于 <display-name> 标签 |
package com.mylifes1110.java;
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;
/**
* value定义了多个url路径:value = {"/hi", "/hello", "*.haha"}
* Servlet的创建时机设置为:loadOnStartup = 0(启动程序时创建并初始化)
*/
@WebServlet(name = "HiServlet", value = {
"/hi", "/hello", "*.haha"}, loadOnStartup = 0)
public class HiServlet extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("初始化方法");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println