java-Servlet 详解
java-Servlet 详解
简介:
Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。使用 Servlet,可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。

Servlet 入门实例
第一步:创建一个JavaWeb项目,并创建一个servlet类-----HelloServlet,实现接口 Servlet
package com.ys.servlet;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class HelloServlet implements Servlet{
//只被调用一次,第一次请求Servlet时,创建Servlet的实例,调用构造器
public HelloServlet() {
System.out.println("构造器 HelloServelt()...");
}
//该方法用于初始化Servlet,就是把该Servlet装载入内存
//只被调用一次,在创建好实例后立即被调用
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("初始化方法 init()...");
}
//被多次调用,每次请求都会调用service方法。实际用于响应请求的
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("执行方法主体 service()...");
}
//只被调用一次,在当前Servlet所在的WEB应用被卸载前调用,用于释放当前Servlet所占用的资源
@Override
public void destroy() {
System.out.println("servlet 销毁时调用方法 destroy()...");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
}
第二步:在 web.xml 文件中配置上面创建的 HelloServlet 映射关系
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<!--在tomcat 服务器中运行时,如果不指名访问文件名,默认的根据项目名访问文件顺序如下配置 -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<!--给创建的 Servlet 配置映射关系 -->
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.ys.servlet.HelloServlet</servlet-class>
<!--servlet的完整名称-->
</servlet>
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<!-- 与上面配置的 servlet-name 名字要对应,一个servlet可以有多个 servlet-mapping -->
<url-pattern>/hello</url-pattern>
<!--访问路径-->
</servlet-mapping>
</web-app>
方式二、不使用web.xml,使用注解方式配置路由
/**
* 注解WebServlet用来描述一个Servlet
* 属性name描述Servlet的名字,可选
* 属性urlPatterns定义访问的URL,或者使用属性value定义访问的URL.(定义访问的URL是必选属性)
*/
@WebServlet(name="ServletDemo",urlPatterns="/ServletDemo")
public class Myservlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("Hello Servlet3.0");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
第三步:将项目部署在 tomcat 服务器,然后启动服务器


如果我们不断的刷新 http://localhost:8080/hello 这个访问链接,那么控制台如下:

Servlet 的生命周期
我们通过上面的实例,可以看到也就是只有第一次才会执行 构造器和 init() 方法,后面每次点击都只调用 service() 方法。那这是为什么呢?

上面这幅图可以这样理解:
- 1、客户端向 Web 服务器发送请求,服务器查询 web.xml 文件配置。根据请求信息找到对应的 Servlet。
- 2、Servlet 引擎检查是否已经装载并创建了该 Servlet 的实例对象,如果有,则直接执行第4步,否则执行第3步,
- 3、Web 服务器加载 Servlet,并调用 Servlet 构造器(只会调用一次),创建 Servlet 的实例对象。并调用 init() 方法,完成 Servlet 实例对象的初始化(只会调用一次)。
- 4、Web 服务器把接收到的 http 请求封装成 ServletRequest 对象,并创建一个 响应消息的 ServletResponse 对象,作为 service() 方法的参数传入。(每一次访问都会调用一次该方法)
- 5、执行 service() 方法,并将处理信息封装到 ServletResponse 对象中返回
- 6、浏览器拆除 ServletResponse 对象,形成 http 响应格式,返回给客户端。
- 7、Web 应用程序停止或者重新启动之前,Servlet 引擎将卸载 Servlet实例,并在卸载之前调用 destory() 方法
创建 Servlet 的三种方法
第一种:就是我们上面写的 实现 Servlet 接口
第二种:由于实现接口我们需要实现里面所有的方法,里面有一些方法我们可能并不想实现,那么我们就继承 GenericServlet 类
public class HelloServlet extends GenericServlet{
//只被调用一次,第一次请求Servlet时,创建Servlet的实例,调用构造器
public HelloServlet() {
System.out.println("构造器 HelloServelt()...");
}
//该方法用于初始化Servlet,就是把该Servlet装载入内存
//只被调用一次,在创建好实例后立即被调用
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("初始化方法 init()...");
}
//被多次调用,每次请求都会调用service方法。实际用于响应请求的
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
System.out.println("执行方法主体 service()...");
}
//只被调用一次,在当前Servlet所在的WEB应用被卸载前调用,用于释放当前Servlet所占用的资源
@Override
public void destroy() {
System.out.println("servlet 销毁时调用方法 destroy()...");
}
}
第三种:通常我们浏览器发出的请求都是 http 请求,那么请求方式可能有多种,比如 get,post,而我们在处理请求的时候都是在 service() 方法中,这种方式显然不够明确。那么我们通常是 继承 HttpServlet 类
public class HelloServlet extends HttpServlet{
//只被调用一次,第一次请求Servlet时,创建Servlet的实例,调用构造器
public HelloServlet() {
System.out.println("构造器 HelloServelt()...");
}
//该方法用于初始化Servlet,就是把该Servlet装载入内存
//只被调用一次,在创建好实例后立即被调用
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("初始化方法 init()...");
}
//处理 post 请求
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
//处理get请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
//只被调用一次,在当前Servlet所在的WEB应用被卸载前调用,用于释放当前Servlet所占用的资源
@Override
public void destroy() {
System.out.println("servlet 销毁时调用方法 destroy()...");
}
}
Servlet 的多线程问题
我们通过 Servlet 的生命周期可以知道,Servlet 类的构造器只会在第一次访问的时候调用,后面的请求都不会再重新创建 Servlet 实例。即 Servlet 是单例,那么既然是单例的,那就要注意多线程访问所造成的安全问题。如下:
public class HelloServlet extends GenericServlet{
//多线程共享资源
private int i = 0;
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
i++;
//为了使多线程访问安全问题更加突出,我们增加一个延时程序
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
我们用两个浏览器,输入 http://localhost:8080/hello,然后一起访问,不断刷新,结果如下:

结果分析:显然,我们用两个浏览器访问,便相当于两个线程,第一个访问,已经执行了 i++,但是还没来得及打印 i 的值,就马上就睡眠了;接着第二个浏览也来访问,执行 i++,那么i的值相当于增加加了两次1,然后这两个浏览器输出最终结果。这便造成了多线程访问共享资源造成冲突。那么如何解决多线程冲突呢?
第一种方法:使用同步代码块
public class HelloServlet extends GenericServlet{
//多线程共享资源
private int i = 0;
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
synchronized (this) {
i++;
//为了使多线程访问安全问题更加突出,我们增加一个延时程序
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
结果:

分析:这种办法虽然能解决多线程同步问题,但是如果 延时程序特别长,那么会造成访问假死的现象。即第一个线程访问结果没有出来,第二个线程就会一直卡死,出不来结果
第二种办法:实现接口 SingleThreadModel
public class HelloServlet extends GenericServlet implements SingleThreadModel{
//多线程共享资源
private int i = 0;
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
i++;
//为了使多线程访问安全问题更加突出,我们增加一个延时程序
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
分析:SingleThreadModel 接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。但是,如果一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销,在现在的Servlet开发中基本看不到SingleThreadModel的使用,这种方式了解即可,尽量避免使用。
第三种办法:避免使用实例变量
线程安全问题很大一部分是由于实例变量造成的,那么我们只要在 Servlet 里面不定义任何的实例变量,那么就不会有线程安全的问题。因为在 Java 内存模型中,方法中的临时变量是在栈上分配空间,而且每个线程都有自己的私有栈空间,不会造成线程安全问题。
public class HelloServlet extends GenericServlet{
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
int i = 0;
i++;
//为了使多线程访问安全问题更加突出,我们增加一个延时程序
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
Servlet 的转发和重定向
重定向:
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.sendRedirect("index.jsp");//重定向
}
转发:
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//response.sendRedirect("index.jsp");
request.getRequestDispatcher("/index.jsp").forward(request, response);//转发
本质区别:转发只发出了一次请求,而重定向发出了两次请求
①.转发:地址栏是初次发出请求的地址
重定向:地址栏不再是初次发出的请求地址,地址栏为最后响应的那个地址
②.转发:在最终的Servlet中,request对象和中转的那个request是同一个对象
重定向:在最终的Servlet中,request对象和中转的那个request不是同一个对象
③.转发:只能转发给当前WEB应用的资源
重定向:可以重定向到任何资源
response.sendRedirect("http://www.baidu.com");是可以的
转发就不行
④.转发:/ 代表的是当前WEB应用的根目录(http://localhost:8080/项目名称/)
重定向: / 代表的是当前WEB站点的根目录(http://localhost:8080/)
注意:这两条跳转语句不能同时出现在一个页面中,否则会报IllegalStateException - if the response was already committed
Servlet 的过滤器
①、什么是 过滤器?
JavaWEB 的一个重要组件,可以对发送到 Servlet 的请求进行拦截,并对响应也进行拦截
②、如何实现一个过滤器?
第一步:创建一个过滤器类,实现 Filter 接口
public class HelloFilter implements Filter{
public HelloFilter() {
System.out.println("构造器 HelloFilter()...");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init()...");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("doFilter()...");
}
@Override
public void destroy() {
System.out.println("destroy()...");
}
}
第二步:在 web.xml 文件中配置过滤器
<!--给创建的过滤器配置关系 -->
<filter>
<filter-name>helloFilter</filter-name>
<filter-class>com.ys.filter.HelloFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>helloFilter</filter-name>
<url-pattern>/*</url-pattern><!-- 这表示可以拦截任何请求 -->
</filter-mapping>
启动服务器:我们发现还没发送请求,过滤器的 构造方法和 init() 方法就已经开始运行了


每刷新一次,控制台都会打印 doFilter()...

总结: 生命周期和 Servlet 的类似。只不过其构造方法和初始化方法是在容器启动时就调用了。而其 doFilter() 方法则是在每次请求的时候调用。故过滤器可以对请求进行拦截过滤。可以用来进行权限设置,对传输数据进行加密等等操作。

浙公网安备 33010602011771号