文章目录
- 类直接实现Servlet接口的弊端
- Servlet接口的方法
- 适配器设计模式
- 适配器对象的改造
- 关于init方法的ServletConfig对象来源
- 使用模板方法设计模式改造init方法
- GenericServlet内置抽象类
- ServletConfig接口
- ServletConfig接口简介
- 测试
- 再谈GenericServlet抽象类
类直接实现Servlet接口的弊端
Servlet接口的方法
上面是jakarta.servlet.Servlet
接口中的方法
- init(): 初始化Servlet对象的相关信息
- service(): 业务的核心方法, 也是Tomcat调用该对象实现逻辑的入口
- destroy(): 销毁Servlet对象的信息
- getServletConfig(): 获取
ServletConfig
对象(这个下面再说) - getServletInfo(): 获取一些无用的信息(作者, 版本号之类的)
适配器设计模式
为什么类不能直接实现Servlet接口呢, 是因为对于一个Servlet对象来说, 除了service
方法, 其他的对象都是不经常用的, 如果我任意一个类都去实现Servlet接口中的所有方法, 那最后的结果就是代码十分的冗余…
适配器设计模式可以类比为手机不能直接插在220V电源上, 需要一个适配器的充电头进行转换…
所以我们创建一个适配器(也就是一个抽象类), 实现这个Servlet接口中的大部分方法, 只把一些需要子类重定义的方法抽象出来, 这样就可以使得代码的冗余度大大降低, 这其实就是适配器设计模式的核心
我们创建一个适配器的抽象类代码如下
java">import jakarta.servlet.*;
import java.io.IOException;
/**
* 适配器设计模式
* 因为我们并不是所有的Servlet对象实现的时候并不需要所有的Servlet方法
* 需要我们添加一个适配器, 实现里面的大部分方法即可(除了Service)
*/
public abstract class ServletAdapt implements Servlet{
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public abstract void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException;
@Override
public String getServletInfo() {
return "";
}
@Override
public void destroy() {
}
}
下面我们创建一个子类实现这个适配器中的抽象方法
java">import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
public class UserServlet extends ServletAdapt{
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 重写适配器的service方法对外提供服务即可
System.out.println("UserServlet service method called");
}
}
适配器对象的改造
我们官方都把适配器对象称之为GenericServlet
(标题的由来)
关于init方法的ServletConfig对象来源
思考这个ServletConfig对象是谁传过来的, 我们大致说一下底层的Tomcat伪代码
java">class Tomcat{
public static void main(String[] args) {
// 首先通过反射机制拿到相关类对象
Class clazz = Class.forName("全限定类名");
// 构造出该类的一个实例
Object object = clazz.getConstructor().newInstance();
// 向下转型成为Servlet对象
Servlet servletImp = (Servlet) object;
// 创建一个ServletConfig对象
ServletConfig servletConfig = new ServletConfigImp();
// 初始化这个类的实例
servletImp.init(servletConfig);
// 使用service方法提供服务
servletImp.service(ServletRequest request, ServletResponse response);
}
}
所以我们的ServletConfig
对象, 是 Tomcat服务器
创建出来的
我们想查看以下这个实现ServletConfig
接口的对象的信息(改造init方法)
在web.xml
文件中添加映射信息如下
在浏览器中输入URL访问…
上面我们说了, 这个对象是 Tomcat 服务器实现的, 我们现在找到上次下载的关于 Tomcat 服务器的源码找到这个类进行分析
可以发现这个类实现了jakarta.servlet.ServletConfig
接口, 这同时也证明了, 我们的Tomcat
服务器实现了 Servlet规范
使用模板方法设计模式改造init方法
关于模板方法设计模式, 核心总结就是下面的一句话
- 对拓展开放, 对修改关闭
假如, 我们想要这个 ServletConfig 对象那应该怎么办呢
init
方法的其中一个参数就是ServletConfig
, 所以我们可以创建一个实例变量接住这个临时变量…
但是假如子类继承了这个适配器, 并且尝试对其中的init
方法进行重写, 那我们的config
对象不就有变为空了吗,
所以一个简单的策略是直接把init
方法使用final
修饰, 此时不允许子类进行重写…
但是还有一个问题, 假设我们的子类真的想要重写init
方法, 但是又不想config
对象变成空, 那我们就可以采用模板方法设计模式
这样就会在不破坏原有代码的基础上进行代码能力的扩展, 这就是模板方法设计模式
可以看到, 在源代码基本功能不变的基础上, 重写的方法正常执行了…
GenericServlet内置抽象类
其实我们上面的分析出来的内容, 实际上内置的类已经实现了相关方法了
GenericServlet
实现了Servlet接口 & ServletConfig接口
下面是我们的GenericServlet
的源码解析
可以看到里面init
方法正是使用了模板方法设计模式
进行的设计, 十分巧妙…
完整源码
java">package jakarta.servlet;
import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
private static final long serialVersionUID = 1L;
private transient ServletConfig config;
public GenericServlet() {
}
public void destroy() {
}
public String getInitParameter(String name) {
return this.getServletConfig().getInitParameter(name);
}
public Enumeration<String> getInitParameterNames() {
return this.getServletConfig().getInitParameterNames();
}
public ServletConfig getServletConfig() {
return this.config;
}
public ServletContext getServletContext() {
return this.getServletConfig().getServletContext();
}
public String getServletInfo() {
return "";
}
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}
public void log(String message) {
ServletContext var10000 = this.getServletContext();
String var10001 = this.getServletName();
var10000.log(var10001 + ": " + message);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ": " + message, t);
}
public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
public String getServletName() {
return this.config.getServletName();
}
}
我们先介绍其中的一部分方法, 等下学习完ServletConfig
之后把全部的方法介绍一下…
ServletConfig接口
ServletConfig接口简介
我们上面学习Servlet接口
的时候, 知道有一个方法getServletConfig
, 这个方法的作用就是返回一个ServletConfig对象
其实也就是上面我们分析的Tomcat服务器创建传入的那个对象
- 一个
Servlet对象
有且仅有唯一一个ServletConfig对象
- 保存的是该
Servlet对象在web.xml中配置的servlet标签一些信息
下面是ServletConfig接口
中的常见的方法
- getInitParameter(String name): 返回一个name对应的参数值
- getInitParameterNames(): 返回一个集合保存所有的name值
- getServletContext(): 返回一个ServletContext对象
- getServletName(): 返回配置的servlet-name
测试
我们在web.xml
中配置的相关信息如下
<servlet>
<servlet-name>user</servlet-name>
<servlet-class>com.qnn.servlet.UserServlet</servlet-class>
<!--下面配置的相关参数信息, 可以通过ServletConfig对象中的方法拿到-->
<init-param>
<param-name>userName</param-name>
<param-value>qiannian</param-value>
</init-param>
<init-param>
<param-name>account</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
</servlet>
关于我们Servlet对象实现的service()
方法的内容如下
java"> @Override
// 测试一下ServletConfig对象中保存的相关信息
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
// 改变输出的方法, 并获取一个输出流对象
response.setContentType("text/html");
PrintWriter out = response.getWriter();
// 通过Servlet接口中的方法获得一个ServletConfig对象
ServletConfig servletConfig = this.getServletConfig();
// 利用ServletConfig对象中的相关的方法来输出我们当前的Servlet对象在web.xml中配置的一些信息
// 这里有一个小点就是, 这个集合类其中的元素不可以使用foreach来进行循环的遍历...
out.print("<h3>" + servletConfig.getServletName() + "</h3>");
out.print("<br/>");
Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
while (initParameterNames.hasMoreElements()) {
String initParameterName = initParameterNames.nextElement();
out.print("<br/>");
out.print("<h3>" + initParameterName + " : " + servletConfig.getInitParameter(initParameterName) + "</h3>");
}
}
结果如下
再谈GenericServlet抽象类
有了上面的基础, 我们想要理解GenericServlet
抽象类中的内容就更容易了…具体不再说了…