动力节点JavaWeb视频
从底层源码开始讲起, 讲解的非常详细,对比过尚硅谷的视频,推荐动力节点。
视频是直播的录像,有些时候比较啰嗦,尤其到EL表达式之后,1小时的内容能讲3小时。
系列文章目录
- 第一章 配置环境、配置IDEA
- 第二章 GenericServlet源码解析 特别重要
- 第三章 HttpServlet
- 第四章 HttpServletRequest
- 第五章 重定向 使用注解简化配置文件
- 第六章 JSP (上)
- 第七章 JSP (下)EL表达式 JTSL标签库
- 第八章 Session、Cookie
- 第九章 Filter过滤器、Listner 监听器
GenericServlet源码解析
Servlet的生命周期
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()
方法,然后销毁对象
总结:
init
方法只有在创建对象时执行一次,适合做初始化操作,例如:初始化线程池、初始化数据库连接池等
destroy
方法只有在对象销毁之前执行一次,适合做资源关闭、保存数据等。
Servlet协议相关的接口
Servlet 接口
- 源码:
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 抽象类 优化 Servlet接口
- 自己写的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();
//获取初始化参数<init-param>中name对应的value
public String getInitParameter(String name);
//获取初始化参数<init-param>中的names
public Enumeration<String> getInitParameterNames();
}
- web.xml配置文件
<-- servletConfig.getServletContext()可以获得整个配置文件,这个webapp中的所有servlet -->
<servlet>
<-- servletConfig.getServletName()可以得到<servlet-name>标签中的内容 -->
<servlet-name>configServlet</servlet-name>
<servlet-class>com.jpowernode.javaweb.servlet.ConfigTest</servlet-class>
<-- 初始化参数 -->
<init-param>
<-- servletConfig.getInitParameterNames()获得<param-name>标签中的内容 -->
<param-name>driver</param-name>
<-- servletConfig.getInitParameter(initParameterName)获得<param-value>标签中的内容 -->
<param-value>com.mysql.cj.jdbc.Driver</param-value>
</init-param>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql//localhost:3306/bjpowernode</param-value>
</init-param>
<init-param>
<param-name>user</param-name>
<param-value>root</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>root</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>configServlet</servlet-name>
<url-pattern>/config</url-pattern>
</servlet-mapping>
</servlet>
ServletConfig 接口的四个方法
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();
//获得配置文件中<init-param>便签下<param-name>的值
Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
while(initParameterNames.hasMoreElements()){
String initParameterName = initParameterNames.nextElement();
//获得配置文件中<init-param>便签下<param-name>对应的<param-value>的值
String initParameter = servletConfig.getInitParameter(initParameterName);
out.write("key: "+initParameterName+"    "+"value: "+initParameter+"</br>");
}
//servletName:配置文件中<servlet-name>
String servletName = servletConfig.getServletName();
out.write("servletName:  "+servletName+"</br>");
//servletContext中包含了整个项目的信息,web.xml的全部信息
ServletContext servletContext = servletConfig.getServletContext();
String contextPath = servletContext.getContextPath();
out.write("servletContext:  "+servletContext.getContextPath()+"</br>");
}
}
输出
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,这样开发人员就能在及时的获得到配置文件中的配置。
ServletContext 接口
- Tomcat实现
ServletContext
接口的类:ApplicationContextFacade.java
(.\apache-tomcat-10.0.12-src-源码\java\org\apache\catalina\core)
ServletContext、SerletConfig、Servlet的关系
- 一个webapp对应多个Servlet,对应一个配置文件web.xml,对应一个
ServletContext
。 - 一个Servlet,对应一组
<servlet></servlet>
标签,对应一个SerletConfig
。 - 一个webapp里面的所有Servlet共有一个
ServletContext
。
用现实生活举例:
一个班级就是一个webapp,班级里面的学生就是Servlet。
一个班级公用的所有东西就是ServletContext
,每个学生的私有物品就是ServletConfig
。
- ServletContext是通过ServletConfig传递到Servlet中,所以在Servlet中很容易得到ServletContext
ServletContext servletContext = getServletConfig().getServletContext();
通过ServletContext获得webapp的全局配置
web.xml
配置文件
<web-app ..省略..>
<-- webapp 的全局配置 -->
<context-param>
<param-name>page</param-name>
<param-value>50</param-value>
</context-param>
<context-param>
<param-name>StartPage</param-name>
<param-value>1</param-value>
</context-param>
<-- servlet 的配置 -->
<servlet>
<servlet-name>ContextConfig</servlet-name>
<servlet-class>com.jpowernode.javaweb.servlet.ContextConfig</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ContextConfig</servlet-name>
<url-pattern>/contextconfig</url-pattern>
</servlet-mapping>
</servlet>
代码部分:
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<String> 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+"</br>");
}
}
}
输出:
context-parm-name: StartPage context-parm-value: 1
context-parm-name: page context-parm-value: 50
获取当前项目的根节点
String servletContextName = servletContext.getContextPath();
out.write(servletContextName+"</br>");
输出:
/servlet_war_exploded
获取webapp中文件的绝对路径
参数:从根路径开始
String realPath = servletContext.getRealPath("/index.html");
out.write(realPath+"</br>");
输出:
D:\JAVA\javaweb\out\artifacts\servlet_war_exploded\index.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<String> 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();
}
}