10-Spring支持Servlet3.0新特性

Spring支持Servlet3.0

由上一节可以知道,利用Servlet3.0的新特性,我们在编写Servlet的时候可以不使用xml配置文件了,直接使用注解就可以了。本节将会介绍Spring是如何支持Servlet3.0。

首先导入SpringMVC的依赖

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.1.14.RELEASE</version>
    </dependency>
</dependencies>

查看spring-web-5.1.14.RELEASE依赖目录下的/META-INF/services/javax.servlet.ServletContainerInitializer文件,文件内容如下

org.springframework.web.SpringServletContainerInitializer

因此我们可以知道,容器在启动时将会调用这个类的#onStartup(...) 方法。

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {
		List<WebApplicationInitializer> initializers = new LinkedList<>();
		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// 如果waiClass不是一个接口、不是一个抽象类、并且是WebApplicationInitializer类的子类,则实例化并加到initializers
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}
        
		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}
		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
        // initializers 列表并执行onStartup方法
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

代码比较简单,利用@HandlesTypes(WebApplicationInitializer.class)注解,收集所有不是接口、不是抽象类并且是WebApplicationInitializer接口的子类,最后调用#onStartup(ServletContext servletContext)方法。

WebApplicationInitializer有三个子类,分别是AbstractAnnotationConfigDispatcherServletInitializerAbstractDispatcherServletInitializerAbstractContextLoaderInitializer

这三个类的关键代码如下所示

AbstractAnnotationConfigDispatcherServletInitializer

// SpringMVC里面有两个容器, 
// 一个是父容器=》存储普通Bean,如Service、Component等
// 一个是子容器=》存储Controller相关Bean
// AbstractAnnotationConfigDispatcherServletInitializer.java
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
		extends AbstractDispatcherServletInitializer {
    // 创建一个父容器, Root WebApplicationContext
	@Override
	@Nullable
	protected WebApplicationContext createRootApplicationContext() {
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
			context.register(configClasses);
			return context;
		}
		else {
			return null;
		}
	}
	// 创建一个子容器, Servlet WebApplicationContext
	@Override
	protected WebApplicationContext createServletApplicationContext() {
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			context.register(configClasses);
		}
		return context;
	}
	// 指定父容器的配置类, 由子类实现
	@Nullable
	protected abstract Class<?>[] getRootConfigClasses();
	// 指定子容器的配置类, 由子类实现
	@Nullable
	protected abstract Class<?>[] getServletConfigClasses();

}

AbstractDispatcherServletInitializer

// AbstractDispatcherServletInitializer.java
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
	// 默认的Servlet名字
	public static final String DEFAULT_SERVLET_NAME = "dispatcher";
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		// 调用父类的onStartup方法, 这里会新建父容器, Root WebApplicationContext
        super.onStartup(servletContext);
        // 注册DispatcherServlet, 这里会新建子容器, Servlet WebApplicationContext
        // 子容器以 Root WebApplicationContext 作为父容器
		registerDispatcherServlet(servletContext);
	}
	// 注册DispatcherServlet
	protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");
        // 创建一个Servlet WebApplicationContext
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
        // 创建 DispatcherServlet
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
		// 向ServletContext中注册DispatcherServlet
		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		if (registration == null) {
			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
					"Check if there is another servlet registered under the same name.");
		}
		registration.setLoadOnStartup(1);
        // 获取Servlet拦截地址
		registration.addMapping(getServletMappings());
		registration.setAsyncSupported(isAsyncSupported());
        // 获取过滤器
		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
                // 往ServletContext中注册过滤器
				registerServletFilter(servletContext, filter);
			}
		}
		customizeRegistration(registration);
	}
	// 获取Servlet名字
	protected String getServletName() {
		return DEFAULT_SERVLET_NAME;
	}
	// 创建一个子容器Servlet WebApplicationContext, 由子类实现
	protected abstract WebApplicationContext createServletApplicationContext();
	// 创建 DispatcherServlet
	protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
        // 这里新建一个DispatcherServlet对象, 并且将 Servlet WebApplicationContext 传递进去
		return new DispatcherServlet(servletAppContext);
	}
	// 指定 DispatcherServlet 拦截规则, 由子类实现
	protected abstract String[] getServletMappings();
	// 省略代码......
}

AbstractContextLoaderInitializer

// AbstractContextLoaderInitializer.java
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
	/** Logger available to subclasses. */
	protected final Log logger = LogFactory.getLog(getClass());
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		registerContextLoaderListener(servletContext);
	}
    // 往ServletContext中注册一个ContextLoaderListener
	protected void registerContextLoaderListener(ServletContext servletContext) {
        // 创建一个Root ApplicationContext
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
            // ContextLoaderListener实现了ServletContextListener接口
            // 在 Servlet 容器启动时,例如 Tomcat、Jetty 启动
            // 则会被 ContextLoaderListener 监听到
            // 从而调用contextInitialized(ServletContextEvent event)方法
            // 初始化 Root WebApplicationContext 容器。 
            // 这里新建 ContextLoaderListener 对象并且将Root ApplicationContext传递过去
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			listener.setContextInitializers(getRootApplicationContextInitializers());
            // 给ServletContext注册一个监听器
			servletContext.addListener(listener);
		}
		else {
			logger.debug("No ContextLoaderListener registered, as " +
					"createRootApplicationContext() did not return an application context");
		}
	}
	// 创建一个父容器, Root ApplicationContext, 由子类实现
	@Nullable
	protected abstract WebApplicationContext createRootApplicationContext();
    // 省略了部分代码......
}

前面提到了 org.springframework.web.context.ContextLoaderListenerorg.springframework.web.servlet.DispatcherServlet 对象 。

ContextLoaderListener 对象。这是一个 javax.servlet.ServletContextListener 对象,会初始化一个Root Spring WebApplicationContext 容器。DispatcherServlet对象。这是一个 javax.servlet.http.HttpServlet 对象,它除了拦截我们制定的请求外,也会初始化一个属于它的 Spring WebApplicationContext 容器,并且它是以Root Spring WebApplicationContext 作为父容器的。

Root WebApplicationContext

我们已经看到,Root WebApplicationContext 容器的初始化,通过 ContextLoaderListener 来实现。在 Servlet 容器启动时,例如 Tomcat、Jetty 启动,则会被 ContextLoaderListener 监听到,从而调用 #contextInitialized(ServletContextEvent event) 方法,初始化 Root WebApplicationContext 容器。

ContextLoaderListener实现了ServletContextListener接口并且继承了ContextLoader类。

由前面的分析可以知道,创建ContextLoaderListener对象时传递了一个WebApplicationContext对象进去。

// ContextLoaderListener.java
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    // 无参构造方法
	public ContextLoaderListener() {
	}
    // 有参构造方法
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}
	@Override
	public void contextInitialized(ServletContextEvent event) {
        // 调用父类 ContextLoader 的 
        // #initWebApplicationContext(ServletContext servletContext) 方法,
        // 初始化 WebApplicationContext 对象
		initWebApplicationContext(event.getServletContext());
	}
    // 销毁 WebApplicationContext 容器的逻辑。
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

接着看ContextLoader相关实现

public class ContextLoader {
    // 无参构造函数
	public ContextLoader() {
	}
    // 有参
	public ContextLoader(WebApplicationContext context) {
		this.context = context;
	}
    // 初始化 WebApplicationContext 对象
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        // 若已经存在 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 对应的 WebApplicationContext 对象,则抛出 IllegalStateException 异常。
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}
        // 记录开始时间
		long startTime = System.currentTimeMillis();
		try {
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
                    // 配置 ConfigurableWebApplicationContext 对象,并刷新容器
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
             // 保存到 servletContext 中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
            // 保存到 currentContext 或 currentContextPerThread 中
			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}
    // 配置 ConfigurableWebApplicationContext 对象,并刷新容器
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}
		wac.setServletContext(sc);
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
        // 执行一些自定义的初始化
		customizeContext(sc, wac);
        // 刷新 context ,执行初始化
		wac.refresh();
	}
}

Servlet WebApplicationContext

Servlet WebApplicationContext 容器的初始化,是在 DispatcherServlet 初始化的过程中执行。 由前面的分析可以知道,创建DispatcherServlet 对象时传递了一个WebApplicationContext对象进去。

DispatcherServlet继承了FrameworkServlet,FrameworkServlet继承了HttpServletBean,HttpServletBean继承了HttpServlet。

HttpServletBean实现了HttpServlet的init()方法

// HttpServletBean.java
public final void init() throws ServletException {
		// Set bean properties from init parameters.
    	// 将init parameters封装到 PropertyValues pvs 中
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
                // 将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。
                // 从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象。
                // BeanWrapper 是 Spring 提供的一个用来操作 Java Bean 属性的工具,使用它可以直接修改一个对象的属性。
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
                // 注册自定义属性编辑器,一旦碰到 Resource 类型的属性,
                // 将会使用 ResourceEditor 进行解析
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
                // 空实现,留给子类覆盖
				initBeanWrapper(bw);
                // 将 pvs 注入到该 BeanWrapper 对象中
				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.
    	// 子类来实现,实现自定义的初始化逻辑
    	// FrameworkServlet实现了这个方法
		initServletBean();
	}

FrameworkServlet#initServletBean()

// FrameworkServlet.java
protected final void initServletBean() throws ServletException {
    // 打日志
    getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
    if (logger.isInfoEnabled()) {
        logger.info("Initializing Servlet '" + getServletName() + "'");
    }
    // 记录开始时间
    long startTime = System.currentTimeMillis();
    try {
        // 初始化 Servlet WebApplicationContext 对象
        this.webApplicationContext = initWebApplicationContext();
        // 空实现。子类有需要,可以实现该方法,实现自定义逻辑
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }
    // 打日志
    if (logger.isDebugEnabled()) {
        String value = this.enableLoggingRequestDetails ?
            "shown which may lead to unsafe logging of potentially sensitive data" :
        "masked to prevent unsafe logging of potentially sensitive data";
        logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
                     "': request parameters and headers will be " + value);
    }
    // 打日志
    if (logger.isInfoEnabled()) {
        logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
    }
}

FrameworkServlet#initWebApplicationContext()

// FrameworkServlet.java
protected WebApplicationContext initWebApplicationContext() {
    // 获得Root WebApplicationContext 对象
    WebApplicationContext rootContext =
        WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    // 第一种情况, 新建对象时就已经传入 webApplicationContext 属性,则直接使用
    // 从前面的分析可以知道, 创建DispatcherServlet对象时传递了一个WebApplicationContext对象进来
    if (this.webApplicationContext != null) {
        wac = this.webApplicationContext;
        if (wac instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
            // 未激活
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    设置wac的父容器为rootContext对象
                    cwac.setParent(rootContext);
                }
                // 配置和初始化 wac
                configureAndRefreshWebApplicationContext(cwac);
            }
        }
    }
    if (wac == null) {
        
        wac = findWebApplicationContext();
    }
    if (wac == null) {
        // No context instance is defined for this servlet -> create a local one
        wac = createWebApplicationContext(rootContext);
    }
    if (!this.refreshEventReceived) {
        // 如果未触发刷新事件,则主动触发刷新事件
        synchronized (this.onRefreshMonitor) {
            // FrameworkServlet 中这是个空方法
            // 但是它的子类 DispatcherServlet实现了这个方法, 触发Spring MVC组件的初始化
            onRefresh(wac);
        }
    }
    if (this.publishContext) {
        // 将 wac 设置到 ServletContext 中
        String attrName = getServletContextAttributeName();
        getServletContext().setAttribute(attrName, wac);
    }
    return wac;
}

FrameworkServlet#configureAndRefreshWebApplicationContext()

// FrameworkServlet.java
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
    // 如果 wac 使用了默认编号,则重新设置 id 属性
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        // 情况一,使用 contextId 属性
        if (this.contextId != null) {
            wac.setId(this.contextId);
        } else {
            // 情况二,自动生成
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                      ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
        }
    }
    // 设置 wac 的 servletContext、servletConfig、namespace 属性
    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    // 添加监听器 SourceFilteringListener 到 wac 中
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
    // The wac environment's #initPropertySources will be called in any case when the context
    // is refreshed; do it eagerly here to ensure servlet property sources are in place for
    // use in any post-processing or initialization that occurs below prior to #refresh
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }
    // 执行处理完 WebApplicationContext 后的逻辑。目前是个空方法,暂无任何实现
    postProcessWebApplicationContext(wac);
    // 执行ApplicationContextInitializer的initialize方法
    applyInitializers(wac);
    // 刷新context, 执行初始化
    wac.refresh();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值