Spring框架扩展Servlet

零、前言

Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。那么,在Web应用开发过程中,我们是如何享受到Spring框架带给我们的各种便利的呢?

一、入口

在Servlet开发中,有两个常用入口可以对Servlet框架进行扩展。

  • 自定义Listener,用于在Web应用启动时进行业务逻辑处理,可定义多个
  • 自定义Servlet,用于在处理请求时整合其他框架,可定义多个

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <context-param>
    <param-name>servletDemoDate</param-name>
    <param-value>20200801</param-value>
  </context-param>

  <!-- 注册自定义listener,可多个 -->
  <listener>
    <listener-class>com.dfx.servletdemo.listener.DemoListener</listener-class>
  </listener>

  <!-- 注册自定义servlet,可多个 -->
  <servlet>
    <servlet-name>demoServlet</servlet-name>
    <servlet-class>com.dfx.servletdemo.servlet.DemoServlet</servlet-class>
    <init-param>
      <param-name>initParam</param-name>
      <param-value>demo-111</param-value>
    </init-param>
    <load-on-startup>4</load-on-startup>
  </servlet>

  <servlet>
    <servlet-name>demo2Servlet</servlet-name>
    <servlet-class>com.dfx.servletdemo.servlet.Demo2Servlet</servlet-class>
    <init-param>
      <param-name>initParam</param-name>
      <param-value>demo2-222</param-value>
    </init-param>
    <load-on-startup>4</load-on-startup>
  </servlet>

  <!-- 使用tomcat默认servlet default来处理html静态资源 -->
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>
  <!-- 使用tomcat默认servlet 来处理jsp静态资源 -->
  <servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
  </servlet-mapping>
  <!-- 自定义demoServlet -->
  <servlet-mapping>
    <servlet-name>demoServlet</servlet-name>
    <url-pattern>*.demo</url-pattern>
  </servlet-mapping>
  <!-- 自定义demoServlet2 -->
  <servlet-mapping>
    <servlet-name>demo2Servlet</servlet-name>
    <url-pattern>*.demo2</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

二、Spring结合Servlet

2.1 结合点

1. Listener、Servlet

web.xml是Java Web项目的常见配置文件,Spring与Servlet的结合点也在Web.xml中。

两个结合点:

  • listener
  • servlet

Spring框架通过实现Listener、Servlet使得我们可以在基于Servlet框架的Web开发中享受到Spring框架的各种特性。

常见web.xml样例如下:

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml</param-value>
  </context-param>

  <!-- 配置上下文载入器 -->
  <!-- 上下文载入器载入除DispatcherServlet载入的配置文件之外的其他上下文配置文件 -->
  <!-- 最常用的上下文载入器是一个Servlet监听器,其名称为ContextLoaderListener -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <!--配置dispatcher.xml作为mvc的配置文件-->
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
    </init-param>
    <!--load-on-startup 配置一般是大于0的数值,表示启动顺序。 如果相同,则表明这两个Servlet启动顺序随意。 -->
    <!-- 不配置该参数时,默认懒加载,只有收到请求时才会实例化DispatcherServlet -->
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

说明:

标签声明的是ServletContextListener的实现类ContextLoaderListener,用于监听ServletContext的生命周期变化。在ServletContext启动(即应用启动)时,ContextLoaderListener负责加载Spring容器;在ServletContext关闭(即应用销毁)时,ContextLoaderListener将销毁Spring容器。

标签声明的是Servlet的实现类DispatcherServlet。在Servlet加载时,DispatcherServlet#init方法将加载启动Spring容器,使得我们可以使用Spring中的Bean来处理请求。当然,是否生效也与当前Servlet配置的路径映射有关。

ContextLoaderListener、DispatcherServlet类将Spring容器与Servlet框架相结合,使我们可以按照Spring规范来方便的开发Web应用。

2. Filter

Filter也是Servlet框架中常用的类,用于在请求到达Servlet前,对请求进行一些预处理操作;或者对Servlet的响应做一些后处理操作。

A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both.
在Servlet框架中,对请求的处理是一个栈型结构,如下:

filter作用机制
在Filter中,也可以使用Spring容器的bean管理功能。

配置样例如下:

    <!--    使用Springbean的filter-->
    <!--    DelegatingFilterProxy类遵循filter-name-bean的原则,会根据web.xml中filter-name的值查找到spring配置文件中id与filter-name相同的值,然后把接受到的处理信息传递给相对应的类处理。-->
    <filter>
        <filter-name>springFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <!-- 如果要保留Filter原有的init,destroy方法的调用,还需要配置初始化参数targetFilterLifecycle为true,该参数默认为false. -->
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>springFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

三、ContextLoaderListener

ContextLoaderListener的类层次结构如下。
ContextLoaderListener类图
Spring通过自定义ContextLoaderListener类实现了Servlet框架中的ServletContextListener接口。

在将ContextLoaderListener配置到web.xml中后。

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

ContextLoaderListener对应的方法就可以在ServletContext启动、关闭时被调用到。

3.1 #contextInitialized

	/**
	 *
	 *  初始化web应用上下文
	 * 
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

作用:在ServletContext初始化(即Web应用启动时)后,初始化Spring的WebApplicationContext。

3.2 #contextDestroyed

	/**
	 *
	 * 关闭web应用上下文
	 * 
	 * Close the root web application context.
	 */
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}

作用:在销毁ServletContext(关闭Web应用)时,关闭Spring的WebApplicationContext。

3.3 #contextInitialized时序图

这里重点关注下,Spring容器是如何引入到基于Servlet的Web应用开发中的,ContextLoaderListener#contextInitialized方法的时序图如下:
在这里插入图片描述

3.4 分析

ContextLoaderListener实现了ServletContextListener接口。作为一个ServletContextListener,受Servlet容器的管理,使得我们可以在Web应用启动时,初始化Spring容器,便于后期使用。

四、DispatcherServlet

DispatcherServlet的类层次结构简图如下:
DispatcherServlet类图
Spring通过DispatcherServlet类扩展了Servlet框架中的HttpServlet抽象类。

4.1 DispatcherServlet初始化

Servlet的初始化入口为 Servlet#init方法,让我们来看下DispatcherServlet的初始化过程。

4.1.1 #init(ServletConfig)

实际代码位置:DispatcherServlet父类GenericServlet#init(ServletConfig)

    /**
     * Called by the servlet container to indicate to a servlet that the
     * servlet is being placed into service.  See {@link Servlet#init}.
     *
     * <p>This implementation stores the {@link ServletConfig}
     * object it receives from the servlet container for later use.
     * When overriding this form of the method, call 
     * <code>super.init(config)</code>.
     *
     * @param config 			the <code>ServletConfig</code> object
     *					that contains configuration
     *					information for this servlet
     *
     * @exception ServletException 	if an exception occurs that
     *					interrupts the servlet's normal
     *					operation
     * 
     * @see 				UnavailableException
     */
    public void init(ServletConfig config) throws ServletException {
			this.config = config;
			this.init();
    }

作用:通过属性config保存了#init方法中传入的ServletConfig实例。提供了无参#init方法,供子类扩展。

4.1.2 #init()

实际代码位置:DispatcherServlet父类HttpServletBean#init()

	/**
	 * Map config parameters onto bean properties of this servlet, and
	 * invoke subclass initialization.
	 * @throws ServletException if bean properties are invalid (or required
	 * properties are missing), or if subclass initialization fails.
	 */
	@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

作用:

  • 将DispatcherServlet的配置参数,以属性方式存储到DispatcherServlet实例中;
  • 提供#initServletBean方法,留给子类扩展;
4.1.3 #initServletBean

实际代码位置:DispatcherServlet父类FrameworkServlet#initServletBean()

	/**
	 * Overridden method of {@link HttpServletBean}, invoked after any bean properties
	 * have been set. Creates this servlet's WebApplicationContext.
	 */
	@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		catch (RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}

作用:

  • initWebApplicationContext:初始化WebApplicationContext实例;
  • 提供initFrameworkServlet方法,供子类扩展;

4.2 DispatcherServlet#init(ServletConfig)时序图

在DispatcherServlet#init中创建Spring容器的详细图如下:
DispatcherServlet#init时序图

4.3 分析

在Servlet框架中,Servlet是用于处理Web客户端请求,并给出响应的。

Spring通过DispatcherServlet实现了Servlet接口,并在#init(ServletConfig)方法中创建了Spring WebApplicationContext,使得我们可以在处理Web请求时,也能使用Spring容器管理的bean。

五、这俩Spring WebApplicationContext啥关系?

标签启动的Spring容器是标签启动的Spring容器的父容器,通过DispatcherServlet#init(ServletConfig)时序图中的11.setParent可知,可对照源码来查看。

子容器能够使用父容器中的Bean,反之不行。

六、DelegatingFilterProxy

最后,来看下在Filter中,如何使用Spring容器。
这里主要使用org.springframework.web.filter.DelegatingFilterProxy构造了代理。

6.1 类结构

1. DelegatingFilterProxy类

DelegatingFilterProxy类
DelegatingFilterProxy类中包含:

  • delegate:Filter类型的Spring bean,对请求执行实际的预处理、后处理。
  • webApplicationContext:用于存储在ContextLoaderListener#contextInitialized中创建的WebApplicationContext。
2. 类结构

DelegatingFilterProxy类结构
从DelegatingFilterProxy类图可知,DelegatingFilterProxy实现了Filter接口,而#init方法是Filter实例的初始化入口。

6.2 执行逻辑

1. DelegatingFilterProxy#init
void init(FilterConfig filterConfig)

init方法是Filter类的初始化方法,DelegatingFilterProxy继承于GenericFilterBean,而GenericFilterBean#init是个final方法,不允许子类重写,源码如下:

	/**
	 * Standard way of initializing this filter.
	 * Map config parameters onto bean properties of this filter, and
	 * invoke subclass initialization.
	 * @param filterConfig the configuration for this filter
	 * @throws ServletException if bean properties are invalid (or required
	 * properties are missing), or if subclass initialization fails.
	 * @see #initFilterBean
	 */
	@Override
	public final void init(FilterConfig filterConfig) throws ServletException {
		Assert.notNull(filterConfig, "FilterConfig must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
		}

		this.filterConfig = filterConfig;

		// Set bean properties from init parameters.
		PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
				Environment env = this.environment;
				if (env == null) {
					env = new StandardServletEnvironment();
				}
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				String msg = "Failed to set bean properties on filter '" +
					filterConfig.getFilterName() + "': " + ex.getMessage();
				logger.error(msg, ex);
				throw new NestedServletException(msg, ex);
			}
		}

		// Let subclasses do whatever initialization they like.
    // 提供了待扩展方法,用于子类重写
		initFilterBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
		}
	}

DelegatingFilterProxy#initFilterBean方法

	@Override
	protected void initFilterBean() throws ServletException {
		synchronized (this.delegateMonitor) {
			if (this.delegate == null) {
				// If no target bean name specified, use filter name.
                // 若没指定属性targetBeanName,那么把filterName赋值给targetBeanName
				if (this.targetBeanName == null) {
					this.targetBeanName = getFilterName();
				}
				// Fetch Spring root application context and initialize the delegate early,
				// if possible. If the root application context will be started after this
				// filter proxy, we'll have to resort to lazy initialization.
                // 获取Spring容器
				WebApplicationContext wac = findWebApplicationContext();
				if (wac != null) {
					this.delegate = initDelegate(wac);
				}
			}
		}
	}

DelegatingFilterProxy#initDelegate方法

	/**
	 * Initialize the Filter delegate, defined as bean the given Spring
	 * application context.
	 * <p>The default implementation fetches the bean from the application context
	 * and calls the standard {@code Filter.init} method on it, passing
	 * in the FilterConfig of this Filter proxy.
	 * @param wac the root application context
	 * @return the initialized delegate Filter
	 * @throws ServletException if thrown by the Filter
	 * @see #getTargetBeanName()
	 * @see #isTargetFilterLifecycle()
	 * @see #getFilterConfig()
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        // 获取Spring容器中name为targetBeanName的Filter bean
		Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
		if (isTargetFilterLifecycle()) {
            // 如果期望在启动时,指定被代理类的init方法,必须将targetFilterLifecycle属性设置为true
            // 调用被代理类的init方法,并传入filterConfig对象
			delegate.init(getFilterConfig());
		}
		return delegate;
	}

至此,DelegatingFilterProxy及其代理类delegate初始化完成。

2. DelegatingFilterProxy#doFilter

#doFilter方法是Filter实例的实际请求处理方法。

DelegatingFilterProxy#doFilter

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// Lazily initialize the delegate if necessary.
        // 延迟加载逻辑
		Filter delegateToUse = this.delegate;
		if (delegateToUse == null) {
			synchronized (this.delegateMonitor) {
				delegateToUse = this.delegate;
				if (delegateToUse == null) {
					WebApplicationContext wac = findWebApplicationContext();
					if (wac == null) {
						throw new IllegalStateException("No WebApplicationContext found: " +
								"no ContextLoaderListener or DispatcherServlet registered?");
					}
					delegateToUse = initDelegate(wac);
				}
				this.delegate = delegateToUse;
			}
		}

		// Let the delegate perform the actual doFilter operation.
        // 通过被代理类来处理请求
		invokeDelegate(delegateToUse, request, response, filterChain);
	}

DelegatingFilterProxy#invokeDelegate

	/**
	 * Actually invoke the delegate Filter with the given request and response.
	 * @param delegate the delegate Filter
	 * @param request the current HTTP request
	 * @param response the current HTTP response
	 * @param filterChain the current FilterChain
	 * @throws ServletException if thrown by the Filter
	 * @throws IOException if thrown by the Filter
	 */
	protected void invokeDelegate(
			Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

        // 通过代理类的doFilter方法来处理请求
		delegate.doFilter(request, response, filterChain);
	}

在实际处理请求时,DelegatingFilterProxy将请求转发给被代理对象delegate处理。

3. DelegatingFilterProxy#destory

#destroy方法负责Filter实例的销毁。

DelegatingFilterProxy#destory

	@Override
	public void destroy() {
		Filter delegateToUse = this.delegate;
		if (delegateToUse != null) {
      // 销毁被代理类
			destroyDelegate(delegateToUse);
		}
	}

DelegatingFilterProxy#destroyDelegate

	/**
	 * Destroy the Filter delegate.
	 * Default implementation simply calls {@code Filter.destroy} on it.
	 * @param delegate the Filter delegate (never {@code null})
	 * @see #isTargetFilterLifecycle()
	 * @see javax.servlet.Filter#destroy()
	 */
	protected void destroyDelegate(Filter delegate) {
		if (isTargetFilterLifecycle()) {
      // 如果期望在销毁时,指定被代理类的destory方法,必须将targetFilterLifecycle属性设置为true
			delegate.destroy();
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值