- Servlet的生命周期
- Servlet协议相关的接口
- Servlet 接口
- 自建GenericServlet 抽象类 优化 Servlet接口
- ServletCofing 接口
- ServletConfig 接口的四个方法
- ServletContext 接口
- ServletContext、SerletConfig、Servlet的关系
- 通过ServletContext获得webapp的全局配置
- 获取当前项目的根节点
- 获取webapp中文件的绝对路径
- 记录日志
- 共享数据
- GenericServlet 源码
Servlet对象是由服务器创建的 。
服务器创建的Servlet对象是由服务器的WEB容器管理器统一管理
开发人员不需要对Servlet对象的创建和销毁进行管理
- 启动服务器后会读取web.xml中的配置文件
- 当用户使用浏览器访问时,服务器会根据配置文件通过反射机制创建Servlet的对象
2.1. 服务器通过对url的解析,找到对应的标签中的标签
2.2. 通过同一组标签的中的名字去标签中查找中查找相同的标签
2.3. 找到通过名字找到对应的class文件的完整路径。通过反射创建对象
3. 创建对象使用的是无参构造方法(所以不能在类中加有参构造方法)
4. 执行对象的init()方法
5. 执行对象的service()方法
6. 当用户再次访问时再次执行service()方法,用户发送多少次请求就再执行多少次service()方法。
7. 关闭服务器时,将会执行destroy()方法,然后销毁对象
总结:
Servlet协议相关的接口 Servlet 接口init方法只有在创建对象时执行一次,适合做初始化操作,例如:初始化线程池、初始化数据库连接池等
destroy方法只有在对象销毁之前执行一次,适合做资源关闭、保存数据等。
- 源码:
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}
- 实现Servlet接口后,共有5个方法,大部分情况都只调用service()一个方法,其他代码虽然不使用但还是要实现,这让代码看起来不够优雅
- 为了解决这个问题,优化Servlet接口,创建GenericServlet抽象类
- 自己写的GenericServlet 抽象类,非源码
public abstract class GenericServlet implements Servlet {
private ServletConfig config;
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();//模板方法设计模式
}
public void init(){}
@Override
//常用的方法作为抽象方法,实现类必须实现此方法,而其他方法则由抽象类全部实现。这样实现类中就只有这一个方法需要实现了。
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
//获取成员变量config的值
@Override
public ServletConfig getServletConfig() {
return config;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
// NOOP by default
}
- 通过GenericServlet 抽象类创建Servlet:
public class Implementation extends GenericServlet {
@Override
public void init() throws ServletException {
//初始化,会自动执行
// TODO:
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
// TODO:
}
}
通过对Servlet接口的优化,Implementation 类中代码已经精简了很多。
总结:
- 使用了适配器设计模式,将最常用的service()方法定义为抽象方法,如果要实现GenericServlet 抽象类,则必须重写此方法,而其他不常用的方法,都已经在此抽象类中实现。
- 使用了模板方法设计模式,当实现类Implementation中想要用init()方法初始化,则可以重写抽象类中的init()方法,这样就保证了抽象类GenericServlet中的init(ServletConfig config)方法不会被重写。
- 当实现类Implementation创建对象时Tomcat会调用抽象类GenericServlet中的init(ServletConfig config)方法,而init(ServletConfig config)方法中会执行this.init();代码,从而也会自动执行实现类Implementation中的init()。
- init(ServletConfig config)中的config对象,将其从局部变量转变为全局变量。通过getServletConfig()方法可获得该变量的值。
为什么非要在实现类中使用init方法呢?可以不使用init这个名字。只要在抽象类init(ServletConfig config)方法中加入需要调用的方法,实现类去实现就可以了,只是习惯使用init这个名字的方法来初始化某些数据。
不容易理解的点:方法名相同但是参数不同,会被当成时两个方法,对继承没有影响,对于实现类来说就有两个init方法一个有参数一个没有参数。
Tomcat创建实现类对象—>执行抽象类中的init(ServletConfig config)—>this.init();—>实现类中的init()方法
这只是简单的模仿了GenericServlet 的由来,GenericServlet 中还包含实现了ServletConfig,java.io.Serializable这两个接口
ServletCofing 接口在Servlet 接口的init(ServletConfig config)方法中有一个ServletConfig 对象,Tomcat在每次创建Servlet实现类的对象时都会调用一次init(ServletConfig config),那么方法中的config 传了什么到Servlet中?
- ServletConfig接口的源代码
public interface ServletConfig {
//获取Servlet的name
public String getServletName();
//获取整个Webapp应用的配置文件
public ServletContext getServletContext();
//获取初始化参数中name对应的value
public String getInitParameter(String name);
//获取初始化参数中的names
public Enumeration getInitParameterNames();
}
- web.xml配置文件
<-- servletConfig.getServletContext()可以获得整个配置文件,这个webapp中的所有servlet -->ServletConfig 接口的四个方法<-- servletConfig.getServletName()可以得到 标签中的内容 --> configServlet com.jpowernode.javaweb.servlet.ConfigTest <-- 初始化参数 --><-- servletConfig.getInitParameterNames()获得 标签中的内容 --> driver <-- servletConfig.getInitParameter(initParameterName)获得标签中的内容 --> com.mysql.cj.jdbc.Driver url jdbc:mysql//localhost:3306/bjpowernode user root password root configServlet /config
import jakarta.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
public class ConfigTest extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
ServletConfig servletConfig = getServletConfig();
//获得配置文件中便签下的值
Enumeration initParameterNames = servletConfig.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
String initParameterName = initParameterNames.nextElement();
//获得配置文件中便签下对应的的值
String initParameter = servletConfig.getInitParameter(initParameterName);
out.write("key: "+initParameterName+"    "+"value: "+initParameter+"");
}
//servletName:配置文件中
String servletName = servletConfig.getServletName();
out.write("servletName:  "+servletName+"");
//servletContext中包含了整个项目的信息,web.xml的全部信息
ServletContext servletContext = servletConfig.getServletContext();
String contextPath = servletContext.getContextPath();
out.write("servletContext:  "+servletContext.getContextPath()+"");
}
}
输出
key: password value: root key: driver value: com.mysql.cj.jdbc.Driver key: user value: root key: url value: jdbc:mysql//localhost:3306/bjpowernode servletName: configServlet servletContext: /servlet_war_exploded
总结:
Tomcat在创建Servlet的对象时,同时会把配置文件中的值都传递给init(ServletConfig config)中的config,这样开发人员就能在及时的获得到配置文件中的配置。
- Tomcat实现ServletContext接口的类:ApplicationContextFacade.java(.apache-tomcat-10.0.12-src-源码javaorgapachecatalinacore)
- 一个webapp对应多个Servlet,对应一个配置文件web.xml,对应一个ServletContext。
- 一个Servlet,对应一组
标签,对应一个SerletConfig。 - 一个webapp里面的所有Servlet共有一个ServletContext。
用现实生活举例:
一个班级就是一个webapp,班级里面的学生就是Servlet。
一个班级公用的所有东西就是ServletContext,每个学生的私有物品就是ServletConfig。
- ServletContext是通过ServletConfig传递到Servlet中,所以在Servlet中很容易得到ServletContext
ServletContext servletContext = getServletConfig().getServletContext();通过ServletContext获得webapp的全局配置
web.xml配置文件
<-- webapp 的全局配置 --> page 50 <-- servlet 的配置 --> StartPage 1 ContextConfig com.jpowernode.javaweb.servlet.ContextConfig ContextConfig /contextconfig
代码部分:
public class ContextConfig extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
//通过ServletConfig获得ServletContext
ServletContext servletContext = getServletConfig().getServletContext();
//获得全局配置标签
Enumeration initParameterNames = servletContext.getInitParameterNames();
//遍历标签,获得name和value
while(initParameterNames.hasMoreElements()){
String name = initParameterNames.nextElement();
String initParameter = servletContext.getInitParameter(name);
System.out.println(name+initParameter);
out.write("context-parm-name:   "+name+
"     context-parm-value:   "+initParameter+"");
}
}
}
输出:
context-parm-name: StartPage context-parm-value: 1 context-parm-name: page context-parm-value: 50获取当前项目的根节点
String servletContextName = servletContext.getContextPath(); out.write(servletContextName+"");
输出:
/servlet_war_exploded获取webapp中文件的绝对路径
参数:从根路径开始
String realPath = servletContext.getRealPath("/index.html");
out.write(realPath+"");
输出:
D:JAVAjavaweboutartifactsservlet_war_explodedindex.html记录日志
参数:可以只记录信息,也可以记录异常信息
servletContext.log("日志记录信息", new RuntimeException());
servletContext.log("日志记录信息");
日志记录的路径
日志的分类
作用:当所有用户共享一份数据,并且数据量小,很少被修改,可以将数据存放到ServletContext中
涉及占用内存和多线程并发带来的问题,所以要求数据量小,且很少被修改
//存取和Map很类似,以键值对的方式存入
servletContext.setAttribute("key", "value");
// 根据key的名字来取
Object key = servletContext.getAttribute("key");
// 根据key的名字删除
servletContext.removeAttribute("key");
GenericServlet 源码
GenericServlet 抽象类 实现Servlet、Servletconfig、ServletContext接口中常用的方法
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
private static final long serialVersionUID = 1L;
// 将Tomcat传递来的ServletConfig改变为全局变量,方便使用
private transient ServletConfig config;
//GenericServlet的无参构造方法
public GenericServlet() {}
// 当Servlet要销毁前执行的方法,实现Servlet接口
@Override
public void destroy() {}
// 获得ServletContext的全局配置的value,实现ServletContext接口
@Override
public String getInitParameter(String name) {
return getServletConfig().getInitParameter(name);
}
// 获得ServletContext的全局配置的name,实现ServletContext接口
@Override
public Enumeration getInitParameterNames() {
return getServletConfig().getInitParameterNames();
}
// 获得ServletConfig,实现Servlet接口
@Override
public ServletConfig getServletConfig() {return config;}
// 从ServletConfig对象中获取ServletContext,实现ServletConfig接口
@Override
public ServletContext getServletContext() {
return getServletConfig().getServletContext();}
@Override
public String getServletInfo() {
return "";
}
// 当用户访问Servlet时,Tomcat自动调用的初始化方法,实现Servlet接口
@Override
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
// 自有的方法,方便子类继承使用。当init(ServletConfig config)方法执行时会自动调用此方法。
public void init() throws ServletException {}
// 日志打印,实现ServletContext接口
public void log(String message) {
getServletContext().log(getServletName() + ": " + message);
}
// 日志打印,实现ServletContext接口
public void log(String message, Throwable t) {
getServletContext().log(getServletName() + ": " + message, t);
}
// 最主要的方法,接收用户请求,处理后返回。实现Servlet接口
@Override
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
// 获取当前webapp的根路径,实现ServletContext接口
@Override
public String getServletName() {
return config.getServletName();
}
}



