【Servlet教科书】JavaWeb技术之Servlet总结(详细记录知识)
文章目录
- @[toc]
- 一、初识Servlet
- 1.1 Servlet概念
- 1.2 Servlet的核心作用
- 1.3 Servlet核心目录结构
- 1.3 IDEA工具内创建核心目录结构
- 1.4 Servlet的开发步骤
- 1.4.1 开发步骤
- 1.4.2 web.xml配置文件添加配置信息
- 1.4.3 我们的第一个Servlet(MyServlet代码)
- 1.4.4 解决浏览器输出显示乱码问题
- 1.4.5 解决idea控制台乱码
- 1.5 Web中路径问题
- 二、Servlet的使用
- 2.1 Servlet核心接口和类
- 2.2 Servlet的两种配置方式
- 2.2.1 web.xml配置方式(支持所有版本)
- 2.2.2 注解类WebServlet配置方式(支持3.0版本以后)
- 2.2.3 IDEA中快捷创建Servlet类并自动添加注解
- 2.2.4 Servlet默认访问欢迎页面配置
- 2.2.5 Servlet访问状态码跳转页面设置
- 2.3 获取请求参数
- 2.4 处理获取请求参数乱码问题
- 2.5 处理响应后浏览器显示乱码问题
- 三、Servlet与JDBC集成的综合案例
- 四、页面跳转(重定向)
- 五、Servlet的生命周期
- 六、Sevlet线程安全问题
- 七、Servlet初始化参数及配置
文章目录
- @[toc]
- 一、初识Servlet
- 1.1 Servlet概念
- 1.2 Servlet的核心作用
- 1.3 Servlet核心目录结构
- 1.3 IDEA工具内创建核心目录结构
- 1.4 Servlet的开发步骤
- 1.4.1 开发步骤
- 1.4.2 web.xml配置文件添加配置信息
- 1.4.3 我们的第一个Servlet(MyServlet代码)
- 1.4.4 解决浏览器输出显示乱码问题
- 1.4.5 解决idea控制台乱码
- 1.5 Web中路径问题
- 二、Servlet的使用
- 2.1 Servlet核心接口和类
- 2.2 Servlet的两种配置方式
- 2.2.1 web.xml配置方式(支持所有版本)
- 2.2.2 注解类WebServlet配置方式(支持3.0版本以后)
- 2.2.3 IDEA中快捷创建Servlet类并自动添加注解
- 2.2.4 Servlet默认访问欢迎页面配置
- 2.2.5 Servlet访问状态码跳转页面设置
- 2.3 获取请求参数
- 2.4 处理获取请求参数乱码问题
- 2.5 处理响应后浏览器显示乱码问题
- 三、Servlet与JDBC集成的综合案例
- 四、页面跳转(重定向)
- 五、Servlet的生命周期
- 六、Sevlet线程安全问题
- 七、Servlet初始化参数及配置
一、初识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("你又被访问到了!哈哈!");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
2.2.3 IDEA中快捷创建Servlet类并自动添加注解
IDEA中为为我们提供了比较快捷的方式来创建一个Servlet类,并自动的添加了@WebServlet注解,而且还覆盖了doPost和doGet方法,如下操作
创建后类中显示如下内容,没有骗你吧。IDEA是不是很强大,很方便!
2.2.4 Servlet默认访问欢迎页面配置
大家都知道,如果在访问项目的默认路径时,会出现默认的访问页面的,默认访问的页面是index.html、index.htm、index.jsp。这个欢迎页面我们是可以手动干预的,可以利用配置来进行设置我们的欢迎页面显示!我把代码贴在下方,大家可以去试试!
<!--默认访问页面设置-->
<!--随便设置访问页面,按顺序默认访问,如果默认设置的页面都没有的话,就会出现404(找不到资源)-->
<welcome-file-list>
<welcome-file>欢迎.html</welcome-file>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
欢迎页面内的文件是顺序被默认访问的,也就是说如果没有欢迎.html,浏览器会默认访问第二个index.html,如果第二个也没有,那就访问第三个index.jsp,如果三个页面都不存在的话,那事情就大了,默认访问会找不到资源,这时我们的老朋友404就出现了!
这么想,如果客户端在访问的时候,资源不见了出现404显得过于生硬,感觉不太友好!这时候我们就可以使用<error-page>配置标签来进行错误页面友好提示的默认访问设置!也就是说如果客户端访问资源不存在,我们把客户端遇到的404问题,再跳转一个友好处理404页面的网页!怎么设置,那就看一下2.2.5的知识点吧!
2.2.5 Servlet访问状态码跳转页面设置
上面那个不友好显示的问题,我们可以再此处得到解决。友好的提示客户端,你的访问状态码对应的错误提示!可以加入如下配置:
<!--注意:我们也可以设置其他访问状态码比如500、302等的跳转页面-->
<!--访问状态码跳转页面设置-->
<error-page>
<!--访问状态码-->
<error-code>404</error-code>
<!--跳转页面路径-->
<location>/error/error404.html</location>
</error-page>
注意:跳转页面的访问路径我是在web文件夹中创建了一个error文件夹专门存放处理访问状态码后跳转页面(该项目中处理404的页面是error404.html)的仓库
2.3 获取请求参数
html页面代码如下:
注意:当不写method方法时,默认时get请求(如果对get/post请求不了解的,可参考HTTP教科书系列)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form method="post" action="/firstservlet/login">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
Servlet代码如下:
package com.mylifes1110.java.login;
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;
/**
* 获取请求参数并响应结果
*/
@WebServlet(name = "LoginServlet", value = "/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* 解决获取浏览器Post请求后在控制台中解码后的乱码问题
*/
request.setCharacterEncoding("utf-8");
/**
* 获取浏览器发送的请求
*/
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username : " + username + "\t" + "password : " + password);
/**
* 解决服务器响应浏览器后浏览器显示内容乱码问题
*/
response.setContentType("text/html;charset=utf-8");
/**
* 根据传来的用户名和密码响应浏览器登录成功或登录失败
*/
if (username.equals("ziph") && password.equals("123456")) {
response.getWriter().println("登录成功!");
} else {
response.getWriter().println("登录失败!");
}
}
}
2.4 处理获取请求参数乱码问题
2.4.1 表单中产生中文乱码的原因
乱码问题,实质上就是因为服务器和客户端交互之间编码不一致造成的,所以我们解决乱码问题肯定根据实质出发,把服务器和客户端交互过程中的编码统一不就好了吗,之后就按照此编码进行数据的传输与接收。
2.4.2 解决接收Get请求参数乱码问题
在tomact7版本及之前版本
客户端以UTF-8的编码传输数据到服务器端,而服务器端的request对象使用的是ISO-8859-1这个字符编码来接收数据,服务器和客户端沟通的编码不一致因此才会产生中文乱码的。解决办法:在接收到数据后,先获取request对象以ISO8859-1字符编码接收到的原始数据的字节数组,然后通过字节数组以指定的编码构建字符串,解决乱码问题。
Tomcat8的版本及之后Get基本就不会乱码了,因为服务器对url的编码格式可以进行自动转换
package com.mylifes1110.java.login;
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;
/**
* 处理接受Get请求乱码问题
* 注意:tomact7版本及以前才会出现,Tomact8已经做出优化!
*/
@WebServlet(name = "GetServlet", value = "/login")
public class GetServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取用户名
String username = request.getParameter("username");
username = new String(username.getBytes("ISO8859-1"), "UTF-8");
//获取密码
String password = request.getParameter("password");
System.out.println("username : " + username + "password : " + password);
}
}
2.4.3 解决接收Post请求参数乱码问题
由于客户端是以UTF-8字符编码将表单数据传输到服务器端的,因此服务器也需要设置以UTF-8字符编码进行接收,要想完成此操作,服务器可以直接使用从ServletRequest接口继承而来的"setCharacterEncoding(charset)"方法进行统一的编码设置。
package com.mylifes1110.java.login;
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;
/**
* 处理接收Post请求乱码问题
* 注意:此设置请求参数的编码格式对Get请求方式无效
*/
@WebServlet(name = "PostServlet")
public class PostServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//解决接收Post请求参数乱码问题————设置请求参数的编码格式(Get无效)
request.setCharacterEncoding("utf-8");
//获取用户名
String username = request.getParameter("username");
//获取密码
String password = request.getParameter("password");
System.out.println("username : " + username + "password : " + password);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
}
2.5 处理响应后浏览器显示乱码问题
2.5.1 响应后浏览器页面显示乱码的原因
浏览器识别不到返回的中文编码格式,就会默认使用GB2312。如果返回时UTF-8编码格式,由于编码格式的不同,以至于浏览器页面就会显示乱码。
2.5.2 解决响应后浏览器页面显示乱码问题
我们在响应给浏览器信息的时候,要设置浏览器接收响应的编码格式。如下:
response.setContentType("test/html;charset=utf-8");//内容类型;编码格式
response.setCharacterEncoding("utf-8");//输出一个完整的网页
三、Servlet与JDBC集成的综合案例
此案例引入了mysql、Druild连接池工具和apache的dbUtils工具等相关jar包,联合完成的案例小项目
目录结构如下:
db.properties配置文件
#连接设置
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf8
username=root
password=Mylifes1110
#初始化连接
initialSize=10
#最大连接数量
maxActive=30
#最小空闲连接
minIdle=5
#超时等待时间以毫秒为单位 1000毫秒等于1秒
maxWait=5000
数据库
create table user
(
id int auto_increment
primary key,
username char(20) not null,
password char(20) not null,
phone char(11) null
);
entity实体类
package com.mylifes1110.java.entity;
/**
* @author Ziph
*/
public class User {
private int id;
private String username;
private String password;
private String phone;
public User() {
}
public User(int id, String username, String password, String phone) {
this.id = id;
this.username = username;
this.password = password;
this.phone = phone;
}
public User(String username, String password, String phone) {
this.username = username;
this.password = password;
this.phone = phone;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
utils工具类
package com.mylifes1110.java.utils;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author Ziph
*/
public class DruidUtils {
private static DruidDataSource dataSource;
static {
Properties properties = new Properties();
InputStream resourceAsStream = DruidUtils.class.getClassLoader().getResourceAsStream("db.properties");
try {
properties.load(resourceAsStream);
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static DruidDataSource getDataSource() {
return dataSource;
}
}
UserDao接口
package com.mylifes1110.java.dao;
import com.mylifes1110.java.entity.User;
import java.util.List;
/**
* @author Ziph
*/
public interface UserDao {
/**
* 查询所有用户信息
*
* @return 返回用户信息的List集合
*/
List<User> getAllUser();
/**
* 添加用户信息
*
* @param user User
* @return number int
*/
int insert(User user);
}
UserDaoImpl实体类
package com.mylifes1110.java.dao.impl;
import com.mylifes1110.java.dao.UserDao;
import com.mylifes1110.java.entity.User;
import com.mylifes1110.java.utils.DruidUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.SQLException;
import java.util.List;
/**
* @author Ziph
*/
public class UserDaoImpl implements UserDao {
private QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
@Override
public List<User> getAllUser() {
try {
List<User> userList = queryRunner.query("select id, username, password, phone from user", new BeanListHandler<User>(User.class));
return userList;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public int insert(User user) {
try {
int insert = queryRunner.update("insert into user (username, password, phone) values (?, ?, ?)", user.getUsername(), user.getPassword(), user.getPhone());
return insert;
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
}
UserService接口
package com.mylifes1110.java.service;
import com.mylifes1110.java.entity.User;
import java.util.List;
/**
* @author Ziph
*/
public interface UserService {
/**
* 实现查询所用用户信息功能
*
* @return 返回用户信息的List集合
*/
List<User> inquireAll();
/**
* 实现添加用户信息功能
*
* @param user User
* @return 返回受影响行数
*/
int add(User user);
}
UserServiceImpl实体类
package com.mylifes1110.java.service.impl;
import com.mylifes1110.java.dao.UserDao;
import com.mylifes1110.java.dao.impl.UserDaoImpl;
import com.mylifes1110.java.entity.User;
import com.mylifes1110.java.service.UserService;
import java.util.List;
/**
* @author Ziph
*/
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public List<User> inquireAll() {
return userDao.getAllUser();
}
@Override
public int add(User user) {
return userDao.insert(user);
}
}
GetAllUserServlet
package com.mylifes1110.java.servlet;
import com.mylifes1110.java.entity.User;
import com.mylifes1110.java.service.UserService;
import com.mylifes1110.java.service.impl.UserServiceImpl;
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.io.PrintWriter;
import java.util.List;
/**
* @author Ziph
*/
@WebServlet(name = "GetAllUserServlet", value = "/getAll")
public class GetAllUserServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1.调用Service
response.setContentType("text/html;charset=utf-8");
UserService userService = new UserServiceImpl();
List<User> userList = userService.inquireAll();
PrintWriter printWriter = response.getWriter();
//页面
printWriter.println("<html>");
printWriter.println("<head>");
printWriter.println("<meta charset='utf-8' />");
printWriter.println("<title>查询所有</title>");
printWriter.println("<body>");
printWriter.println("<table border='1'>");
printWriter.println("<tr>");
printWriter.println("<th>编号</th>");
printWriter.println("<th>用户名</th>");
printWriter.println("<th>密码</th>");
printWriter.println("<th>手机号</th>");
printWriter.println("</tr>");
for (User user : userList) {
printWriter.println("<tr>");
printWriter.println("<td>");
printWriter.println(user.getId());
printWriter.println("</td>");
printWriter.println("<td>");
printWriter.println(user.getUsername());
printWriter.println("</td>");
printWriter.println("<td>");
printWriter.println(user.getPassword());
printWriter.println("</td>");
printWriter.println("<td>");
printWriter.println(user.getPhone());
printWriter.println("</td>");
printWriter.println("</tr>");
}
printWriter.println("</table>");
printWriter.println("</body>");
printWriter.println("</head>");
printWriter.println("</html>");
}
}
InsertUserServlet
package com.mylifes1110.java.servlet;
import com.mylifes1110.java.entity.User;
import com.mylifes1110.java.service.UserService;
import com.mylifes1110.java.service.impl.UserServiceImpl;
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.io.PrintWriter;
/**
* @author Ziph
*/
@WebServlet(name = "InsertUserServlet", value = "/insert")
public class InsertUserServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String phone = request.getParameter("phone");
PrintWriter printWriter = response.getWriter();
if (username.equals("") || username.trim().length() == 0) {
printWriter.println("用户名不能为空!");
return;
}
if (password.equals("") || password.trim().length() == 0) {
printWriter.println("密码不能为空!");
return;
}
User user = new User(username, password, phone);
UserService userService = new UserServiceImpl();
int result = userService.add(user);
if (result > 0) {
printWriter.println("新增成功!");
} else {
printWriter.println("新增失败!");
}
}
}
index.html页面(添加用户页面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加学生</title>
</head>
<body>
<form method="post" action="/sjdemo/insert">
<table>
<tr>
<td>用户名</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td>手机号</td>
<td><input type="text" name="phone"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
<td><input type="reset" value="重置"></td>
</tr>
</table>
</form>
</body>
</html>
四、页面跳转(重定向)
作为后台开发人员,我们大多时候都在接收处理用户请求,给予用户响应,为了方便操作,服务器软件将请求和响应封装成了request和response,此章节展示Java Web服务端控制页面跳转主要有两种:重定向和转发
4.1 重定向
重定向就是通过各种方法将网络请求重新定个方向转到其它位置。
4.1.1 重定向实现原理
客户浏览器发送http请求 - > web服务器接受后发送302状态码响应及对应新的location给客户浏览器 - > 客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址 - > 服务器根据此请求寻找资源并发送给客户。在这里location可以重定向到任意URL,既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器路径栏显示的是其重定向的路径,客户可以观察到地址的变化的。
4.1.2 重定向的特点
- 重定向是客户端行为。
- 重定向是浏览器做了至少两次的访问请求。
- 重定向浏览器地址改变。
- 重定向两次跳转之间传输的信息会丢失(request范围)。
- 重定向可以指向任何的资源,包括当前应用程序中的其他资源,同一个站点上的其他应用程序中的资源,其他站点的资源。
注意:传递给HttpServletResponse.sendRedirect 方法的相对URL以“/”开头,它是相对于整个WEB站点的根目录
4.1.3 利用重定向实现页面跳转
在我们jdbc和Servlet集成综合案例中的添加用户功能,响应浏览器“添加成功”和“添加失败”改为重定向为提示页面,来实现反馈给用户提示信息!
重定向核心代码:response.sendRedirect("/项目名称/XXX.html");
项目中代码:response.sendRedirect("/sjdemo/insertok.html");
<!-- 页面1 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>帅哥美女,你新增成功了!</h1>
</body>
</html>
<!-- 页面2 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>对不起,您新增失败了,很遗憾!</h1>
</body>
</html>
package com.mylifes1110.java.servlet;
import com.mylifes1110.java.entity.User;
import com.mylifes1110.java.service.UserService;
import com.mylifes1110.java.service.impl.UserServiceImpl;
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.io.PrintWriter;
/**
* @author Ziph
*/
@WebServlet(name = "InsertUserServlet", value = "/insert")
public class InsertUserServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String phone = request.getParameter("phone");
PrintWriter printWriter = response.getWriter();
if (username.equals("") || username.trim().length() == 0) {
printWriter.println("用户名不能为空!");
return;
}
if (password.equals("") || password.trim().length() == 0) {
printWriter.println("密码不能为空!");
return;
}
User user = new User(username, password, phone);
UserService userService = new UserServiceImpl();
int result = userService.add(user);
if (result > 0) {
/**
* 重定向
*/
response.sendRedirect("/sjdemo/insertok.html");
} else {
response.sendRedirect("/sjdemo/inserterror.html");
}
}
}
4.2 请求转发
Servlet除了支持重定向之外还支持请求转发
4.2.1 请求转发实现原理
客户浏览器发送http请求 - > web服务器接受此请求 - > 调用内部的一个方法在容器内部完成请求处理和转发动作 - > 将目标资源发送给客户。在这里,转发的路径必须是同一个web容器下的url,其不能转向到其他的web路径上去,中间传递的是自己的容器内的request。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。
4.2.2 请求转发的特点
- 转发是服务器行为
- 转发是浏览器只做了一次访问请求
- 转发浏览器地址不变
- 转发两次跳转之间传输的信息不会丢失,所以可以通过request进行数据的传递
- 转发只能将请求转发给同一个WEB应用中的组件
注意:如果创建RequestDispatcher 对象时指定的相对URL以“/”开头,它是相对于当前WEB应用程序的根目录。
4.2.3 利用请求转发实现功能
再次利用我们Servlet与JDBC集成的综合案例,当添加用户后直接请求转发到查询所有用户的界面!
请求转发核心代码:request.getRequestDispatcher("/项目资源名称").forward(request, response);
项目中代码:request.getRequestDispatcher("/getAll").forward(request, response);
package com.mylifes1110.java.servlet;
import com.mylifes1110.java.entity.User;
import com.mylifes1110.java.service.UserService;
import com.mylifes1110.java.service.impl.UserServiceImpl;
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.io.PrintWriter;
/**
* @author Ziph
*/
@WebServlet(name = "InsertUserServlet", value = "/insert")
public class InsertUserServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String phone = request.getParameter("phone");
PrintWriter printWriter = response.getWriter();
if (username.equals("") || username.trim().length() == 0) {
printWriter.println("用户名不能为空!");
return;
}
if (password.equals("") || password.trim().length() == 0) {
printWriter.println("密码不能为空!");
return;
}
User user = new User(username, password, phone);
UserService userService = new UserServiceImpl();
int result = userService.add(user);
if (result > 0) {
/**
* 利用转发添加成功后直接查询所有用户信息
* 注意:转发必须是同一项目资源下,所以路径写 /servlet资源名称 (/getAll)
*/
request.getRequestDispatcher("/getAll").forward(request, response);
} else {
/**
* 重定向
*/
response.sendRedirect("/sjdemo/inserterror.html");
}
}
}
五、Servlet的生命周期
5.1 什么是生命周期?
生命周期就是指一个对象的生老病死。 拿产品来做例子说明,就是包括制造产品所需要的原材料的采集、加工等生产过程,也包括产品贮存、运输等流通过程,还包括产品的使用过程以及产品报废或处置等废弃回到自然过程,这个过程构成了一个完整的产品的生命周期。
5.2 Servlet生命周期的四个阶段
先说一下从调用自身构造器开始Servlet的六个阶段: 实例化(调用构造器) 、初始化 、获取配置信息 、提供服务 、返回有关信息 、销毁 。
其实它们都是整个生命周期中的一员,但是我们选取重要的生命周期成员就有四个阶段:实例化(调用构造器) 、初始化 、提供服务 、销毁 。
阶段一、实例化(调用构造方法)
实例化阶段是Servlet生命周期中的第一步,由Servlet容器调用Servlet的构造器创建一个具体的Servlet对象的过程。而这个创建的时机可以是在容器收到针对这个组件的请求之后,即用了才创建;也可以在容器启动之后立刻创建实例,而不管此时Servlet是否使用的上。使用如下代码可以设置Servlet是否在服务器启动时就执行创建
<load-on-startup>1</load-on-startup> //影响创建的时机
阶段二、初始化(init方法)
Servlet在被加载实例化之后,必须要初始化它。在初始化阶段,init()方法会被调用。这个方法在javax.servlet.Servlet接口中定义。其中,方法以一个ServletConfig类型的对象作为参数。ServletConfig对象由Servlet引擎负责创建,从中可以读取到事先在web.xml文件中通过<init-param>节点配置的多个name-value名值对。ServletConfig对象还可以让Servlet接受一个ServletContext对象。
一般情况下,init方法不需要编写,因GenericServlet已经提供了init方法的实现,并且提供了getServletConfig方法来获得ServletConfig对象。
注意:init方法只被执行一次
阶段三、提供服务/就绪(service方法)
Servlet被初始化以后就处于能够响应请求的就绪状态。每个对Servlet的请求由一个ServletRequest对象代表,Servlet给客户端的响应由一个ServletResponse对象代表。当客户端有一个请求时,容器就会将请求与响应对象转给Servlet,以参数的形式传给service方法。service方法由javax.servlet.Servlet定义,由具体的Servlet实现,而HttpServlet将service方法拆分了成我们常用的doGet和doPost方法
阶段四、销毁(destroy方法)
Servlet容器在销毁Servlet对象时会调用destroy方法来释放资源。通常情况下Servlet容器停止或者重新启动都会引起销毁Servlet对象的动作,但除此之外,Servlet容器也有自身管理Servlet对象的准则,整个生命周期并不需要人为进行干预
5.2.1 HttpServlet生命周期演示
package com.mylifes1110.java.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Ziph
*/
@WebServlet(name = "HttpServlet", value = "/test")
public class HttpServlet extends javax.servlet.http.HttpServlet {
/**
* 默认无参构造器
*/
public HttpServlet() {
System.out.println("1.调用无参构造器");
}
/**
* 调用init方法进行初始化
*/
@Override
public void init() throws ServletException {
super.init();
System.out.println("2.进行初始化工作");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
System.out.println("3.提供服务中...");
response.getWriter().append("Servlet at:").append(request.getContextPath());
}
/**
* 调用destroy方法进行销毁
*/
@Override
public void destroy() {
super.destroy();
System.out.println("4.进行销毁");
}
}
六、Sevlet线程安全问题
6.1 线程安全问题
一个Web容器中只创建一个Servlet对象(单例设计模式),因为每次请求都会创建一个线程,如果多人并发请求,那么就会存在多个线程操作同一个Servlet对象,那么如果在对应的方法中操作了成员变量,就会可能产生线程安全问题!
6.2 如何保证线程安全问题
如何保证线程安全问题有三种方法:加synchronized同步代码块(锁) 、实现SingleThreadModle接口 、使用局部变量 。
package com.mylifes1110.java.servlet;
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;
@WebServlet(name = "SafeServlet", value = "/safe")
public class SafeServlet extends HttpServlet {
int num = 0;
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
num++;
System.out.println("当前访问次数为:" + num);
}
}
问题: 会出现多个线程发出请求,访问的是同一个Servlet对象,也就是说num值访问的是同一个,它会一直做自增1操作
注意: 多个线程发出请求正常应该是访问多个Servlet对象,也就是一个线程对应一个num值来自增1记录该线程的访问次数
6.2.1 加synchronized同步代码块(锁)
加synchronized同步代码块(锁)来保证线程安全
6.2.2 实现SingleThreadModle接口
实现SingleThreadModle接口来保证线程安全
6.2.3 使用局部变量
在Servlet中尽量使用局部变量来保证线程安全
注意: 虽然是用的同一个Servlet对象,但是方法中的局部变量不是公用的,而是每个线程访问该方法的局部变量独一份
package com.mylifes1110.java.testservlet;
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;
@WebServlet(name = "SafeServlet2", value = "/safe2")
public class SafeServlet2 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* 测试方法中的线程
*/
System.out.println(Thread.currentThread().getId() + "已启动!");
int num = 0;
num++;
System.out.println("被访问的次数为:" + num);
}
}
七、Servlet初始化参数及配置
7.1 什么是Servlet初始化参数
Servlet初始化参数是Servlet调用init初始化方法同时加载的参数
7.2 了解Servlet初始化参数机制
既然是了解初始化机制,我们就必须去翻看源码了!翻看源码时,我们发现init()初始化方法是写在GenericServlet类中的,而且有两个一个是带参数的,一个是不带参数的。
private transient ServletConfig config;//源码中声明的ServletConfig对象是空的
看到重载的方法中传入了一个ServletConfig对象,然后内部处理初始化了该参数this.config = config,既然初始化了该参数,我们就可以使用getServletConfig方法去调用获得该初始化配置对象了!
我们有没有一个疑问呢?是谁传给init方法中的ServletConfig对象呢?仔细想一下,我们可以想到是tomact容器调用init初始化方法传入的ServletConfig对象,所以如果我们想要用get方法去获取该初始化配置参数,就需要我们去配置初始化参数了!那就继续看看是如何配置初始化参数的吧!
7.3 Servlet初始化参数配置方式
Servlet初始化参数配置方式有两种:xml配置文件配置 、注解配置 。
7.3.1 xml配置文件配置初始化参数
配置参数如下:
init-param元素用来定义Servlet启动的参数,可以定义多个
param-name表示参数名称
param-value表示参数值
献上xml配置文件配置信息,如下:
<servlet>
<servlet-name>init1</servlet-name>
<servlet-class>com.mylifes1110.java.testservlet.InitParamServlet1</servlet-class>
<!-- 可以配置多个init-param(初始化参数) -->
<init-param>
<param-name>username</param-name>
<param-value>Ziph</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>init1</servlet-name>
<url-pattern>/init1</url-pattern>
</servlet-mapping>
</web-app>
Java代码如下:
package com.mylifes1110.java.testservlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class InitParamServlet1 extends HttpServlet{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* 初始化参数获取当前相关所有配置信息
* 注意:因为是继承GenericServlet类得到的getServletConfig方法,可以直接this获取
*/
ServletConfig servletConfig = this.getServletConfig();
String username = servletConfig.getInitParameter("username");
String password = servletConfig.getInitParameter("password");
/**
* 控制台打印结果:username : Ziph password : 123456
*/
System.out.println("username : " + username + "\t" + "password : " + password);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
7.3.2 使用注解配置初始化参数
配置参数如下:
initParams:配置Servlet的初始化参数
注意: 在注解中的initParams默认是一个空数组,而数组中传入的参数即为:@WebInitParam(),再看@WebInitParam()中需要传入的参数即为:name和value(需要传入字符串类型)
注解写法参考如下:
@WebServlet(name = "InitParamServlet", value = "/init", initParams = {@WebInitParam(name = "username", value = "Ziph"), @WebInitParam(name = "password", value = "123456")})
完整Java代码如下:
package com.mylifes1110.java.testservlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "InitParamServlet", value = "/init", initParams = {@WebInitParam(name = "username", value = "Ziph"), @WebInitParam(name = "password", value = "123456")})
public class InitParamServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletConfig servletConfig = this.getServletConfig();
String username = servletConfig.getInitParameter("username");
String password = servletConfig.getInitParameter("password");
/**
* 打印结果为:username : Ziph password : 123456
*/
System.out.println("username : " + username + "\t" + "password : " + password);
}
}
Servlet内容较多,所以把状态管理内容放在了下一篇【JavaWeb之Servlet状态管理】,下一章内容概括Cookei、Session和ServletContext对象