3-Servlet详解2

本文详细介绍了Servlet的访问方式,包括XML配置和注解方式,并深入讲解了Servlet的生命周期,包括init、service、destroy方法的执行时机。此外,文章还探讨了Tomcat如何通过反射机制创建Servlet对象,以及无参构造函数的调用顺序。最后,通过一个简单的TomcatTest类模拟了Tomcat创建Servlet对象的过程。
摘要由CSDN通过智能技术生成

3-Servlet详解2

一、访问方式

浏览器不能直接访问Servlet文件,只能通过映射的方式来间接访问 Servlet ,映射需要开发者手动配置,有两种配置方式。

  • 基础xml配置,先配置servlet,再配置servlet-mappping对应servlet

    <servlet>
        <servlet-name>MyServlet</servlet-name>
        <servlet-class>com.southwind.servlet.MyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyServlet</servlet-name>
        <url-pattern>/myservlet</url-pattern>
    </servlet-mapping>
    
  • 基于注解的方式,在自己写的继承Servlet类上面写@WebServlet("/myservlet"),格式@WebServlet(url)

    @WebServlet("/myservlet")
    public class MyServlet implements Servlet {
        
    }
    

上述两种配置方式结果完全一致,将url-myservletclass-MyServlet进行映射,即在浏览器地址栏中直接访问myservlet就可以直接映射到MyServlet这个文件。

二、Servlet的生命周期

单从对象角度想,肯定有创建、使用、回收的过程。从Java角度想,我们不需要去管理对象的回收过程,因为我们有垃圾回收机制(题外话:java的垃圾回收机制GC根据不同的算法去回收程序不使用的对象)。生命周期具体到我们的代码里面,就是我们继承Servet之后,重写的initservicedestory

package com.southwind.servlet;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import java.io.IOException;

/**
 * @author 田港
 * @version 1.0
 * @date 2021-03-26 14:01
 */
@WebServlet("/MyServlet2")
public class MyServlet2 implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("对Servlet完成初始化操作。。。");
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("执行了Servlet的业务方法。。。");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
        System.out.println("释放了Servlet对象");
    }
}

run一下,然后访问http://localhost:8080/MyServlet2,然后看看控制台输出什么
在这里插入图片描述
是的,他来了,他来了,他带着信息走来了。
当我们第一次通过浏览器访问链接时,init方法和service方法同时执行,那么我们再访问一次呢?
在这里插入图片描述
结果发现只有service这个方法被执行了,init方法没有被执行。为什么呢?这里的init方法才用的是单例设计模式(先记住),为了节省内存,当我们的MyServlet2没有被创建时,他会执行,当创建了,那么他就不执行了。而service方法随着我们不断地刷新页面会不断的被执行,每发送一次请求,service就会被执行一次,因为service是我们处理请求的方法。(当你需要打电话时,你需要一步手机,于是你可以完成你的第一次电话,那么当你再打电话时,你还会再买一部手机吗?就是这个道理,省钱,省资源。)
现在我们关闭这个程序,会发现什么呢?
在这里插入图片描述
程序调用了destory函数。这个程序没用了,该下岗的下岗,卸磨杀驴。(电话打完了,就可以挂电话了)

说到这来我们忽略了一个很重要的问题,那就是我们写了class,但是没有new一个对象啊,程序怎么运行方法的啊?

肯定有对象的,那么谁new了一个对象呢?是Tomcat容器给我们写的类new了一个对象,Tomcat通过反射机制来完成这个操作的,这就解释了我们在web.xml中需要写全类名

<servlet>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>com.southwind.servlet.MyServlet</servlet-class> <!-- 全类名 -->
</servlet>
<servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/myservlet</url-pattern>
</servlet-mapping>

Tomcat通过反射机制拿到我们写的类的构造函数,再通过构造函数再创建对象。我们来验证一下对象是怎么创建出来的。我们说通过反射机制来创建对象,反射机制创建对象一般调用的是类的无参构造函数,所以说我们在当前类中开头写一个无参构造函数。

public MyServlet2() {
    System.out.println("创建了Servlet对象");
}

这里想一想,MyServlet2()init()哪个先被执行。肯定是构造函数MyServlet2(),因为初始化(init)的前提是先有,才能初始化。构造函数也属于生命周期,但不属于我们的接口范围。刷新一下页面
在这里插入图片描述

三、题外知识点

当我们没有new一个对象时,不是代表对象不存在,是有人已经帮我们做好了。(题外话:当我们感觉快乐时,多想想是谁让我们快乐了,又是谁在背后为我们的快乐买单。)
Tomcat自动帮我们去创建对象,那么怎么创建的呢?Tomcat调用我们写的类的无参构造函数,那么怎么调用的呢,是通过反射机制去调用的。我们简单写一下过程。在src\com.southwind.servlet新建一个TomcatTest.class。里面写一个main方法,简单理解一下。
浏览器发情请求时,web.xml拿到url之后去解析,根据全类名找到我们的class。所以我们在main方法中定义一个String,赋值就是全类名。

package com.southwind.servlet;

/**
 * @author 田港
 * @version 1.0
 * @date 2021-03-26 15:56
 */
public class TomcatTest {
    public static void main(String[] args) {
        String str = "com.southwind.servlet.MyServlet2";
    }
}

接下呢,我们就需要通过反射机制来创建MyServlet2的一个对象。怎么创建呢,首先得拿到他的运行时类-Class,这个涉及到JVM虚拟机底层东西,我们简单了解一下。
在这里插入图片描述

当我们启动程序时,我们写的java文件会被加载到内存中,编译后的字节码文件会被放到虚拟机的方法区,起个名字叫做运行时类,这就是个模板,根据这个模板我们可以创造很多个对象,类是死的,对象是活的。程序运行依靠的是对象与对象之间的相互调用。
比如我们在源码中写出MyServlet2 myservlet2 = new MyServlet2();这句话时,就是写死了这个创建,而Tomcat不是这样,它是通过反射机制根据运行时类动态创建对象。代码如下

public class TomcatTest {
    public static void main(String[] args) {
        String str = "com.southwind.servlet.MyServlet2";
        // Class就是运行时类,Class.forName是它的静态方法,传入全类名就行,
        // 这里IDEA提示报错,处理异常,指针放到这一行,安装ctrl+alt+t,选择 try catch,自动补全代码
		// 不建议抛出异常,能自己处理就自己处理。
		// 不然抛出异常,最终最底层JVM虚拟机就会帮我们处理,这样JVM压力很多。是你的错误就不要甩锅!!
        Class clazz = Class.forName(str); 
    }
}

修改之后:

public class TomcatTest {
    public static void main(String[] args) {
        String str = "com.southwind.servlet.MyServlet2";
        try {
            Class clazz = Class.forName(str);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

下一步我们把他的无参构造函数取出来,并且是无参的:

public class TomcatTest {
    public static void main(String[] args) {
        String str = "com.southwind.servlet.MyServlet2";
        try {
            Class clazz = Class.forName(str);
            try {
                clazz.getConstructor(); // getConstructor就是他的构造器,不传参数就是无参构造
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

能不能合并这两个异常呢?可以的,写异常的父类就行(多态的体现)

public class TomcatTest {
    public static void main(String[] args) {
        String str = "com.southwind.servlet.MyServlet2";
        try {
            Class clazz = Class.forName(str);
            clazz.getConstructor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

现在我们拿到了这个构造函数,把他输出

public class TomcatTest {
    public static void main(String[] args) {
        String str = "com.southwind.servlet.MyServlet2";
        try {
            Class clazz = Class.forName(str);
            Constructor constructor = clazz.getConstructor();
            System.out.println(constructor);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

没有结果?没找到javax.servlet.Servlet?

我们这个是javaSE内部环境,加载不到servlet,所以没有结果。将外部Tomcat中的servlet-api.jar包导进来。在web文件夹下新建lib文件夹,将servlet-api.jar拷贝进去。
在这里插入图片描述

再次运行:找到了这个运行时类
在这里插入图片描述

复现与总结:

我们写的MyServlet2.实现了Servlet中的接口,但是ServletTomcat中的,所以我们不启动Tomcat就无法调用这个包,那么很简单,我们将整个包导入我们的JavaSE环境中就可以了。

现在我们拿到这个构造器了,接下来就是通过构造器创建对象了。

public class TomcatTest {
    public static void main(String[] args) {
        String str = "com.southwind.servlet.MyServlet2";
        try {
            Class clazz = Class.forName(str);
            Constructor constructor = clazz.getConstructor();
            // System.out.println(constructor);
            Object object = constructor.newInstance(); // 这里用Object来接受一下,实际上还是多态
            System.out.println(object);  // 输出
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

为什么会输出"创建了Servlet对象"这句话呢?因为我们代码中的constructor实际上就是运行时类的无参构造,我们在MyServlet2.java中重写了无参构造函数,输出这几话。再一次理解一下什么是面向对象吧!当前的constructor就是用来描述MyServlet2.java中的无参构造函数的,把程序中的方法看成一个对象,constructor.newInstance()就相当于在调用这个方法,相当于new这个东西。constructor.newInstance()调用这个构造方法,那么结果就是返回一个对象,对象就是"创建了Serlvet对象"下面那句输出,这也是反射机制的意思,反向动态创建。

这就是 Tomcat 帮我们完成的事情。

四、总结

  1. Servlet的生命周期
    • 当浏览器访问 Servlet 时,Tomcat 会查询当前 Servlet 的实例化对象是否存在,如果不存在,Tomcat 则通过反射机制动态创建对象,如果存在,直接执行第 3 步;
    • 调用 init 方法完成初始化操作;
    • 调用 service 方法完成业务逻辑操作;
    • 关闭 Tomcat 时,会调用 destory 方法,释放当前对象所占用的资源。
  2. Servlet的生命周期方法:无参构造函数、init、service、destory
    • 无参构造函数只调用一次,创建对象。
    • init 调用一次,初始化对象。
    • service 调用 N 次,执行业务方法。
    • destory 调用一次,用来卸载对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值