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() 方法则是在每次请求的时候调用。故过滤器可以对请求进行拦截过滤。可以用来进行权限设置,对传输数据进行加密等等操作。

 

posted @ 2021-01-31 10:28  邓维-java  阅读(2669)  评论(0)    收藏  举报