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

news2024/11/19 11:26:37

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 {

    @Override
    public void init() {
        System.out.println("servlet init...");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.getWriter().write("Get Method");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.getWriter().write("Post Method");
    }

    @Override
    public 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<>();

    @Override
    public 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默认为false
    if (!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 now
                    SecurityUtil.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 {
	...
	@Override
	public void init(ServletConfig config) throws ServletException {
	    this.config = config; // 绑定配置对象
	    this.init(); // 调用真正的Servlet类init方法
	}
	...
}

@WebServlet("/my")
public class MyServlet extends HttpServlet {
	... 
	/**
	* 最终调用了MyServlet的init方法,完成初始化
	*/
    @Override
    public 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 logic
	            doGet(req, resp);
	        } else {
	            long ifModifiedSince;
	            try {
	                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
	            } catch (IllegalArgumentException iae) {
	                // Invalid date header - proceed as if none was set
	                ifModifiedSince = -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 less
	                maybeSetLastModified(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 {
	... 
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.getWriter().write("Get Method");
    }

    @Override
    protected 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 {

    @Override
    public void init(FilterConfig filterConfig) {
        System.out.println("filter init...");
    }

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

    @Override
    public 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的方法。

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

// filterDefs同时也是一个HashMap,其中保存的数据是(过滤器名 :过滤器定义对象)
private HashMap<String, FilterDef> filterDefs = new HashMap<>();

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

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

相关文章

vue如何发送请求给后端(包括前后端跨域)

目录 有哪些方法可以发送请求要请求先解决跨域问题代理服务器后端解决跨域问题 axios发送请求vue-resource发送请求 有哪些方法可以发送请求 以前可能了解过&#xff1a; xhr 即&#xff1a;new XMLHttpRequest()jQuery 即&#xff1a;$.get $.postaxios fetch 在vue中特有的…

27.统一网关Gateway-路由断言工厂

在配置文件中写的断言规则只是字符串&#xff0c;这些字符串会被Predicate Factory读取并处理&#xff0c;转变为路由判断的条件。 例如&#xff1a;Path /user/** 是按照路劲匹配&#xff0c;这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRouteP…

数据库锁介绍

数据库锁是一种同步机制&#xff0c;用于控制多个事务对共享资源的访问&#xff0c;防止并发操作造成的数据不一致。在数据库中&#xff0c;锁通常分为两种基本类型&#xff1a;排他锁&#xff08;Exclusive Locks&#xff09;和共享锁&#xff08;Shared Locks&#xff09;。排…

PotatoPie 4.0 实验教程(22) —— FPGA实现摄像头图像对数(log)变换

什么是图像的log变换&#xff1f; 总的来说&#xff0c;对数变换是一种常用的图像增强技术&#xff0c;可以改善图像的视觉质量、减少噪声以及突出图像中的细节&#xff0c;从而提高图像在视觉感知和分析中的效果和可用性。 图像的对数变换&#xff08;log transformation&am…

xLua详解

目录 环境准备xLua导入 C#调用LuaLua解析器Lua文件加载重定向Lua解析管理器全局变量的获取全局函数的获取List和Dictionary映射table类映射table接口映射tableLuaTable映射table Lua调用C#准备工作Lua使用C#类Lua调用C#枚举Lua使用C# 数组 List 字典数组List字典 Lua使用C#扩展…

锂电池SOH预测 | 基于CNN-GRU的锂电池SOH预测(matlab)

锂电池SOH预测 锂电池SOH预测完整代码锂电池SOH预测 锂电池的SOH(状态健康度)预测是一项重要的任务,它可以帮助确定电池的健康状况和剩余寿命,从而优化电池的使用和维护策略。 SOH预测可以通过多种方法实现,其中一些常用的方法包括: 容量衰减法:通过监测电池的容量衰减…

图像处理ASIC设计方法 笔记19 连通域标记ASIC系统设计

目录 核心的模块有:标记ASIC的工作流程如下:该芯片的系统结构具有如下特点:P131 第6章 连通域标记与轮廓跟踪 本章节讲述了多值分割图像连通域标记芯片的系统设计 多值分割图像连通域标记芯片(以下简称"标记芯片",也称"标记 ASIC"),完成图像连通域标…

PotatoPie 4.0 实验教程(27) —— FPGA实现摄像头图像拉普拉斯边缘提取

拉普拉斯边缘提取有什么作用&#xff1f; 拉普拉斯边缘检测是一种常用的图像处理技术&#xff0c;用于检测图像中的边缘和边界。它的主要作用包括&#xff1a; 边缘检测&#xff1a;拉普拉斯算子可以帮助检测图像中的边缘&#xff0c;即图像中亮度快速变化的位置。这些边缘通常…

MAC有没有免费NTFS tuxera激活码 tuxera破解 tuxera for mac2023序列号直装版 ntfs formac教程

Tuxera NTFS 2023破解版是一款非常好用的在线磁盘读写工具&#xff0c;该软件允许mac用户在Windows NTFS格式的硬盘上进行读写操作&#xff0c;Mac的文件系统是HFS&#xff0c;而Windows则使用NTFS格式&#xff0c;这导致在Mac系统上不能直接读写Windows格式的硬盘。然而&#…

什么ISP是住宅IP,和普通IP有什么区别?

ISP&#xff08;Internet Service Provider&#xff09;即互联网服务提供商&#xff0c;是向广大用户综合提供互联网接入业务、信息业务和增值业务的电信运营商。住宅IP&#xff0c;也称为家庭IP&#xff0c;是指由ISP分配给家庭或个人用户的IP地址。这些IP地址是真实的&#x…

【深度学习】Yolov8使用心得

兜兜转转&#xff0c;原本以为和yolov没啥关系了&#xff0c;但是新公司又回到了yolov侧。 yolov8 可以用pip的方式安装&#xff0c;以package的三方软件包形式&#xff0c;隐藏了很多细节。当然你也可以从git上把全套代码down下来。 1.分类模型 1.1 改错误 位置&#xff1a…

jenkins教程

jenkins 一、简介二、下载安装三、配置jdk、maven和SSH四、部署微服务 一、简介 Jenkins是一个流行的开源自动化服务器&#xff0c;用于自动化软件开发过程中的构建、测试和部署任务。它提供了一个可扩展的插件生态系统&#xff0c;支持各种编程语言和工具。 Jenkins是一款开…

boot https ssl 使用http协议访问报错

在springboot中配置ssl以后&#xff0c; 再次使用http访问对应的接口就会报错 可以考虑如下设置&#xff0c;将http访问的端口重定向到https对应的端口 import org.apache.catalina.Context; import org.apache.catalina.connector.Connector; import org.apache.tomcat.util…

分段函数拟合-施加分段点连续约束条件|【Matlab源码+视频介绍】

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《复杂函数拟合案例分享》本专栏旨在提供 1.以案例的形式讲解各类复杂函数拟合的程序实现方法&#xff0c;并提供所有案例完整源码&#xff1b;2.…

CISSP通关学习笔记:共计 9 个章节(已完结)

1. 笔记说明 第 0 章节为开篇介绍&#xff0c;不包括知识点。第 1 - 8 章节为知识点梳理汇总&#xff0c;8 个章节的知识框架关系如下图所示&#xff1a; 2. 笔记目录 「 CISSP学习笔记 」0.开篇「 CISSP学习笔记 」1.安全与风险管理「 CISSP学习笔记 」2.资产安全「 CISSP…

MIPS32 指令架构

指令格式 R 类型 说明&#xff1a; 用于寄存器和寄存器操作 参数说明: Op: 指令操作码Rs: 第一个源操作数寄存器号&#xff0c;参与运算使用Rd: 目的操作数寄存器号&#xff0c;保存结果使用Shamt: 位偏移量&#xff0c;仅在位移指令使用&#xff0c;在此直接置0Func: 指令函…

MF(推荐系统的矩阵分解技术)论文笔记

论文概述 推荐系统的矩阵分解技术可以为用户提供更为准确的个性化推荐&#xff0c;对比传统的近邻技术&#xff0c;矩阵分解技术可以纳入更多信息&#xff0c;如隐式反馈、时间效应和置信度 近邻技术&#xff1a;基于用户或物品之间的相似性进行推荐&#xff0c;当用户之间已…

【java数据结构之八大排序(上)-直接插入排序,希尔排序,选择排序,堆排序,向下调整(大根堆,小根堆)等知识详解】

&#x1f308;个人主页&#xff1a;努力学编程’ ⛅个人推荐&#xff1a;基于java提供的ArrayList实现的扑克牌游戏 |C贪吃蛇详解 ⚡学好数据结构&#xff0c;刷题刻不容缓&#xff1a;点击一起刷题 &#x1f319;心灵鸡汤&#xff1a;总有人要赢&#xff0c;为什么不能是我呢 …

RabbitMQ知识点总结(一)

为什么要使用RabbitMQ? 异步&#xff0c;解耦&#xff0c;削峰。 异步 提高效率&#xff1b;一个挂了&#xff0c;另外的服务不受影响。 解耦 增加或减少服务比较方便。 削峰 每天0点到16点&#xff0c;A系统风平浪静&#xff0c;每秒并发数量就100个。结果每次到了16点到…

[笔试强训]day3

1.简写单词 题目链接&#xff1a;简写单词_牛客题霸_牛客网 思路&#xff1a;利用scanf读取特性&#xff0c;因为scanf是以空格&#xff0c;换行或者文件末尾为结束标志。 代码&#xff1a; #include <iostream> using namespace std;int main() {char str[100] {0}…