java-springmvc 01 补充 javaweb 三大组件Servlet,Filter、Listener(源码都是tomcat8.5项目中的)

01.JavaWeb三大组件指的是:Servlet、Filter、Listener,三者提供不同的功能
这三个在springmvc 运用很多

Servlet
在这里插入图片描述

01.Servlet接口:

public interface Servlet {/*** 初始化方法* 实例化servlet之后,该方法仅调用一次 * init方法必须执行完成,servlet才能接收任何请求*/public void init(ServletConfig config) throws ServletException;/*** 获取Servlet的初始化和启动参数对象*/public ServletConfig getServletConfig();/*** 处理request请求*/public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;/*** 返回有关servlet的信息,例如作者,版本和版权*/public String getServletInfo();/*** 销毁方法*/public void destroy();
}

02.Servlet相关体系
在这里插入图片描述

ServletConfig接口:用来定义一个在初始化期间将配置信息(Servlet名、初始化参数等)传递给Servlet的Servlet配置对象。它的主要实现子类是StandardWrapperFacade类。

GenericServlet抽象类:用于包装Servlet接口,其中提供了很多Servlet接口的默认实现,这样我们实现Servlet的时候,就不必实现Servlet接口的所有方法,只重写核心方法即可。

HttpServlet抽象类:听这个类的名字就大概能够知道,HttpServlet类是专门用于处理http请求的Servlet类。它继承了GenericServlet类,其中有很多http请求专用的处理方法(例如:doGet、doPost、doPut等等方法)

03.Servlet使用例子

@WebServlet("/my")
public class MyServlet extends HttpServlet {@Overridepublic void init() {System.out.println("servlet init...");}@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {resp.getWriter().write("Get Method");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {resp.getWriter().write("Post Method");}@Overridepublic void destroy() {System.out.println("servlet destroy...");}
}

以上代码就是一个基础的Servlet代码,使用起来十分简单。我们只要继承HttpServlet,然后覆盖其init、destroy、doGet、doPost(还有其他的doXXX方法,后面源码分析会讲到)即可。
从使用层面上来看,实现一个Servlet真的没什么难度,但是我们关注不是如何使用,而是其中的实现原理。

@WebServlet的使用:
Servlet 中,web.xml 扮演的角色十分的重要,它可以将所有的 Servlet 的配置集中进行管理,但是若项目中 Servelt 数量较多时,web.xml 的配置会变得十分的冗长。这种情况下,注解(Annotation)就是一种更好的选择。

@WebServlet 注解的属性: 一部分
在这里插入图片描述
使用 @WebServlet 注解
@WebServlet 属于类级别的注解,标注在继承了 HttpServlet 的类之上。常用的写法是将 Servlet 的相对请求路径(即 value)直接写在注解内,
@WebServlet(​urlPatterns = “/MyServlet”)。
@Web​Servlet(“/MyServlet”) 省略了 urlPatterns 属性名

04.Servlet执行流程

配置信息初始化阶段:当服务器启动后,首先会读取web.xml配置文件或者注解配置的相关Servlet信息,然后创建对应的对象,为之后Servlet初始化做准备。

Servlet初始化阶段:当请求访问达到,Servlet首先调用init方法进行初始化。

Servlet执行阶段:当Servlet初始化完成后,就会调用service方法进行业务处理。

05.Servlet源码分析
基于以上对Servlet的了解,那么现在开始进入Servlet的源码分析。

1.配置信息初始化阶段:
首先来了解下配置信息初始化阶段,就拿上面自定义的MyServlet类来作为例子讲解(为了讲解源码时候排除不必要的干扰,此后的源码解析内容只针对关键部分代码进行讲解)。

当我们定义好MyServlet类后,便启动Tomcat服务器。
通过调试可以发现配置信息初始化的入口是ContextConfig类的configureContext方法:这个就是启动Tomcat后,初始化Servlet配置信息的入口方法。如果你有足够的好奇心的话,肯定会对这个方法有不少的疑问。

private void configureContext(WebXml webxml) {... // 省略干扰代码// 从web.xml配置文件或注解配置信息中获取配置的Servlet,并将其封装成ServletDef对象(这个ServletDef是个什么?)for (ServletDef servlet : webxml.getServlets().values()) { Wrapper wrapper = context.createWrapper(); // 从context容器中创建wrapper对象(这个context是个什么?warpper呢?)... wrapper.setName(servlet.getServletName()); // 为wrapper设置Servlet名称Map<String,String> params = servlet.getParameterMap();for (Entry<String, String> entry : params.entrySet()) {wrapper.addInitParameter(entry.getKey(), entry.getValue()); // 为wrapper添加初始化参数}...wrapper.setServletClass(servlet.getServletClass()); // 为wrapper设置Servlet类限定名... 
}

ServletDef :
通过查看ServletDef类的方法和属性,以及官方解释,可以得知:这个ServletDef类其实就是用来封装Servlet相关配置信息的,我称其为Servlet定义对象。

/*** 来看下官方解释:* Web应用程序的Servlet定义的表示形式,如在部署描述符的<servlet>元素* 例如以下Servlet配置:* <servlet>* 	<servlet-name>MyServlet</servlet-name>*   <servlet-class>com.servlet.MyServlet</servlet-class>* </servlet>*  * 说白了,其实这个ServletDef就是将我们配置的Servlet信息封装成了一个对象* 其中,它还有两个重要属性:servletName、servletClass
*/
public class ServletDef implements Serializable {...private String servletName = null; // Servlet名,必须唯一。表示<servlet-name>标签中的内容private String servletClass = null; // Servlet类全限定名。表示<servlet-class>标签中的内容... 
}

context:(tomcat相关容器)

这个context对象,其实代表的是容器,它是StandardContext类的实例化对象。这个通过调试就可以知道,在此不解释太多。

wrapper:(tomcat相关容器)
对于warpper对象来说,它是通过调用 context.createWrapper() 方法而来的。它是StandardWrapper类的实例化对象。单看方法名调用就可以大概猜测到,它是在容器中生成并返回的,因此我们来看下StandarContext类的createWrapper方法:

/**
* 从这个方法的关键代码来看,其实这个wrapper对象是通过反射的方式创建的
* 而且它是StandarWrapper类的实例对象
*/
public Wrapper createWrapper() {Wrapper wrapper = null;if (wrapperClass != null) {try {wrapper = (Wrapper) wrapperClass.getConstructor().newInstance();} catch (Throwable t) {ExceptionUtils.handleThrowable(t);log.error("createWrapper", t);return null;}} else {wrapper = new StandardWrapper();}... return wrapper;
}

用到了变量wrapperClass :

private Class<?> wrapperClass = null;

为了全面了解这个wrapper对象,我们继续往下看StandarWrapper类:

/**
* 看下官方解释:
* Wrapper接口的标准实现,表示单个servlet定义。不允许使用任何子容器,并且父容器必须是上下文
*/
public class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper, NotificationEmitter {//构造函数public StandardWrapper() {super();swValve=new StandardWrapperValve();//阀门,这个和tomcat的管道阀门模型有关pipeline.setBasic(swValve);broadcaster = new NotificationBroadcasterSupport();}}

先不说StandardWrapper类其他细节,通过官方的解释,我们可以知道它其实是Wrapper接口的实现子类。那么为了搞清楚这个StandardWrapper类的具体目的,我们再来看下这个Wrapper接口:

/**
* 结合官方的注释,以及其中定义的各种抽象方法,可以知道:
* Wrapper的实现类负责管理其基础servlet类的servlet生命周期,包括在适当的时间调用init()和destroy()等等
*/
public interface Wrapper extends Container {... // 其中的抽象方法都是针对于Servlet设置/获取相关信息,在此不一一列举
}

到这里可以明白StandardWrapper类其实是用于管理Servlet生命周期的,从创建到销毁。那么基于这个理论,我们再来看看它其中的重要属性和方法:

public class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper, NotificationEmitter {// 真正的Servlet对象,默认为空protected volatile Servlet instance = null;... // Servlet类的全限定名,默认为空protected String servletClass = null;// Servlet类的参数集合protected HashMap<String, String> parameters = new HashMap<>();@Overridepublic void setServletClass(String servletClass) { // 设置Servlet类全限定名String oldServletClass = this.servletClass;this.servletClass = servletClass;support.firePropertyChange("servletClass", oldServletClass,this.servletClass);if (Constants.JSP_SERVLET_CLASS.equals(servletClass)) {isJspServlet = true;}}...// 设置Servlet名(name属性并不是在当前类中定义的)public void setServletName(String name) {setName(name);}//设置Servlet参数,这里是符合Wrapper包装的Servlet的参数public void addInitParameter(String name, String value) {parametersLock.writeLock().lock();try {parameters.put(name, value);} finally {parametersLock.writeLock().unlock();}fireContainerEvent("addInitParameter", name);}
}

到此, Servlet配置信息初始化阶段的源码解析就结束了。在这个阶段做的事情并不多,主要就是创建wrapper对象,并为其设置相应的Servlet配置信息,为之后的阶段做准备。

为了加深理解,送出下图:
在这里插入图片描述
2. Servlet初始化阶段
当Tomcat服务器启动后,Servlet相关的配置信息已经初始化好了。那么当我们在网页上通过网址访问后端Servlet时,此时就会进入Servlet的第二个阶段——Servlet初始化。

通过调试,可以知道Servlet初始化的入口是StandardWrapperValve类的invoke方法:

public final void invoke(Request request, Response response) throws IOException, ServletException {...Servlet servlet = null;StandardWrapper wrapper = (StandardWrapper) getContainer();... // 分配一个Servlet实例来处理request请求try {if (!unavailable) {servlet = wrapper.allocate();}}// 在之后的操作中会使用到上面分配到的servlet对象对过滤链进行初始化// 因为在请求到达Servlet之前,会经过一系列的过滤器校验过滤// 但本文只是对Servlet进行源码解析,对于Filter过滤器的源码不做太多解释... 
}

allocate方法:

public Servlet allocate() throws ServletException {...// 如果不是单线程模式,则每次分配都返回同一个Servlet对象(复用Servlet)// singleThreadModel默认为falseif (!singleThreadModel) {// 此处的instance就是配置信息初始化阶段的时候说的真正Servlet对象// 而instance初始化时候,默认为空。说明第一次请求访问对应的Servlet时候,需要创建instance// instanceInitialized属性表示instance是否已初始化if (instance == null || !instanceInitialized) { synchronized (this) { // 初始化servlet属于同步操作if (instance == null) {try {if (log.isDebugEnabled()) { // 日志相关log.debug("Allocating non-STM instance");}instance = loadServlet(); // 加载Servlet(重点关注)newInstance = true; // newInstance表示当前instance是否是此次访问新建的if (!singleThreadModel) { // 不是单线程模式,则记录已分配Servlet次数加1(用于多线程计数)countAllocated.incrementAndGet();}} catch (ServletException e) {throw e;} catch (Throwable e) {ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("standardWrapper.allocate"), e);}}if (!instanceInitialized) { // 加载获取到的Servlet对象仍未初始化initServlet(instance); // 初始化已加载的Servlet对象}}}if (singleThreadModel) {...} else {if (log.isTraceEnabled()) { // 日志相关log.trace("  Returning non-STM instance");}if (!newInstance) { // instance不是此次访问新建的,说明instance已创建countAllocated.incrementAndGet(); // 记录分配Servlet对象次数加1}return instance; // 返回分配的Servlet对象}}...
}

可以看到,返回的instance对象是由loadServlet方法加载而来:

public synchronized Servlet loadServlet() throws ServletException {if (!singleThreadModel && (instance != null)) // 如果instance已存在则直接返回(复用)return instance;...Servlet servlet;try {long t1=System.currentTimeMillis();if (servletClass == null) {  // servletClass为空,说明Servlet类限定名设置失败,抛出异常(servletClass属性是由配置信息初始阶段时设置的,忘了回头看)unavailable(null);throw new ServletException(sm.getString("standardWrapper.notClass", getName()));}InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();try {servlet = (Servlet) instanceManager.newInstance(servletClass); // 根据Servlet类全限定名通过反射创建Servlet实例对象}... initServlet(servlet); // 初始化Servlet实例对象...} ...return servlet; // 最后返回已创建并初始化好的Servlet对象
}

对Servlet实例对象进行初始化,实际上就是调用其init方法:

private synchronized void initServlet(Servlet servlet) throws ServletException {if (instanceInitialized && !singleThreadModel)  // 如果此Servlet已初始化过,则直接返回(同时说明,init方法实例化Servlet后只执行一次)return;try {if(Globals.IS_SECURITY_ENABLED) {boolean success = false;try {Object[] args = new Object[] { facade };SecurityUtil.doAsPrivilege("init", servlet, classType, args);success = true;} finally {if (!success) {// destroy() will not be called, thus clear the reference nowSecurityUtil.remove(servlet);}}} else {servlet.init(facade); // 调用Servlet的init方法,并传入facade属性(facade属性是什么?),进行初始化}instanceInitialized = true; // 标识该Servlet已经初始化过了} catch (UnavailableException f) {unavailable(f);throw f;} catch (ServletException f) {throw f;} catch (Throwable f) {ExceptionUtils.handleThrowable(f);getServletContext().log("StandardWrapper.Throwable", f );throw new ServletException(sm.getString("standardWrapper.initException", getName()), f);}
}

到此,可能很多人都以为执行到了 servlet.init(facade) 方法就结束了,认为下一步直接就执行了我们自定义的MyServlet类的init方法。但其实并不是,回头看看MyServlet类的init方法有传入参数吗?
并没有,因此代码执行到这里还没有结束!为了解决疑惑,我们需要继续往下看:

public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {...@Overridepublic void init(ServletConfig config) throws ServletException {this.config = config; // 绑定配置对象this.init(); // 调用真正的Servlet类init方法}...
}@WebServlet("/my")
public class MyServlet extends HttpServlet {... /*** 最终调用了MyServlet的init方法,完成初始化*/@Overridepublic void init() {System.out.println("servlet init...");}...
}

通过调试我们可以看到,其实是先调用了GenericServlet类的init方法,而在方法最后再调用Servlet的init方法,完成初始化。
GenericServlet类在前面已经说过了,它为Servlet接口中的方法提供了默认实现。而它的init方法也只是为当前Servlet绑定了配置对象而已。

此时,可能会有人提问了。调用GenericServlet类的init方法中传入的facade参数是什么?其实通过它的init方法的接收参数命名,我们可以猜测facade其实是ServletConfig对象,也就是封装了Servlet配置信息的对象。
为了验证我们的猜测,来看看StandardWrapper类的facade属性:

// 原来是StandardWrapperFacade类的实例对象
protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);/**
* 通过查看StandardWrapperFacade类,发现它实现了ServletConfig接口
* 观察其中的属性和方法,可以将StandardWrapperFacade看做是获取Servlet配置相关信息的类
*/
public final class StandardWrapperFacade implements ServletConfig {private final ServletConfig config; .../*** 将传入的StandardWrapper类的实例对象,绑定到本类中的config属性*/public StandardWrapperFacade(StandardWrapper config) {super();this.config = config;}... // 其他方法都是针对于config属性进行获取操作
}

在这里插入图片描述
Servlet执行阶段
Servlet的执行阶段,我们要了解的是当请求访问到达后端时候,服务器中它是如何判断这个请求,并准确的调用到响应的doXXX方法。

但针对于Servlet执行阶段的过程中,并不只是仅仅涉及到Servlet,其中还与Filter过滤器有很大的关系。为了更好理解这个阶段,在这里先提前说下Servlet的执行阶段的流程:

1.当启动Tomcat服务器时,服务器除了会封装Servlet相关配置信息之外,其实还会封装Filter过滤器的相关配置信息,以及对Filter过滤器进行创建并初始化。

2.当Tomcat服务器完全启动后,由前端发送request请求,此时服务器检测到有请求到达,此时会加载对应的Servlet实例对象(如果是初次访问,则创建),然后通过请求路径、请求类型、Servlet名三个条件对已存在的过滤器进行匹配筛选,然后将匹配筛选成功的过滤器组成过滤器链。

3.当过滤器链完成后,首先从链头开始调用每个过滤器的doFilter方法进行请求校验(我们自己实现的),一直到链中的所有过滤器doFilter方法都调用完毕,且都通过后,最后才会轮到对应的Servlet处理请求。

从以上分析的流程可知,在真正执行Servlet业务操作前,还需经过层层Filter过滤器的过滤。因此,如果想要完全理解Servlet的执行过程的话,需要先去了解JavaWeb三大组件——Filter过滤器源码解析。

在本文的话,就直接跳过执行Filter的过程,从调用Servlet方法入口着手:

/**
* 从以下代码可以得知,调用Servlet方法入口其实是:ApplicationFilterChain类的internalDoFilter方法
* 正印证了前面说的流程:需要执行过滤器链中的过滤器方法,最后才执行Servlet方法
*/
public final class ApplicationFilterChain implements FilterChain {...private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {... // 前面的代码都是执行过滤器的,此处无须关注// 当链中的过滤器都执行完了,才会执行以下代码try {...if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) && Globals.IS_SECURITY_ENABLED ) {final ServletRequest req = request;final ServletResponse res = response;Principal principal = ((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[]{req, res};SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args, principal);} else {servlet.service(request, response); // 调用Servlet的service方法(重点关注)}} ...}...
}

此时的调用 servlet.service(request, response) 的这个servlet其实就是MyServlet类的实例对象,它早在创建过滤器链的时候被设置进来。
因此,此处是直接调用MyServlet类中的service方法,而MyServlet类又是继承HttpServlet抽象类:

public abstract class HttpServlet extends GenericServlet {.../*** 最后调用的是HttpServlet类的service方法*/public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {HttpServletRequest  request;HttpServletResponse response;// 对传入的请求和响应对象进行类型强转,强转失败则抛出异常// 因为继承的是HttpServlet类,它是专门处理http请求的Servlet类,所以需要确保传入的请求和响应也是属于http范围内的try {request = (HttpServletRequest) req;response = (HttpServletResponse) res;} catch (ClassCastException e) {throw new ServletException("non-HTTP request or response");}service(request, response);}/*** 根据传入的request请求的类型进行匹配,执行子类中相应的重写方法(例如MyServlet的doGet、doPost方法)*/	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String method = req.getMethod(); // 获取request请求的类型if (method.equals(METHOD_GET)) { // GET类型long lastModified = getLastModified(req);if (lastModified == -1) {// servlet doesn't support if-modified-since, no reason// to go through further expensive logicdoGet(req, resp);} else {long ifModifiedSince;try {ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);} catch (IllegalArgumentException iae) {// Invalid date header - proceed as if none was setifModifiedSince = -1;}if (ifModifiedSince < (lastModified / 1000 * 1000)) {// If the servlet mod time is later, call doGet()// Round down to the nearest second for a proper compare// A ifModifiedSince of -1 will always be lessmaybeSetLastModified(resp, lastModified);doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);}}} else if (method.equals(METHOD_HEAD)) { // HEAD类型long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} else if (method.equals(METHOD_POST)) { // POST类型doPost(req, resp);} else if (method.equals(METHOD_PUT)) { // PUT类型doPut(req, resp);} else if (method.equals(METHOD_DELETE)) { // DELETE类型doDelete(req, resp);} else if (method.equals(METHOD_OPTIONS)) { // OPTIONS类型doOptions(req,resp);} else if (method.equals(METHOD_TRACE)) { // TRACE类型doTrace(req,resp);} else { // 若以上类型都不匹配,则向前端输出异常信息String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);}}...
}

最终根据request请求的类型匹配调用相应的方法(最常用的就是doGet、doPost):


@WebServlet("/my")
public class MyServlet extends HttpServlet {... @Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {resp.getWriter().write("Get Method");}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {resp.getWriter().write("Post Method");}...
}

到此,Servlet执行阶段源码分析结束。主要是创建过滤器链,并执行链中过滤器过滤方法,所有过滤方法通过后,最后执行Servlet的service方法,其中再根据request请求的类型匹配对应执行方法:
在这里插入图片描述


Filter:
在这里插入图片描述
Filter是过滤器的核心接口,其中定义了初始化方法、拦截请求后的要做的具体任务方法、销毁方法。

public interface Filter {//初始化方法,整个生命周期中只执行一次。//在init方法成功(失败如抛异常等)执行完前,不能提供过滤服务。//参数FilterConfig用于获取初始化参数public void init(FilterConfig filterConfig) throws ServletException;//执行过滤任务的方法,参数FilterChain表示过滤器链,doFilter方法中只有执行chain.doFilter()后才能调用下一个过滤器的doFilter方法//才能将请求交经下一个Filter或Servlet执行public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;//销毁方法,当移出服务时由web容器调用。整个生命周期中destroy方法只会执行一次//destroy方法可用于释放持有的资源,如内存、文件句柄等public void destroy();
}

拦截器使用例子

@WebFilter(urlPatterns = "/my")
public class MyFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) {System.out.println("filter init...");}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("在这个方法里面完成权限认证、信息加密等操作");chain.doFilter(request, response); // 执行下一个过滤器doFilter方法。如果在执行此句之前就return返回,则请求到此结束。}@Overridepublic void destroy() {System.out.println("filter destroy...");}
}

Filter拦截器使用起来十分简单,只要我们自定义的过滤器类实现Filter接口,覆盖其中的init、doFilter、destroy方法即可。
使用上来说,过滤器的实现可以说是有手就行了。但是其中的具体实现逻辑,比如说Filter是在什么时候起作用的?为什么可以不断的层级调用?等等问题。作为Java工程师来说,在工作中运用如此频繁的组件,是时候应该详细了解一番了。

Filter的执行流程

详细的说,Filter的执行流程主要分为两个部分:

  • 初始化部分:对于定义好的Filter过滤器(例如上面自定义的MyFilter),会首先创建过滤器对象,并保存到过容器中,并调用其init方法进行初始化。

    执行部分:当匹配到相应的请求路径时,首先会对该请求进行拦截,执行doFilter中的逻辑,若不通过则该请求则到此为止,不会继续往下执行(此时通常会进行重定向或者转发到其他地方进行处理);若通过则继续执行下一个拦截器的doFilter方法,直到指定的过滤器都执行完doFilter后,便执行Servlet中的业务逻辑。

  1. 初始化部分
    首先来了解下Filter的初始化流程,就拿上面自定义的MyFilter类来作为例子讲解(为了讲解源码时候排除不必要的干扰,此后的源码解析内容只针对关键部分代码进行讲解)。

当我们定义好MyFilter类后,便开启Tomcat服务器,开始启动程序。
通过调试发现初始化Filter的入口是:StandardContext类的startInternal方法

@Override
protected synchronized void startInternal() throws LifecycleException {... // 省略不必要代码fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); // 读取web.xml配置文件或者注解配置信息,创建并添加Filter... if (ok) {if (!filterStart()) { // 初始化Filter。若初始化成功则继续往下执行;若初始化失败则抛出异常,终止程序log.error(sm.getString("standardContext.filterFail"));ok = false;}}... 
}

可以看到,原来filterStart方法才是真正初始化Filter的方法。

那么在看filterStart方法源码之前,我们先来了解下一些相关的重点属性(有助于之后的源码阅读):

// filterConfigs是一个HashMap,以键值对的形式保存数据(key :value = 过滤器名 :过滤器配置信息对象)
private HashMap<String, ApplicationFilterConfig> filterConfigs = new HashMap<>();
// filterDefs同时也是一个HashMap,其中保存的数据是(过滤器名 :过滤器定义对象)
private HashMap<String, FilterDef> filterDefs = new HashMap<>();

看下其中的FilterDef、ApplicationFilterConfig这两个类,再进一步了解上面的两个属性:

FilterDef:

/**
* 来看下官方解释:
* Web应用程序的过滤器定义的表示形式,如部署描述符中<filter>元素中的所示。
* 例如:
* <filter>  
* 	<filter-name>MyFilter</filter-name>  
*   <filter-class>com.filter.MyFilter</filter-class>  
* </filter>  
* 
* 说白了,这个FilterDef其实就是封装了配置信息中<filter>标签当中的元素
* 其中就有三个重点属性:filterName、filterClass、filter
*/
public class FilterDef implements Serializable {...private String filterName = null; // 过滤器名,对应的是<filter-name>中的内容private String filterClass = null; // 过滤器全限定类名,对应的是<filter-class>中的内容(用于反射创建过滤器对象)private transient Filter filter = null; // 真正的过滤器对象(如:MyFilter实例对象)...
}

ApplicationFilterConfig:

/**
* ApplicationFilterConfig类主要用于保存自定义Filter的一些配置信息,例如过滤器名、初始化参数、过滤器定义对象等等
*/
public final class ApplicationFilterConfig implements FilterConfig, Serializable {.../*** 构造方法*/ApplicationFilterConfig(Context context, FilterDef filterDef)throws ClassCastException, ClassNotFoundException, IllegalAccessException,InstantiationException, ServletException, InvocationTargetException, NamingException,IllegalArgumentException, NoSuchMethodException, SecurityException {super();this.context = context; this.filterDef = filterDef; // 由此可知,ApplicationFilterConfig类中其实定义了过滤器定义对象// 初始化真正的过滤器对象if (filterDef.getFilter() == null) {getFilter();} else {this.filter = filterDef.getFilter();getInstanceManager().newInstance(filter);initFilter();}}...
}

了解完上面的属性,我们再来研究filterStart方法到底做了什么:

public boolean filterStart() {if (getLogger().isDebugEnabled()) { // 日志相关getLogger().debug("Starting filters");}boolean ok = true;synchronized (filterConfigs) { // 初始化过滤器属于同步操作filterConfigs.clear(); // 在初始化前,先清空for (Entry<String,FilterDef> entry : filterDefs.entrySet()) { // 循环遍历过滤器定义对象集合(filterDefs初始化在哪?)String name = entry.getKey(); // 获取过滤器名if (getLogger().isDebugEnabled()) { // 日志相关getLogger().debug(" Starting filter '" + name + "'");}try {ApplicationFilterConfig filterConfig = new ApplicationFilterConfig(this, entry.getValue()); // 创建过滤器配置对象filterConfigs.put(name, filterConfig); // 添加配置对象} catch (Throwable t) {t = ExceptionUtils.unwrapInvocationTargetException(t);ExceptionUtils.handleThrowable(t);getLogger().error(sm.getString("standardContext.filterStart", name), t);ok = false;}}}return ok;
}

此处,如果细心的朋友就会发现,filterStart方法中直接就拿filterDefs进行循环遍历获取过滤器名了。但是filterDefs是在哪里进行添加元素的呢?
回想一下,我们发现初始化过滤器的入口是StandardContext类中的startInternal方法,而在其中执行真正过滤器初始化方法前,还有一步:

fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null); // 读取web.xml配置文件或者注解配置信息,创建并添加FilterDef过滤器定义对象// 通过调试代码,可以找到其实是调用了ContextConfig类中configureContext方法,以下部分是该方法中的关键代码
for (FilterDef filter : webxml.getFilters().values()) { // 循环配置信息中的过滤器定义对象if (filter.getAsyncSupported() == null) {filter.setAsyncSupported("false");}context.addFilterDef(filter); // 将过滤器定义对象添加到容器中
}/**
* 最后发现fireLifecycleEvent方法最终调用的是StandardContext类中的addFilterDef方法
* 而参数filterDef正是容器context经过解析web.xml文件或者注解配置后创建的过滤器定义对象
* 但此时filterDef中的真正过滤器对象filter还未初始化,因此才会有之后的初始化过滤器方法
*/
public void addFilterDef(FilterDef filterDef) {synchronized (filterDefs) { // 同步添加过滤器定义对象filterDefs.put(filterDef.getFilterName(), filterDef); }fireContainerEvent("addFilterDef", filterDef);
}

明白了filterDefs的初始化,我们再回到filterStart方法,而此时执行到创建ApplicationFilterConfig过滤器配置对象:

/**
* 没错,就是调用这个构造方法创建的filterConfig
**/
ApplicationFilterConfig(Context context, FilterDef filterDef)throws ClassCastException, ClassNotFoundException, IllegalAccessException,InstantiationException, ServletException, InvocationTargetException, NamingException,IllegalArgumentException, NoSuchMethodException, SecurityException {super();this.context = context;this.filterDef = filterDef;// 现在重点来关注以下部分的代码// 从过滤器定义对象中获取真正的过滤器对象(正常来说,应该都是空。因为从之前filterDefs的添加元素逻辑来看,添加的过滤器定义对象中的过滤器对象都是空的)if (filterDef.getFilter() == null) { // 过滤器对象为空getFilter(); // 获取并初始化过滤器(此处没有进行接收结果,说明此处只是进行初始化)} else {this.filter = filterDef.getFilter(); // 绑定过滤器对象getInstanceManager().newInstance(filter);initFilter(); // 调用过滤器的init方法,初始化过滤器}
}

获取并初始化过滤器

Filter getFilter() throws ClassCastException, ClassNotFoundException, IllegalAccessException,InstantiationException, ServletException, InvocationTargetException, NamingException,IllegalArgumentException, NoSuchMethodException, SecurityException {if (this.filter != null) // 若ApplicationFilterConfig类中已存在过滤器对象,则直接返回return (this.filter);// 通过反射的方式创建过滤器对象,并绑定到配置对象的filter上String filterClass = filterDef.getFilterClass(); this.filter = (Filter) getInstanceManager().newInstance(filterClass);initFilter(); // 调用过滤器对象的init方法,初始化过滤器return (this.filter);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/317205.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud(微服务介绍,远程调用RestTemplate,注册中心Nacos,负载均衡Ribbon,环境隔离,进程和线程的区别)【详解】

目录 一、微服务介绍 1. 系统架构的演变 1 单体架构 2 分布式服务 3 微服务 2. SpringCloud介绍 SpringCloud简介 SpringCloud版本 3. 小结 二、远程调用RestTemplate【理解】 1. 服务拆分 1 服务拆分原则 2 服务拆分示例 1) 创建父工程 2) 准备用户服务 1. 用户…

Kubernetes TDengine 系列|安装 TDengine 的 Grafana 插件|Grafana监控TDengine数据

为了让Grafana 能够监控到TDengine 数据&#xff0c;快速集成搭建数据监测报警系统&#xff0c;所以直接安装TDengine 插件。 目录 一、安装 TDengine 的 Grafana 插件1、下载TDengine grafana插件2、解压到指定目录3、配置未签名插件 二、配置数据源&#xff0c;简单查询TDen…

python的输入输出(爽文,备忘,查询,友好)

Python中的输入输出主要涉及到输入函数和输出函数。 输出函数&#xff1a;print() print() 函数用于将信息输出到屏幕上。它可以输出字符串、变量的值&#xff0c;以及其他各种数据类型。 name "Alice" age 30 print("姓名:", name, "年龄:&quo…

气象数据nc数据矢量化处理解析及可视化

气象数据可视化是将气象学领域中复杂的数据集转化为图形或图像的过程&#xff0c;以直观展示天气现象、气候模式、趋势和预报结果。气象数据的可视化技术广泛应用于科学研究、气象预报、航空、航海、农业生产、灾害预警系统、城市规划、公众服务等领域。以下是一些关键的气象数…

mac虚拟机软件哪个好 mac虚拟机怎么安装Windows 苹果Mac电脑上受欢迎的主流虚拟机PK Parallels Desktop和VM

什么是苹果虚拟机&#xff1f; 苹果虚拟机是一种软件工具&#xff0c;它允许在非苹果硬件上运行苹果操作系统&#xff08;如ios&#xff09;。通过使用虚拟机&#xff0c;您可以在Windows PC或Linux上体验和使用苹果的操作系统&#xff0c;而无需购买苹果硬件。 如何使用苹果虚…

【智能算法】海象优化算法(WO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2024年&#xff0c;M Han等人受到海象群体自然行为启发&#xff0c;提出了海象优化算法&#xff08;Walrus Optimizer, WO&#xff09;。 2.算法原理 2.1算法思想 WO灵感来自海象通过接收关键信号…

设计模式-01 设计模式单例模式

设计模式-01 设计模式单例模式 目录 设计模式-01 设计模式单例模式 1定义 2.内涵 3.使用示例 4.具体代码使用实践 5.注意事项 6.最佳实践 7.总结 1 定义 单例模式是一种设计模式&#xff0c;它确保一个类只能被实例化一次。它通过在类内部创建类的唯一实例并提供一个全…

飞书API(6):使用 pandas 处理数据并写入 MySQL 数据库

一、引入 上一篇了解了飞书 28 种数据类型通过接口读取到的数据结构&#xff0c;本文开始探讨如何将这些数据写入 MySQL 数据库。这个工作流的起点是从 API 获取到的一个完整的数据&#xff0c;终点是写入 MySQL 数据表&#xff0c;表结构和维格表结构类似。在过程中可以有不同…

完美解决AttributeError: module ‘backend_interagg‘ has no attribute ‘FigureCanvas‘

遇到这种错误通常是因为matplotlib的后端配置问题。在某些环境中&#xff0c;尤其是在某些特定的IDE或Jupyter Notebook环境中&#xff0c;可能会因为后端配置不正确而导致错误。错误信息提示 module backend_interagg has no attribute FigureCanvas 意味着当前matplotlib的后…

首页最新 多IP浏览器防关联:如何配置多个独立且稳定的IP地址?

在互联网时代&#xff0c;IP地址的重要性不言而喻。然而&#xff0c;IP关联问题却成为一项令人担忧的隐私和安全挑战。针对这个问题&#xff0c;多IP浏览器是一种解决方案&#xff0c;可以帮助用户单独配置多个独立且稳定的IP地址&#xff0c;有效地防止IP关联。 一、IP关联是…

【Python小练】求斐波那契数列第n个数

题目 输出斐波那契数列第n个数。 分析 首先我们要知道&#xff0c;斐波那契数列&#xff0c;这个数列从第三位开始等于前两个数的和&#xff0c;要知道数列第n个数&#xff08;n>2&#xff09;&#xff0c;就要知道其前两相的值&#xff0c;着就需要用到递归了。来看一下吧…

开源、轻量、易用的服务器实时监控工具:哪吒探针

本文首发于只抄博客&#xff0c;欢迎点击原文链接了解更多内容。 前言 哪吒探针是一个开源、轻量、易用的服务器监控、运维工具&#xff0c;它有以下几个特点&#xff1a; 一键安装&#xff1a;可以一键安装面板与 Agent&#xff0c;并且支持 Linux、Windows、MacOS、OpenWRT…

纯血鸿蒙APP实战开发——发布图片评论

介绍 本示例将通过发布图片评论场景&#xff0c;介绍如何使用startAbilityForResult接口拉起相机拍照&#xff0c;并获取相机返回的数据。 效果图预览 使用说明 通过startAbilityForResult接口拉起相机&#xff0c;拍照后获取图片地址。 实现思路 创建CommentData类&#…

VSCode 配置 CMake

VSCode 配置 C/C 环境的详细过程可参考&#xff1a;VSCode 配置 C/C 环境 1 配置C/C编译环境 方案一 如果是在Windows&#xff0c;需要安装 MingW&#xff0c;可以去官网(https://sourceforge.net/projects/mingw-w64/)下载安装包。 注意安装路径不要出现中文。 打开 windows…

虚析构与纯虚析构

这里的new Cat("Tom"&#xff09;是由于基类函数中的构造函数里面带有string变量 1. 法一:利用虚函数&#xff0c;虚化基类中的析构函数 virtual ~Animal() { cout << "动物的析构函数调用" << endl; } 2. 法二:利用纯…

VitePress 构建的博客如何部署到 Netlify 平台?

VitePress 构建的博客如何部署到 Netlify 平台&#xff1f; 前言 之前写了篇文章【使用 Vitepress 构建博客并部署到 github 平台】&#xff0c;有个老哥说 github page 访问太慢了&#xff0c;希望放到 Netlify 平台上面。 咱也没部署过&#xff0c;就试了一下&#xff0c;发…

低功耗数字IC后端设计实现典型案例| UPF Flow如何避免工具乱用Always On Buffer?

下图所示为咱们社区低功耗四核A7 Top Hierarchical Flow后端训练营中的一个案例&#xff0c;设计中存在若干个Power Domain&#xff0c;其中Power Domain2(简称PD2)为default Top Domain&#xff0c;Power Domain1&#xff08;简称PD1&#xff09;为一个需要power off的domain&…

【再探】设计模式—抽象工厂及建造者模式

抽象工厂模式和建造者模式都属于创建型模式。两者都能创建对应的对象&#xff0c;而创建者模式更侧重于创建复杂对象&#xff0c;将对象的创建过程封装起来&#xff0c;让客户端不需要知道对象的内部细节。 1 抽象工厂模式 需求&#xff1a; 在使用工厂方法模式时&#xff0…

Postgresql 从小白到高手 十一 :数据迁移ETL方案

文章目录 Postgresql 数据迁移ETL方案1、Pg 同类型数据库2 、Pg 和 不同数据库 Postgresql 数据迁移ETL方案 1、Pg 同类型数据库 备份 : pg_dump -U username -d dbname -f backup.sql插入数据&#xff1a; psql -U username -d dbname -f backup.sqlpg_restore -U username…

【Godot4.2】自定义Todo清单类 - myTodoList

概述 在写myList类的时候&#xff0c;就想到可以写一个类似的Todo清单类。 基础思路 本质还是在内部维护一个数组&#xff0c;在其基础上进行增删改查操作的封装为了方便存储数据&#xff0c;编写一个自定义内置类TodoItem&#xff0c;内部数组就变成了Array[TodoItem]类型的…