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.mzph.cn/pingmian/5445.shtml

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

相关文章

区块链 | 由外部实体导致的 NFT 安全问题

&#x1f98a;原文&#xff1a; Understanding Security Issues in the NFT Ecosystem &#x1f98a;警告&#xff1a; 本文只记录了原文的第 6 节。 1 问题描述 NFT 所指向的数字资产&#xff08;图片、视频等&#xff09;必须是可以访问的&#xff0c;这样 NFT 才具有意义…

flake8,一个超强的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个超强的 Python 库 - flake8。 Github地址&#xff1a;https://github.com/PyCQA/flake8 Flake8是一个流行的Python库&#xff0c;用于检查代码质量和风格一致性&#xff0c;它集成了PyFlakes、…

powershell 注册全局热键——提升效率小工具

powershell 注册全局热键 01 前言 在处理一些重复工作问题的时候&#xff0c;想搞一个小工具&#xff0c;配合全局快捷键来提高效率。因为是Windows系统&#xff0c;想到C#&#xff0c;但是又不想用VS开发&#xff0c;因为那样不够灵活&#xff0c;没办法随时修改随时用&…

Windows系统下安装Mosquitto的步骤(2)

接前一篇文章&#xff1a;Windows系统下安装Mosquitto的步骤&#xff08;1&#xff09; 本文内容参考&#xff1a; Windows10上安装Mosquitto的步骤(win10、win11 安装mqtt) - IPS99技术分享 MQTT&#xff1a;windows环境下配置MQTT服务器&#xff08;mosquitto&#xff09;_…

2024.阳光能源追光计划暨大陆考察团交流分享会

近日大陆考察团抵达香港&#xff0c;受到了本司热情接待和安排。公司于4月27日下午举办了阳光能源追光计划主题交流会。 会上公司营销部总监张超&#xff0c;分享了阳光能源近几年的能源发展之路及公司新推出的追光计划&#xff0c;得到了大陆考察交流团团长杨国均先生的高度赞…

Vue.js课后练习(登录注册和大小比较)

第一题 请编写登录页面和注册页面&#xff0c;通过动态组件实现动态切换页面中显示的组件&#xff0c;效果如图1和图2所示。 图1 登录页面 图2 注册页面 代码&#xff1a; my.vue代码: <template>登录 </template><script setup> </script><st…

【Go 语言入门专栏】Go 语言的起源与发展

前言 Go 语言是当下最为流行的编程语言之一&#xff0c;大约在 2020、2021 年左右开始于国内盛行&#xff0c;许多大厂很早就将部分 Java 项目迁移到了 Go&#xff0c;足可看出其在性能方面的优越性。 相信各位都知道&#xff0c;在爬虫业务中&#xff0c;并发是一个关键的需…

Notes for the missing semester. Useful and basic knowledge about Linux.

The Shell Contents The first course is to introduce some simple commands. I’ll list some commands that I’m not familiar with: # --silent means dont give log info, # --head means we only want the http head. curl --head --silent bing.com.cn# cut --deli…

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

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

Java File类

1. File类概述 1.1 什么是File类 File是java.io包下作为文件和目录的类。File类定义了一些与平台无关的方法来操作文件&#xff0c;通过调用File类中的方法可以得到文件和目录的描述信息&#xff0c;包括名称、所在路径、读写性和长度等&#xff0c;还可以对文件和目录进行新建…

从Paint 3D入门glTF

Paint 3D Microsoft Paint 3D是微软的一款图像编辑软件&#xff0c;它是传统的Microsoft Paint程序的升级版。 这个新版本的Paint专注于三维设计和创作&#xff0c;使用户可以使用简单的工具创建和编辑三维模型。 Microsoft Paint 3D具有直观的界面和易于使用的工具&#xff0…

GitLab常用指令!(工作中常用的)

目录 克隆代码创建分支切换分支将代码提交到分支当中Merge合并 克隆代码 复制完地址&#xff0c;打开Git Bash&#xff0c;然后 git clone “复制的地址”创建分支 创建new_test分支 git branch new_test切换分支 切换到new_test分支 git checkout new_test将代码提交到分…

Hotcoin Research | 市场洞察:2024年4月22日-28日

加密货币市场表现 本周内加密大盘整体呈现出复苏状态&#xff0c;在BTC减半后进入到震荡上行周期。BTC在$62000-66000徘徊&#xff0c;ETH在$3100-3300徘徊&#xff0c;随着港交所将于 4 月 30 日开始交易嘉实基金的比特币和以太坊现货 ETF&#xff0c;周末行情有一波小的拉升…

vue+elementUI实现点击左右箭头切换按钮功能

原本是可以用el-tabs做的,就像下面的样式,但是领导说不行 最后用button和element里面的el-carousel(走马灯)结合了一下 长这样 感觉还不错 可以自己改样式 代码如下: <div class"drawer-carousel"><el-carousel arrow"always" :loop"false…

自动驾驶框架 UniAD环境部署

感谢大佬们的开源工作 UniAD-github地址-YYDS更多bev算法部署参考如果您觉得本帖对您有帮助&#xff0c;感谢您一键三连支持一波^_^ 统一自动驾驶框架 (UniAD) &#xff0c;第一个将全栈驾驶任务整合到一个深度神经网络中的框架&#xff0c;并可以发挥每个子任务以及各个模块的…

NASA数据集——VIIRS每日 L3深蓝气溶胶网格产品(AERDB_D3_VIIRS_SNPP),以 1 x 1 度

VIIRS/SNPP Deep Blue Level 3 monthly aerosol data, 1 degree x1 degree grid 简介 美国国家航空航天局&#xff08;NASA&#xff09;的可见红外成像辐射计套件&#xff08;VIIRS&#xff09;标准三级&#xff08;L3&#xff09;每月深蓝气溶胶产品来自苏米国家极轨伙伴关系…

开通Jetbrains个人账号,赠送这些付费插件

开通Jetbrains个人账号&#xff0c;或者Jetbrains现成账号的, 可赠送以下付费插件 现成账号&#xff1a;https://web.52shizhan.cn/activity/xqt8ly 个人账号&#xff1a;https://web.52shizhan.cn/legal 账号支持全家桶系列&#xff1a;AppCode,CLion,DataGrip,GoLand,Intell…

WebAuthn 无密码身份认证

文章目录 WebAuthn简介工作原理组成部分架构实现注册认证应用场景案例演示 WebAuthn简介 WebAuthn&#xff0c;全称 Web Authentication&#xff0c;是由 FIDO 联盟&#xff08;Fast IDentity Online Alliance&#xff09;和 W3C&#xff08;World Wide Web Consortium&#x…

java技术栈快速复习05_基础运维(linux,git)

Linux知识总览 linux可以简单的理解成和window一样的操作系统。 Linux和Windows区别 Linux是严格区分大小写的&#xff1b;Linux中一切皆是文件&#xff1b;Linux中文件是没有后缀的&#xff0c;但是他有一些约定俗成的后缀&#xff1b;Windows下的软件一般是无法直接运行的Li…

如何安全可控的进行跨区域数据交换,提高数据价值?

跨区域数据交换指的是在不同地理位置或不同网络环境下的数据传输和共享。随着数字化转型的加速&#xff0c;企业及组织越来越依赖于数据的流动来优化业务流程、增强决策制定和推动创新。然而&#xff0c;跨区域数据交换也带来了一系列的挑战和风险&#xff0c;主要包括&#xf…