[JavaWeb]在学习Servlet的过程中一个经典面试题
大家天天开心!
文章目录
- 前言
- 一、ServletConfig的简介图:
- 二、详细代码和解释:
- 1.示例代码:
- 2.详细解释:
- 深入解析:父类方法中的
this
指向 - 三、面试题:
- 1.this的指向:
- 2.为什么需要
super.init(config)
: - 3.错误实现(导致问题)
- 总结
前言
在学习ServletConfig这个接口的时候,突然发现了一个非常容易有陷阱,就是它其实是一层套着一层,下面我给大家简单的介绍一下这个陷阱同时也复习一下前面OOP的知识,我们要时时刻刻的知道我们是OOP程序员。
提示:以下是本篇文章正文内容,下面案例可供参考
一、ServletConfig的简介图:
二、详细代码和解释:
1.示例代码:
package com.hspedu.servlet;import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.util.concurrent.ConcurrentHashMap;/*** @author 韩顺平* @version 1.0*/
public class DBServlet extends HttpServlet {/*** 老师梳理ServletConfig config 使用流程* 1. 当DBServlet对象初始化时, tomcat会同时创建一个 ServletConfig对象* 2. 这时如果DBServlet init() 方法中你调用 super.init(config);* 3. 调用 父类 GenericServlet* public void init(ServletConfig config) throws ServletException {* this.config = config;* this.init();* }* 这时就会把 Tomcat创建的 ServletConfig对象赋给 GenericServlet的属性 config* 4. 因此如果你重写init()方法,记住如果你想在其它方法通过 getServletConfig() 方法获取ServletConfig* , 则一定要记住 调用 super.init(config);* @param config* @throws ServletException*/@Overridepublic void init(ServletConfig config) throws ServletException {//ConcurrentHashMap, 是一个线程安全的容器.System.out.println("init" + config);super.init(config);}protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {//在DBServlet 执行 doGet()/doPost() 时,可以获取到web.xml配置的用户名和密码//你是一个OOP程序员->现有的方法或对象来搞定//DBServlet的父类GenericServlet有getServletConfig()/*** 老韩解读* 1. getServletConfig() 方法是 GenericServlet* 2. 返回的 servletConfig对象是 GenericServlet private transient ServletConfig config;* 3. 当一个属性被 transient 修饰,表示该属性不会被串行化(有些重要信息,不希望保存到文件)*/ServletConfig servletConfig = getServletConfig();System.out.println("doPost=" + servletConfig);String username = servletConfig.getInitParameter("username");String pwd = servletConfig.getInitParameter("pwd");System.out.println("初始化参数username= " + username);System.out.println("初始化参数pwd= " + pwd);}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {doPost(request, response);}
}
2.详细解释:
DBServlet 未重写 getServletConfig()
ServletConfig config = this.getServletConfig();
实际调用 GenericServlet.getServletConfig()。
动态绑定过程:
-
编译时类型:
DBServlet
(this
的声明类型) -
运行时类型:
DBServlet
(实际对象类型) -
方法查找顺序:
-
先在
DBServlet
中查找getServletConfig()
-
未找到 → 在父类
HttpServlet
中查找 -
未找到 → 在祖父类
GenericServlet
中找到。
-
对象创建与内存结构:
// Tomcat 通过反射创建实例
DBServlet dbServlet = new DBServlet();
-
Java中所有方法调用(除static外)都是基于当前对象实例
-
父类方法只是代码的存放位置,执行时操作的仍是子类实例的内存空间
深入解析:父类方法中的 this
指向
在 Java 中,this
永远指向当前执行方法的对象实例。无论这个方法定义在哪个类中,this
都指向实际调用该方法的对象.
图解:
现实类比:
想象一家三代人:
-
祖父(
GenericServlet
)有个保险箱(config
字段) -
父亲(
HttpServlet
)继承了保险箱 -
儿子(
DBServlet
)实际拥有这个保险箱
当祖父说:"给我保险箱里的东西"(this.config
):
-
如果是对父亲说,拿父亲的保险箱
-
如果是对儿子说,拿儿子的保险箱
方法("给我"这个动作)是祖父定义的,但拿的是实际说话对象(this
)的保险箱。】
三、面试题:
public class DBServlet extends HttpServlet {@Overridepublic void init(ServletConfig config) throws ServletException {//ConcurrentHashMap, 是一个线程安全的容器.System.out.println("init" + config);super.init(config);}protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {ServletConfig servletConfig = getServletConfig();System.out.println("doPost=" + servletConfig);String username = servletConfig.getInitParameter("username");String pwd = servletConfig.getInitParameter("pwd");System.out.println("初始化参数username= " + username);System.out.println("初始化参数pwd= " + pwd);}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {doPost(request, response);}
}
对于这段代码,如果我们在init()方法中去掉了super.init(config);当我们发送请求时,ServletConfig会显示什么?
大家可以思考一下:
答案是NULL。
下面我将详细的给大家解释一下子
1. this
的指向
-
在
GenericServlet.init()
中:this
指向实际创建的DBServlet
实例 -
this.config = config
实际是设置DBServlet
实例的字段
2. 为什么需要 super.init(config)
场景 |
| 结果 | 原因 |
---|---|---|---|
调用 | 执行 |
| 父类方法将配置存入当前实例 |
不调用 | 不执行 |
| 父类赋值逻辑被跳过 |
// Tomcat创建实例后
DBServlet实例 = {config: null // 初始状态
}// 调用init(config)后
DBServlet实例 = {config: ServletConfig对象 // 通过super.init()设置
}
错误实现(导致问题)
public void init(ServletConfig config) throws ServletException {// 忘记调用super.init(config)System.out.println("配置:" + config); // 此时config参数有值// 但父类的config字段未被设置!
}protected void doPost(...) {// 这里获取的是父类字段,仍为null!ServletConfig config = getServletConfig();
}
-
如果跳过
super.init(config)
:-
父类的赋值逻辑
this.config = config
不会执行 -
DBServlet
实例的config
字段保持null
-
后续
getServletConfig()
返回null
-
总结
以上这就是 Servlet 生命周期管理的精妙之处——通过继承和模板方法,既保证了必要流程的执行,又提供了灵活的扩展点。我会持续更新的,谢谢大家!