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-myservlet
与class-MyServlet
进行映射,即在浏览器地址栏中直接访问myservlet
就可以直接映射到MyServlet
这个文件。
二、Servlet的生命周期
单从对象角度想,肯定有创建、使用、回收的过程。从Java角度想,我们不需要去管理对象的回收过程,因为我们有垃圾回收机制(题外话:java的垃圾回收机制GC根据不同的算法去回收程序不使用的对象)。生命周期具体到我们的代码里面,就是我们继承Servet
之后,重写的init
,service
、destory
。
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
中的接口,但是Servlet
是Tomcat
中的,所以我们不启动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 帮我们完成的事情。
四、总结
- Servlet的生命周期
- 当浏览器访问 Servlet 时,Tomcat 会查询当前 Servlet 的实例化对象是否存在,如果不存在,Tomcat 则通过反射机制动态创建对象,如果存在,直接执行第 3 步;
- 调用 init 方法完成初始化操作;
- 调用 service 方法完成业务逻辑操作;
- 关闭 Tomcat 时,会调用 destory 方法,释放当前对象所占用的资源。
- Servlet的生命周期方法:无参构造函数、init、service、destory
- 无参构造函数只调用一次,创建对象。
- init 调用一次,初始化对象。
- service 调用 N 次,执行业务方法。
- destory 调用一次,用来卸载对象。