tomcat与spring-web
文章目录
- SpringServletContainerInitializer
- WebApplicationInitializer
- WebApplicationInitializer接口
- AbstractContextLoaderInitializer抽象类
- AbstractDispatcherServletInitializer抽象类
- AbstractAnnotationConfigDispatcherServletInitializer抽象类
- WebApplicationContext
- WebApplicationContextUtils
- ContextLoaderListener
- ContextLoader
- DispatcherServlet
SpringServletContainerInitializer
介绍
SpringServletContainerInitializer是spring-web jar包下的类,并且在jar包中的META-INF/services的特定文件中有如下配置:
org.springframework.web.SpringServletContainerInitializer
这是利用了tomcat的3.0特性,因为tomcat会扫描类路径上的每个jar包中的META-INF/services中的这个指定名称的文件,并且读取这个文件中的类,比如这里的SpringServletContainerInitializer,这个类必须实现ServletContainerInitializer接口。然后这个类会被实例化,并且回调这个类的onStartup方法,并且还可以通过@HandleTypes注解传入感兴趣的类的子类作为第一个参数,ServletContxt作为第二个参数。利用这个特性可以给ServletContainer容器配置功能。
源码
/*** 使用Servlet3.0的ServletContainerInitializer特性,可以使用基于java代码的方式配置servlet容器,而取代以前的web.xml* 的配置方式。SpringServletContainerInitializer通过@HandleTypes,传入WebApplicationInitializer的实现类,来实现* 对ServletContext初始化。也就是说,我们只要定义一个类实现WebApplicationInitializer这个接口,那么我们的这个类就能够* 拿到ServletContext,并且往ServletContext中添加三大组件。* Servlet 3.0 {@link ServletContainerInitializer} designed to support code-based* configuration of the servlet container using Spring's {@link WebApplicationInitializer}* SPI as opposed to (or possibly in combination with) the traditional* {@code web.xml}-based approach.** 只要把spring-web 这个jar包放在类路径上,那么就会使用到tomcat的这个servlet 3.0的特性,不需要这个功能,* 那就移除这个jar包就行了。* 因为spring-web 包有指定的这个文件META-INF/services/javax.servlet.ServletContainerInitializer,* ** <h2>Mechanism of Operation</h2>* This class will be loaded and instantiated and have its {@link #onStartup}* method invoked by any Servlet 3.0-compliant container during container startup assuming* that the {@code spring-web} module JAR is present on the classpath. This occurs through* the JAR Services API {@link ServiceLoader#load(Class)} method detecting the* {@code spring-web} module's {@code META-INF/services/javax.servlet.ServletContainerInitializer}* service provider configuration file. See the* <a href="http://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider">* JAR Services API documentation</a> as well as section <em>8.2.4</em> of the Servlet 3.0* Final Draft specification for complete details.** * <h3>In combination with {@code web.xml}</h3>* A web application can choose to limit the amount of classpath scanning the Servlet* container does at startup either through the {@code metadata-complete} attribute in* {@code web.xml}, which controls scanning for Servlet annotations or through an* {@code <absolute-ordering>} element also in {@code web.xml}, which controls which* web fragments (i.e. jars) are allowed to perform a {@code ServletContainerInitializer}* scan. When using this feature, the {@link SpringServletContainerInitializer}* can be enabled by adding "spring_web" to the list of named web fragments in* {@code web.xml} as follows:** <pre class="code">* {@code* <absolute-ordering>* <name>some_web_fragment</name>* <name>spring_web</name>* </absolute-ordering>* }</pre>** SpringServletContainerInitializer负责把用户定义的实现了WebApplicationInitializer接口的类实例化,并且把* ServletContext传给用户自定义实现了WebApplicationInitializer接口的类对象的void onStartup(ServletContext)* 方法,这样在tomcat初始化过程中,就可以配置servlet容器了。** <h2>Relationship to Spring's {@code WebApplicationInitializer}</h2>* Spring's {@code WebApplicationInitializer} SPI consists of just one method:* {@link WebApplicationInitializer#onStartup(ServletContext)}. The signature is intentionally* quite similar to {@link ServletContainerInitializer#onStartup(Set, ServletContext)}:* simply put, {@code SpringServletContainerInitializer} is responsible for instantiating* and delegating the {@code ServletContext} to any user-defined* {@code WebApplicationInitializer} implementations. It is then the responsibility of* each {@code WebApplicationInitializer} to do the actual work of initializing the* {@code ServletContext}. The exact process of delegation is described in detail in the* {@link #onStartup onStartup} documentation below.** 一般性建议: 这个类用于支持框架, 用户可以自定义实现了WebApplicationInitializer接口的类,放在类路径上* 来做定制化修改。* * <h2>General Notes</h2>* In general, this class should be viewed as <em>supporting infrastructure</em> for* the more important and user-facing {@code WebApplicationInitializer} SPI. Taking* advantage of this container initializer is also completely <em>optional</em>: while* it is true that this initializer will be loaded and invoked under all Servlet 3.0+* runtimes, it remains the user's choice whether to make any* {@code WebApplicationInitializer} implementations available on the classpath. If no* {@code WebApplicationInitializer} types are detected, this container initializer will* have no effect.** 这个容器初始化器,并没有要求一定要使用springmvc, 它允许你只要实现了 WebApplicationInitializer这个接口,那么* 你从这个接口的方法里拿到ServletContext后,可以做你任何想要添加的servlet, listener, or filter这些组件,并不一定* 是spring的组件。** <p>Note that use of this container initializer and of {@code WebApplicationInitializer}* is not in any way "tied" to Spring MVC other than the fact that the types are shipped* in the {@code spring-web} module JAR. Rather, they can be considered general-purpose* in their ability to facilitate convenient code-based configuration of the* {@code ServletContext}. In other words, any servlet, listener, or filter may be* registered within a {@code WebApplicationInitializer}, not just Spring MVC-specific* components.** <p>This class is neither designed for extension nor intended to be extended.* It should be considered an internal type, with {@code WebApplicationInitializer}* being the public-facing SPI.** <h2>See Also</h2>* See {@link WebApplicationInitializer} Javadoc for examples and detailed usage* recommendations.<p>** @author Chris Beams* @author Juergen Hoeller* @author Rossen Stoyanchev* @since 3.1* @see #onStartup(Set, ServletContext)* @see WebApplicationInitializer*/
@HandlesTypes(WebApplicationInitializer.class) // 感兴趣的类是 WebApplicationInitializer接口的子类
public class SpringServletContainerInitializer implements ServletContainerInitializer {@Override // classpath路径上的所有类只要是@HandlesTypes注解标注的类,都会传过来,即WebApplicationInitializerpublic void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();// 把传过来的类实例化(不能被实例化的就不管了),添加到initializers集合中if (webAppInitializerClasses != null) {for (Class<?> waiClass : webAppInitializerClasses) {// Be defensive: Some servlet containers provide us with invalid classes,// no matter what @HandlesTypes says...if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&WebApplicationInitializer.class.isAssignableFrom(waiClass)) {try {initializers.add((WebApplicationInitializer) 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");// 对这些initializers进行一个排序,排序可以通过实现Ordered接口,或者使用@Order注解AnnotationAwareOrderComparator.sort(initializers);for (WebApplicationInitializer initializer : initializers) {initializer.onStartup(servletContext);}}}
WebApplicationInitializer
介绍
通过SpringServletContainerInitializer的介绍,我们知道了spring-web模块能够把类路径上实现了WebApplicationInitializer这个接口的类给例化,并且把ServletContext给传过来给onStartup方法,那么我们就可以自己往ServletContext中添加web的三大组建了。
我们先看下常规的web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"><!-- 使用spring 监听器 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- Spring核心配置文件 --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!-- 配置SpringMVC --><servlet><servlet-name>springmvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>springmvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>
源码
通过看上面的继承关系图,我们来看下它们的源码
WebApplicationInitializer接口
public interface WebApplicationInitializer {/*** 实现了这个接口的类可以往传过来的servletContext中注入servlets, filters, listeners三大组件* 这个接口方法的回调是由SpringServletContainerInitializer负责的** Configure the given {@link ServletContext} with any servlets, filters, listeners* context-params and attributes necessary for initializing this web application. See* examples {@linkplain WebApplicationInitializer above}.* @param servletContext the {@code ServletContext} to initialize* @throws ServletException if any call against the given {@code ServletContext}* throws a {@code ServletException}*/void onStartup(ServletContext servletContext) throws ServletException;}
AbstractContextLoaderInitializer抽象类
/*** 这个类实现了WebApplicationInitializer,目的是往ServlvetContext中添加ContextLoaderListener,* 这跟我们在web.xml 中配置ContextLoaderListener监听器是一样的** Convenient base class for {@link WebApplicationInitializer} implementations* that register a {@link ContextLoaderListener} in the servlet context.** <p>The only method required to be implemented by subclasses is* {@link #createRootApplicationContext()}, which gets invoked from* {@link #registerContextLoaderListener(ServletContext)}.** @author Arjen Poutsma* @author Chris Beams* @author Juergen Hoeller* @since 3.2*/
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {/** Logger available to subclasses */protected final Log logger = LogFactory.getLog(getClass());/** 这个重写接口的方法负责使用servletContext注册ContextLoaderListener监听器*/@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {registerContextLoaderListener(servletContext);}/**** 1.创建spring容器,准确的说应该是spring的根容器,并且把这个spring的根容器设置给了ContextLoaderListener监听器,* 2.getRootApplicationContextInitializers()方法获取到ApplicationContextInitializer实例,这个在此处是可以* 在当前这个类的这个模板方法里自定义返回ApplicationContextInitializer实例的。这个对应到web.xml中做的如下配置* <context-param>* <param-name>contextInitializerClasses</param-name>* <param-value>com.zzhua.initializer.MyApplicationContextInitializer</param-value>* </context-param>* 这里返回的 ApplicationContextInitializer 实例用于对spring的根容器的修改,并且可以返回不止1个,* 可以使用@Order注解或者实现Order接口来做一个排序,按顺序对spring根容器修改。* 这个ApplicationContextInitializer的回调是发生在ContextLoader的* configureAndRefreshWebApplicationContext()方法中调用customizeContext(sc, wac)方法时。* * * Register a {@link ContextLoaderListener} against the given servlet context. The* {@code ContextLoaderListener} is initialized with the application context returned* from the {@link #createRootApplicationContext()} template method.* @param servletContext the servlet context to register the listener against*/protected void registerContextLoaderListener(ServletContext servletContext) {WebApplicationContext rootAppContext = createRootApplicationContext();if (rootAppContext != null) {// spring容器被设置给了ContextLoaderListener的父类ContextLoader的WebApplicationContext属性// 说明ContextLoaderListener是持有spring根容器的ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);listener.setContextInitializers(getRootApplicationContextInitializers());servletContext.addListener(listener);}else {logger.debug("No ContextLoaderListener registered, as " +"createRootApplicationContext() did not return an application context");}}/*** Create the "<strong>root</strong>" application context to be provided to the* {@code ContextLoaderListener}.* <p>The returned context is delegated to* {@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)} and will* be established as the parent context for any {@code DispatcherServlet} application* contexts. As such, it typically contains middle-tier services, data sources, etc.* @return the root application context, or {@code null} if a root context is not* desired* @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer*/protected abstract WebApplicationContext createRootApplicationContext();/*** Specify application context initializers to be applied to the root application* context that the {@code ContextLoaderListener} is being created with.* @since 4.2* @see #createRootApplicationContext()* @see ContextLoaderListener#setContextInitializers*/protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {return null;}}
AbstractDispatcherServletInitializer抽象类
/*** * 这个类同样使用WebApplicationInitializer接口, 用于往ServletContext中注册 DispatcherServlet,* 并且对DispatcherServlet做相关的配置** Base class for {@link org.springframework.web.WebApplicationInitializer}* implementations that register a {@link DispatcherServlet} in the servlet context.** <p>Concrete implementations are required to implement* {@link #createServletApplicationContext()}, as well as {@link #getServletMappings()},* both of which get invoked from {@link #registerDispatcherServlet(ServletContext)}.* Further customization can be achieved by overriding* {@link #customizeRegistration(ServletRegistration.Dynamic)}.** <p>Because this class extends from {@link AbstractContextLoaderInitializer}, concrete* implementations are also required to implement {@link #createRootApplicationContext()}* to set up a parent "<strong>root</strong>" application context. If a root context is* not desired, implementations can simply return {@code null} in the* {@code createRootApplicationContext()} implementation.** @author Arjen Poutsma* @author Chris Beams* @author Rossen Stoyanchev* @author Juergen Hoeller* @author Stephane Nicoll* @since 3.2*/
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {/*** The default servlet name. Can be customized by overriding {@link #getServletName}.*/public static final String DEFAULT_SERVLET_NAME = "dispatcher";@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {// 先调用父类的方法,即先创建spring的父容器super.onStartup(servletContext);// 注册DispatcherServlet到ServletContext中,在此过程中,会创建spring容器,即spring子容器registerDispatcherServlet(servletContext);}/**** 创建spring的web子容器, 创建方法是模板方法,交给子类实现,* new了一个DispatcherServlet,并且把上面创建的spring子容器传给了DispatcherServlet* 获取到ApplicationContextInitializer的实例,获取方法是模板方法。获取的这些实例将应用于初始化spring的web子容器* 获取实例的过程,对应于web.xml中的servlet标签下的如下配置* <init-param>* <param-name>contextInitializerClasses</param-name>* <param-value>com.zzhua.initializer.MyAppContextInitializer,</param-value>* </init-param>* 加上 下面这个配置也会对spring的web子容器初始化* <context-param>* <param-name>contextInitializerClasses</param-name>* <param-value>com.zzhua.initializer.MyApplicationContextInitializer</param-value>* </context-param>* 这些初始化的调用是在DispatcherServlet的父类FrameworkServlet中的* configureAndRefreshWebApplicationContext方法调用applyInitializers(wac);时被调用** Register a {@link DispatcherServlet} against the given servlet context.* <p>This method will create a {@code DispatcherServlet} with the name returned by* {@link #getServletName()}, initializing it with the application context returned* from {@link #createServletApplicationContext()}, and mapping it to the patterns* returned from {@link #getServletMappings()}.* <p>Further customization can be achieved by overriding {@link* #customizeRegistration(ServletRegistration.Dynamic)} or* {@link #createDispatcherServlet(WebApplicationContext)}.* @param servletContext the context to register the servlet against*/protected void registerDispatcherServlet(ServletContext servletContext) {String servletName = getServletName();Assert.hasLength(servletName, "getServletName() must not return empty or null");// 创建一个spring的web容器, 这个创建方法是个模板方法,实现交给子类WebApplicationContext servletAppContext = createServletApplicationContext();Assert.notNull(servletAppContext,"createServletApplicationContext() did not return an application " +"context for servlet [" + servletName + "]");// 将创建的spring的web子容器作为创建DispatcherServlet的构造参数,从而创建DispatcherServlet,// 这说明,DispatcherServlet持有WebApplicationContext的,并且是在父类FrameWorkServlet中属性持有的FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);// 将Initializers设置给dispatcherServlet,用于初始化spring的web子容器dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());// 将DispatcherServlet注册给ServletContextServletRegistration.Dynamic registration = servletContext.addServlet(servletName,dispatcherServlet);Assert.notNull(registration,"Failed to register servlet with name '" + servletName + "'." +"Check if there is another servlet registered under the same name.");// 立即初始化registration.setLoadOnStartup(1);// 设置前端控制器DispatcherServlet的映射路径registration.addMapping(getServletMappings());// 异步支持registration.setAsyncSupported(isAsyncSupported());// 获取到filter,获取方法时模板方法,// 这个filter只拦截的servlet-name就是前端控制器DispatcherServletFilter[] filters = getServletFilters();if (!ObjectUtils.isEmpty(filters)) {for (Filter filter : filters) {registerServletFilter(servletContext, filter);}}// 自定义registration方法, 模板方法,让用户继续自定义registrationcustomizeRegistration(registration);}/**** 默认就是: servletName** Return the name under which the {@link DispatcherServlet} will be registered.* Defaults to {@link #DEFAULT_SERVLET_NAME}.* @see #registerDispatcherServlet(ServletContext)*/protected String getServletName() {return DEFAULT_SERVLET_NAME;}/*** 模板方法,创建spring的web子容器的逻辑,交给子类来做** Create a servlet application context to be provided to the {@code DispatcherServlet}.* <p>The returned context is delegated to Spring's* {@link DispatcherServlet#DispatcherServlet(WebApplicationContext)}. As such,* it typically contains controllers, view resolvers, locale resolvers, and other* web-related beans.* @see #registerDispatcherServlet(ServletContext)*/protected abstract WebApplicationContext createServletApplicationContext();/*** DispatcherServlet中传了spring的web子容器,说明dispatcherServlet是持有spring的web子容器的* 这个子容器将传给前端控制器的父类FrameWorkServlet的webApplicationContext* * Create a {@link DispatcherServlet} (or other kind of {@link FrameworkServlet}-derived* dispatcher) with the specified {@link WebApplicationContext}.* <p>Note: This allows for any {@link FrameworkServlet} subclass as of 4.2.3.* Previously, it insisted on returning a {@link DispatcherServlet} or subclass thereof.*/protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {return new DispatcherServlet(servletAppContext);}/*** 这里返回的初始化器,将应用于前端控制器创建时传入的spring的web容器的初始化* Specify application context initializers to be applied to the servlet-specific* application context that the {@code DispatcherServlet} is being created with.* @since 4.2* @see #createServletApplicationContext()* @see DispatcherServlet#setContextInitializers* @see #getRootApplicationContextInitializers()*/protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {return null;}/*** 设置前端控制器DispatcherServlet的处理请求的url-pattern** Specify the servlet mapping(s) for the {@code DispatcherServlet} —* for example {@code "/"}, {@code "/app"}, etc.* @see #registerDispatcherServlet(ServletContext)*/protected abstract String[] getServletMappings();/*** 指定拦截前端控制器DispatcherServlet的filter** Specify filters to add and map to the {@code DispatcherServlet}.* @return an array of filters or {@code null}* @see #registerServletFilter(ServletContext, Filter)*/protected Filter[] getServletFilters() {return null;}/*** Add the given filter to the ServletContext and map it to the* {@code DispatcherServlet} as follows:* <ul>* <li>a default filter name is chosen based on its concrete type* <li>the {@code asyncSupported} flag is set depending on the* return value of {@link #isAsyncSupported() asyncSupported}* <li>a filter mapping is created with dispatcher types {@code REQUEST},* {@code FORWARD}, {@code INCLUDE}, and conditionally {@code ASYNC} depending* on the return value of {@link #isAsyncSupported() asyncSupported}* </ul>* <p>If the above defaults are not suitable or insufficient, override this* method and register filters directly with the {@code ServletContext}.* @param servletContext the servlet context to register filters with* @param filter the filter to be registered* @return the filter registration*/protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {// 这里就是在往ServletContext注册Filter用于拦截前端控制器DispatcherServletString filterName = Conventions.getVariableName(filter);Dynamic registration = servletContext.addFilter(filterName, filter);if (registration == null) {int counter = -1;while (counter == -1 || registration == null) {counter++;registration = servletContext.addFilter(filterName + "#" + counter, filter);Assert.isTrue(counter < 100,"Failed to register filter '" + filter + "'." +"Could the same Filter instance have been registered already?");}}registration.setAsyncSupported(isAsyncSupported());registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());return registration;}private EnumSet<DispatcherType> getDispatcherTypes() {return (isAsyncSupported() ?EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));}/*** 异步支持* A single place to control the {@code asyncSupported} flag for the* {@code DispatcherServlet} and all filters added via {@link #getServletFilters()}.* <p>The default value is "true".*/protected boolean isAsyncSupported() {return true;}/*** 留给用户自定义前端控制器的方法,* Optionally perform further registration customization once* {@link #registerDispatcherServlet(ServletContext)} has completed.* @param registration the {@code DispatcherServlet} registration to be customized* @see #registerDispatcherServlet(ServletContext)*/protected void customizeRegistration(ServletRegistration.Dynamic registration) {}}
AbstractAnnotationConfigDispatcherServletInitializer抽象类
/*** * spring借助tomcat的3.0新特性SCI机制,提供的自动扫描 WebApplicationInitializer 接口实现类,* 得到ServletContext,从而注册Servlet、listener、filter等web组件。** 使用AnnotationConfigWebApplicationContext支持使用@Configuration注解类作为spring容器的配置信息,创建spring容器* servlet的spring的web子容器* ServletContextListener的spring的根容器** 作为这个类的具体实现,需要重写getRootConfigClasses()和getServletConfigClasses()方法,指定spring的配置类** Base class for {@link org.springframework.web.WebApplicationInitializer}* implementations that register a* {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}* configured with annotated classes, e.g. Spring's* {@link org.springframework.context.annotation.Configuration @Configuration} classes.** <p>Concrete implementations are required to implement {@link #getRootConfigClasses()}* and {@link #getServletConfigClasses()} as well as {@link #getServletMappings()}.* Further template and customization methods are provided by* {@link AbstractDispatcherServletInitializer}.** <p>This is the preferred approach for applications that use Java-based* Spring configuration.** @author Arjen Poutsma* @author Chris Beams* @since 3.2*/
public abstract class AbstractAnnotationConfigDispatcherServletInitializerextends AbstractDispatcherServletInitializer {/*** 获取配置类创建spring的web子容器, 这里用的是 AnnotationConfigWebApplicationContext* {@inheritDoc}* <p>This implementation creates an {@link AnnotationConfigWebApplicationContext},* providing it the annotated classes returned by {@link #getRootConfigClasses()}.* Returns {@code null} if {@link #getRootConfigClasses()} returns {@code null}.*/@Overrideprotected WebApplicationContext createRootApplicationContext() {// 获取配置类Class<?>[] configClasses = getRootConfigClasses();if (!ObjectUtils.isEmpty(configClasses)) {AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();// 注册给spring的配置文件,但并未刷新rootAppContext.register(configClasses);return rootAppContext;}else {return null;}}/*** 获取配置类创建spring根容器,这里用的是 AnnotationConfigWebApplicationContext** {@inheritDoc}* <p>This implementation creates an {@link AnnotationConfigWebApplicationContext},* providing it the annotated classes returned by {@link #getServletConfigClasses()}.*/@Overrideprotected WebApplicationContext createServletApplicationContext() {AnnotationConfigWebApplicationContext servletAppContext = newAnnotationConfigWebApplicationContext();// 获取配置类Class<?>[] configClasses = getServletConfigClasses();if (!ObjectUtils.isEmpty(configClasses)) {// 把配置类注册给了spring容器,但并未刷新servletAppContext.register(configClasses);}return servletAppContext;}/*** 指定spring根容器使用的配置类,这个配置类需要使用@Configuration或者是@Component注解* Specify {@link org.springframework.context.annotation.Configuration @Configuration}* and/or {@link org.springframework.stereotype.Component @Component} classes to be* provided to the {@linkplain #createRootApplicationContext() root application context}.* @return the configuration classes for the root application context, or {@code null}* if creation and registration of a root context is not desired*/protected abstract Class<?>[] getRootConfigClasses();/** * 指定spring的web子容器使用的配置类,这个配置类需要使用@Configuration或者是@Component注解* Specify {@link org.springframework.context.annotation.Configuration @Configuration}* and/or {@link org.springframework.stereotype.Component @Component} classes to be* provided to the {@linkplain #createServletApplicationContext() dispatcher servlet* application context}.* @return the configuration classes for the dispatcher servlet application context or* {@code null} if all configuration is specified through root config classes.*/protected abstract Class<?>[] getServletConfigClasses();}
应用
从WebApplicationIntializer接口的继承体系和下面子类的实现来看,它主要是做好了准备工作,
- 创建Spring的根容器(注意此时并没有刷新spring容器),并且设置到了ContextLoaderListener当中,并且把ContextLoaderListener注册到了ServletContext中,等待tomcat回调ServletContextListener的ContextInitialized方法时,再刷新spring容器
- 创建Spring的web子容器(注意此时也未刷新该spring容器),并且设置到了DispatcherServlet当中,并且把DispatcherServlet注册到了ServletContext中,等待tomcat初始化Servlet时,回调servlet的init方法时,再刷新spring容器
那么 其实接下来就是要看tomcat回调ContextLoaderLisetener和DispatcherServlet的初始化方法,那么在初始化方法过程中,肯定就会被spring容器进行一个自定义,并且完成spring根容器和spring的web子容器的刷新。
示例:
RootConfig
@ComponentScan(basePackages = "com.zzhua",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class RootConfig { // 配置根容器中的bean}
ServletConfig
@ComponentScan(basePackages = "com.zzhua",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)},useDefaultFilters = false)
public class ServletConfig extends WebMvcConfigurerAdapter { // 配置spring的web子容器中的bean}
MyWebApplicationInitializer
public class MyWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {// 把这个配置类设置给了ContextLoaderListener的父类ContextLoader持有的webApplicationContxt属性的配置类。@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[]{RootConfig.class};}// 把这个配置类设置给了DispacherServlet的父类FrameWorkServlet持有的webApplicationContxt属性的配置类。@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{ServletConfig.class};}@Overrideprotected String[] getServletMappings() {return new String[]{"/"};}@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {super.onStartup(servletContext);// 我们可以顺便注册一下其它的一些需要的组件 //注册组件 ServletRegistrationServletRegistration.Dynamic servlet = servletContext.addServlet("userServlet",new HelloServlet());//配置servlet的映射信息servlet.addMapping("/hello1");}
}
WebApplicationContext
// Spring-web的WebApplicationContext继承了Spring的ApplicationContext,
// 也就是说WebApplicationContext具有了spring容器的功能public interface WebApplicationContext extends ApplicationContext {// 此处注意这个名字, 这个名字是创建了spring容器后, 将这个spring容器存到ServletContext的键,// 通过这个键能拿到spring根容器String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";String SCOPE_REQUEST = "request";String SCOPE_SESSION = "session";String SCOPE_APPLICATION = "application";String SERVLET_CONTEXT_BEAN_NAME = "servletContext";String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";@Nullable // 从这个定义的方法, 可以看出WebApplicationContext(Spring容器)也能拿到SerlvetContextServletContext getServletContext();
}
WebApplicationContextUtils
public abstract class WebApplicationContextUtils {// ...public static WebApplicationContext getWebApplicationContext(ServletContext sc) {return getWebApplicationContext(sc, // 从ServletContext中取出spring容器WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE // 引用了上面这个键);}// ...
}
// 可以通过这种静态方法拿到spring容器, 所以只要能拿到ServletContext就能拿到Spring容器
// 源码可以参看ContextLoaderListener
ContextLoaderListener
ContextLoaderLisetener继承了ContextLoader,实现了ServletContextListener接口。ContextLoaderListener的大部分功能实现都在它的父类ContextLoader中实现,实现ServletContextListener接口,只是成为父类方法被tomcat容器调用的入口。
我们先看下ContextLoaderListener类
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {// 无参构造public ContextLoaderListener() {}// 传入web版的spring的容器public ContextLoaderListener(WebApplicationContext context) {super(context);}// 这个方法很重要,这个方法将会被tomcat在生命周期中回调,并且将ServletContext传给了父类处理// 我们不要忘了,我们把未刷新的spring容器放到了ContextLoaderListener中,并且保存在父类中,// 下面就要看父类是如何处理的/** * Initialize the root web application context.*/@Overridepublic void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());}/*** Close the root web application context.*/@Overridepublic void contextDestroyed(ServletContextEvent event) {closeWebApplicationContext(event.getServletContext());ContextCleanupListener.cleanupAttributes(event.getServletContext());}}
ContextLoader
package org.springframework.web.context;public class ContextLoader {/** 作为内部维护的web版spring容器在ServletContext中的contextId* Config param for the root WebApplicationContext id,* to be used as serialization id for the underlying BeanFactory: {@value}*/public static final String CONTEXT_ID_PARAM = "contextId";// spring容器刷新时用到的配置文件,我们应该还记得使用web.xml配置的时候,// 会给该监听器配置一个ContextConfigLocation,来让spring容器根据这个配置文件刷新/*<!-- 使用spring 监听器 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- Spring核心配置文件 --><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param>*/ // 这个context-param将会被tomcat读取到servletContext,而获取到servletContext后// ,可以使用servletContext.getInitParameter("contextConfigLocation")获取到对应的值// public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";// 获取web版的spring容器的实现类/*** Config param for the root WebApplicationContext implementation class to use: {@value}* @see #determineContextClass(ServletContext)*/public static final String CONTEXT_CLASS_PARAM = "contextClass";// 用来定义初始化web版spring容器的initializer类(需要实现ApplicationContextInitializer接口),// 这些类将会被实例化, 并且调用initializer.initialize(wac)来初始化spring容器/*** Config param for {@link ApplicationContextInitializer} classes to use* for initializing the root web application context: {@value}* @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)*/public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";// 定义全局的用来初始化spring容器的ApplicationContextInitializer接口实现类/*** Config param for global {@link ApplicationContextInitializer} classes to use* for initializing all web application contexts in the current application: {@value}* @see #customizeContext(ServletContext, ConfigurableWebApplicationContext)*/public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";/*** Optional servlet context parameter (i.e., "{@code locatorFactorySelector}")* used only when obtaining a parent context using the default implementation* of {@link #loadParentContext(ServletContext servletContext)}.* Specifies the 'selector' used in the* {@link ContextSingletonBeanFactoryLocator#getInstance(String selector)}* method call, which is used to obtain the BeanFactoryLocator instance from* which the parent context is obtained.* <p>The default is {@code classpath*:beanRefContext.xml},* matching the default applied for the* {@link ContextSingletonBeanFactoryLocator#getInstance()} method.* Supplying the "parentContextKey" parameter is sufficient in this case.*/public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector";/*** Optional servlet context parameter (i.e., "{@code parentContextKey}")* used only when obtaining a parent context using the default implementation* of {@link #loadParentContext(ServletContext servletContext)}.* Specifies the 'factoryKey' used in the* {@link BeanFactoryLocator#useBeanFactory(String factoryKey)} method call,* obtaining the parent application context from the BeanFactoryLocator instance.* <p>Supplying this "parentContextKey" parameter is sufficient when relying* on the default {@code classpath*:beanRefContext.xml} selector for* candidate factory references.*/public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey";// 定义可以使用的初始化参数的分隔符/*** Any number of these characters are considered delimiters between* multiple values in a single init-param String value.*/private static final String INIT_PARAM_DELIMITERS = ",; \t\n";// 在ContextLoader的同包下有ContextLoader.properties文件,里面定义了// org.springframework.web.context.WebApplicationContext\// =org.springframework.web.context.support.XmlWebApplicationContext/*** Name of the class path resource (relative to the ContextLoader class)* that defines ContextLoader's default strategy names.*/private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";// 默认策略的配置从默认的策略路径文件中读取private static final Properties defaultStrategies;static {// Load default strategy implementations from properties file.// This is currently strictly internal and not meant to be customized// by application developers.try {// 使用的是ClassPathResource来读取与ContextLoader同包下的ContextLoader.properties文件ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);// 使用PropertiesLoaderUtils的静态方法来读取resourcedefaultStrategies = PropertiesLoaderUtils.loadProperties(resource);}catch (IOException ex) {throw new IllegalStateException("Could not load 'ContextLoader.properties': "+ ex.getMessage());}}/*** Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext.*/private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread =new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1);/*** The 'current' WebApplicationContext, if the ContextLoader class is* deployed in the web app ClassLoader itself.*/private static volatile WebApplicationContext currentContext;// 从这里我们可以看到ContextLoader维护的spring容器这个属性// ,我们可以理解为,把spring容器存储到了tomcat的监听器中/*** The root WebApplicationContext instance that this loader manages.*/private WebApplicationContext context;/*** Holds BeanFactoryReference when loading parent factory via* ContextSingletonBeanFactoryLocator.*/private BeanFactoryReference parentContextRef;// contextInitializer将会被维护起来,它们将会用来初始化spring容器/** Actual ApplicationContextInitializer instances to apply to the context */private final List<ApplicationContextInitializer<ConfigurableApplicationContext>> contextInitializers =new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();// 无参构造方法public ContextLoader() {}// 有参构造方法,public ContextLoader(WebApplicationContext context) {this.context = context;}// 添加到ContextLoader维护的List<ApplicationContextInitializer>集合中@SuppressWarnings("unchecked")public void setContextInitializers(ApplicationContextInitializer<?>... initializers) {if (initializers != null) {for (ApplicationContextInitializer<?> initializer : initializers) {this.contextInitializers.add((ApplicationContextInitializer<ConfigurableApplicationContext>) initializer);}}}// 这是个很重要的方法, // 如果在构造阶段传入了spring容器,那么就初始化它;// 如果没有,那么就使用配置的contextClass以及contextConfigLocation来创建并刷新spring容器;public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {// spring的根容器都会放到ServletContext中并且以下面这个东西为键,并且只能放1个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!");}Log logger = LogFactory.getLog(ContextLoader.class);servletContext.log("Initializing Spring root WebApplicationContext");if (logger.isInfoEnabled()) {logger.info("Root WebApplicationContext: initialization started");}// 记录开始时间long startTime = System.currentTimeMillis();try {// Store context in local instance variable, to guarantee that// it is available on ServletContext shutdown.if (this.context == null) {// 如果刚开始构造当前监听器的时候,并没有传入容器,那么就根据默认的策略创建spring容器this.context = createWebApplicationContext(servletContext);}// 到这里,this.context就一定有值了if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;// isActive表示spring容器被刷新了,并且还没有关闭if (!cwac.isActive()) {// The context has not yet been refreshed -> provide services such as// setting the parent context, setting the application context id, etcif (cwac.getParent() == null) {// 如果context没有父容器,那么就从servletContext中的loadParentContext方法中弄出来一个// The context instance was injected without an explicit parent ->// determine parent for root web application context, if any.ApplicationContext parent = loadParentContext(servletContext);cwac.setParent(parent);}// 给this.context找完父容器之后,开始配置并且刷新spring容器:this.context// 这是个非常关键的方法,spring容器刷新是个非常重要的阶段configureAndRefreshWebApplicationContext(cwac, servletContext);}}// spring容器刷新之后,将spring容器存到servletContext中,并且键如下// ,这下我们可以看到为什么一进这个方法就判断这个键了servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = this.context;}else if (ccl != null) {currentContextPerThread.put(ccl, this.context);}if (logger.isDebugEnabled()) {logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");}if (logger.isInfoEnabled()) {long elapsedTime = System.currentTimeMillis() - startTime;logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");}return this.context;}catch (RuntimeException ex) {logger.error("Context initialization failed", ex);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);throw ex;}catch (Error err) {logger.error("Context initialization failed", err);servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);throw err;}}// 创建容器的方法,先从servletContext中配置的contextClass参数反射创建spring容器protected WebApplicationContext createWebApplicationContext(ServletContext sc) {Class<?> contextClass = determineContextClass(sc);if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {throw new ApplicationContextException("Custom context class [" + contextClass.getName() +"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");}return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);}// 确定contextClassprotected Class<?> determineContextClass(ServletContext servletContext) {// 先从ServletContext中查找配置,配置找不到,那就使用默认策略的(ContextLoader.properteis配置的)String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);if (contextClassName != null) {try {return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());}catch (ClassNotFoundException ex) {throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", ex);}}else {// 默认策略contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());try {return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());}catch (ClassNotFoundException ex) {throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", ex);}}}protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {if (ObjectUtils.identityToString(wac).equals(wac.getId())) {// spring容器的默认id就是 ObjectUtils.identityToString(this),// 如果它没有被改变,那就根据已有的信息,做个修改// 首先看servletContext有没有配置contextId, 不然的话就设置一个默认的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()));}}// 这里我们就知道了, web版的spring容器中保存了servletContext,// 而servletContext也通过(域)一个键保存了spring容器wac.setServletContext(sc);// spring容器刷新前需要设置配置文件,首先看servletContext中有没有配置contextConfigLocationString configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);if (configLocationParam != null) {wac.setConfigLocation(configLocationParam);}// spring容器持有一个environment对象,environment对象有两个功能,一个是解析属性,二是对bean逻辑分组(profile)// 这里获取environment的逻辑也简单,第一次获取,就创建并设置到spring容器的属性里// ,这里创建的是StandardServletEnvironment,// environment主要持有了一个propertySource的集合以及使用包装了propertySource的集合// 的PropertySourcesPropertyResolver对象来作解析ConfigurableEnvironment env = wac.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {// 将servletContext包装成ServletContextPropertySource设置到env的propertySource集合中((ConfigurableWebEnvironment) env).initPropertySources(sc, null);}// 刷新spring容器之前的最后一步// 在这一步,我们可以将initializer配置给servletContext,// 那么在这里就会获取到所有配置的initializer(ApplicationInitializer)对象,(servletContext+属性上的)// 逐个对sprin根容器进行初始化,注意这些initializer对象是可以设置顺序的(@Order注解或者实现Order接口)// 比如: 我们这个时候就可以往spring容器中注入我们的组件了,这是一个很好的时机customizeContext(sc, wac);// 准备工作都做好了,开始刷新spring容器wac.refresh();}protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =determineContextInitializerClasses(sc);for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {Class<?> initializerContextClass =GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {throw new ApplicationContextException(String.format("Could not apply context initializer [%s] since its generic parameter [%s] " +"is not assignable from the type of application context used by this " +"context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),wac.getClass().getName()));}this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));}AnnotationAwareOrderComparator.sort(this.contextInitializers);for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {initializer.initialize(wac);}}protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>determineContextInitializerClasses(ServletContext servletContext) {List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);if (globalClassNames != null) {for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {classes.add(loadInitializerClass(className));}}String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);if (localClassNames != null) {for (String className : StringUtils.tokenizeToStringArray(localClassNames,INIT_PARAM_DELIMITERS)) {classes.add(loadInitializerClass(className));}}return classes;}@SuppressWarnings("unchecked")private Class<ApplicationContextInitializer<ConfigurableApplicationContext>> loadInitializerClass(String className) {try {Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());if (!ApplicationContextInitializer.class.isAssignableFrom(clazz)) {throw new ApplicationContextException("Initializer class does not implement ApplicationContextInitializer interface: " + clazz);}return (Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz;}catch (ClassNotFoundException ex) {throw new ApplicationContextException("Failed to load context initializer class [" + className + "]", ex);}}/*** Template method with default implementation (which may be overridden by a* subclass), to load or obtain an ApplicationContext instance which will be* used as the parent context of the root WebApplicationContext. If the* return value from the method is null, no parent context is set.* <p>The main reason to load a parent context here is to allow multiple root* web application contexts to all be children of a shared EAR context, or* alternately to also share the same parent context that is visible to* EJBs. For pure web applications, there is usually no need to worry about* having a parent context to the root web application context.* <p>The default implementation uses* {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator},* configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and* {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context* which will be shared by all other users of ContextsingletonBeanFactoryLocator* which also use the same configuration parameters.* @param servletContext current servlet context* @return the parent application context, or {@code null} if none* @see org.springframework.context.access.ContextSingletonBeanFactoryLocator*/protected ApplicationContext loadParentContext(ServletContext servletContext) {ApplicationContext parentContext = null;String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);if (parentContextKey != null) {// locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);Log logger = LogFactory.getLog(ContextLoader.class);if (logger.isDebugEnabled()) {logger.debug("Getting parent context definition: using parent context key of '" +parentContextKey + "' with BeanFactoryLocator");}this.parentContextRef = locator.useBeanFactory(parentContextKey);parentContext = (ApplicationContext) this.parentContextRef.getFactory();}return parentContext;}/*** Close Spring's web application context for the given servlet context. If* the default {@link #loadParentContext(ServletContext)} implementation,* which uses ContextSingletonBeanFactoryLocator, has loaded any shared* parent context, release one reference to that shared parent context.* <p>If overriding {@link #loadParentContext(ServletContext)}, you may have* to override this method as well.* @param servletContext the ServletContext that the WebApplicationContext runs in*/public void closeWebApplicationContext(ServletContext servletContext) {servletContext.log("Closing Spring root WebApplicationContext");try {if (this.context instanceof ConfigurableWebApplicationContext) {((ConfigurableWebApplicationContext) this.context).close();}}finally {ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl == ContextLoader.class.getClassLoader()) {currentContext = null;}else if (ccl != null) {currentContextPerThread.remove(ccl);}servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);if (this.parentContextRef != null) {this.parentContextRef.release();}}}/*** Obtain the Spring root web application context for the current thread* (i.e. for the current thread's context ClassLoader, which needs to be* the web application's ClassLoader).* @return the current root web application context, or {@code null}* if none found* @see org.springframework.web.context.support.SpringBeanAutowiringSupport*/public static WebApplicationContext getCurrentWebApplicationContext() {ClassLoader ccl = Thread.currentThread().getContextClassLoader();if (ccl != null) {WebApplicationContext ccpt = currentContextPerThread.get(ccl);if (ccpt != null) {return ccpt;}}return currentContext;}}
DispatcherServlet
见 框架源码学习收藏汇总
- DispatcherServlet(上) - 继承体系与配置详解
- DispatcherServlet(下) - 处理请求流程
- SpringMvc-HandlerMapping(全)
- HandlerAdapter
- RequestMappingHandlerAdapter(上)
- RequestMappingHandlerAdatper(中)
- RequesetMappingHandlerAdatper(下)
- 视图解析器ViewResolver(上)
- 视图解析器ViewResolver(下)
- RequestResponseBodyMethodProcessor
- 处理器方法参数解析器&参数校验
- @EnableWebMvc源码&内容协商管理器
- ResourceHttpRequestHandler指定静态资源文件夹
相关文章:
tomcat与spring-web
文章目录 SpringServletContainerInitializerWebApplicationInitializerWebApplicationInitializer接口AbstractContextLoaderInitializer抽象类AbstractDispatcherServletInitializer抽象类AbstractAnnotationConfigDispatcherServletInitializer抽象类 WebApplicationContext…...
将电脑控制手机编写为MCP server
文章目录 电脑控制手机后,截屏代码复习MCP server构建修改MCP的config文件测试效果困惑电脑控制手机后,截屏代码复习 def capture_window(hwnd: int, filename: str = None) -> dict:""&...
[ctfshow web入门]burpsuite的下载与使用
下载 吾爱破解网站工具区下载burpsuite https://www.52pojie.cn/thread-1544866-1-1.html 本博客仅转载下载链接,下载后请按照说明进行学习使用 打开 配置 burpsuite配置 burpsuite代理设置添加127.0.0.1:8080 浏览器配置 如果是谷歌浏览器,打开win…...
文章记单词 | 第25篇(六级)
一,单词释义 mathematical:形容词,意为 “数学的;数学上的;运算能力强的;关于数学的”trigger:名词,意为 “(枪的)扳机;(炸弹的&…...
讯飞语音合成(流式版)语音专业版高质量的分析
一、引言 在现代的 Web 应用开发中,语音合成技术为用户提供了更加便捷和人性化的交互体验。讯飞语音合成(流式版)以其高效、稳定的性能,成为了众多开发者的首选。本文将详细介绍在 Home.vue 文件中实现讯飞语音合成(流…...
【MediaPlayer】基于libvlc+awtk的媒体播放器
基于libvlcawtk的媒体播放器 libvlc下载地址 awtk下载地址 代码实现libvlc相关逻辑接口UI媒体接口实例化媒体播放器注意事项 libvlc 下载地址 可以到https://download.videolan.org/pub/videolan/vlc/去下载一个vlc版本,下载后其实是vlc的windows客户端࿰…...
复古未来主义屏幕辉光像素化显示器反乌托邦效果PS(PSD)设计模板样机 Analog Retro-Futuristic Monitor Effect
这款模拟复古未来主义显示器效果直接取材于 90 年代赛博朋克电影中的黑客巢穴,将粗糙的屏幕辉光和像素化的魅力强势回归。它精准地模仿了老式阴极射线管显示器,能将任何图像变成故障频出的监控画面或高风险的指挥中心用户界面。和……在一起 2 个完全可编…...
Kafka 如何保证消息有序性?
Kafka 保证消息顺序性,是基于 Partition(分区)级别的顺序 来实现的。下面我们详细拆解一下: ✅ 同一个 Partition 内,消息是严格有序的 Kafka 在 同一个分区(Partition)内,消息是按…...
【积木画】——第十三届蓝桥杯(2022)T7思路解析
题目描述 关键词 递推、dp 思路 显然这是一道递推题。 但是为什么我还要写在这呢?因为我虽然看了题解但是还是没想明白,综合了下面两篇 参考文献我才初步理解这题的精髓。所以还是自己写一遍为好。 我们把最终结果记为F(n)。 情况1 直接以一个竖着…...
Android studio xml布局预览中 Automotive和Autotive Distant Display的区别
在 Android Studio 中,Configure Hardware Profile 设置中的 Device Type 选项有两个不同的设置:Android Automotive 和 Android Automotive Distant Display,它们的含义和用途如下: 1. Android Automotive 含义:这个…...
第十三章:持久化存储_《凤凰架构:构建可靠的大型分布式系统》
第十三章 持久化存储 一、Kubernetes存储设计核心概念 (1)存储抽象模型 PersistentVolume (PV):集群级别的存储资源抽象(如NFS卷/云存储盘)PersistentVolumeClaim (PVC):用户对存储资源的声明请求&#…...
Nginx 基础使用(2025)
一、Nginx目录结构 [rootlocalhost ~]# tree /usr/local/nginx /usr/local/nginx ├── client_body_temp # POST 大文件暂存目录 ├── conf # Nginx所有配置文件的目录 │ ├── fastcgi.conf # fastcgi相…...
Docker基础1
本篇文章我将从系统的知识体系讲解docker的由来和在linux中的安装下载 随后的文章会介绍下载镜像、启动新容器、登录新容器 如需转载,标记出处 docker的出现就是为了节省资本和服务器资源 当企业需要一个新的应用程序时,需要为它买台全新的服务器。这样…...
【奇点时刻】GPT4o新图像生成模型底层原理深度洞察报告
个人最近一直在关注openai的新图像生成特性,以下内容基于现阶段社区及研究者们对 GPT-4O 图像生成功能的公开测试、逆向分析与技术推测综合而成,OpenAI 并未正式发布完整的技术报告,因此本文为非官方推断总结。但从多方信息与技术背景出发&am…...
Java的Selenium的特殊元素操作与定位之模态框
Modal Dialogue Box,又叫做模式对话框,是指在用户想要对对话框以外的应用程序进行操作时,必须首先对该对话框进行响应。如单击【确定】或【取消】按钮等将该对话框关闭。 alert(警告) //访问本地的HTML文件 chromeDr…...
回归预测 | Matlab实现NRBO-Transformer-LSTM多输入单输出回归预测
回归预测 | Matlab实现NRBO-Transformer-LSTM多输入单输出回归预测 目录 回归预测 | Matlab实现NRBO-Transformer-LSTM多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.【JCR一区级】Matlab实现NRBO-Transformer-LSTM多输入单输出回归预测…...
Python菜鸟教程(小程序)
目录 一.简易计算器 二.学生成绩分级 三.密码设置 四.作业选择 点赞收藏,评论支持 一.简易计算器 print(-------使用的运算符-------\n) print(1.加号) print(2.减号) print(3.乘号) print(4.除号) Aint(input(请输入第一个数: )) Bint(input(请输入第二个数: )) Fi…...
类的(多态性、虚函数)基础练习
练习1:(简单) #include <iostream> using namespace std; class Vehicle { public: virtual void run() const0; }; class Car: public Vehicle { public: void run() const { cout << "run a car. "<<…...
特殊的质数肋骨--dfs+isp
1.dfs全排列组数,an记得还原 2.如果范围确定且只比较质数,isp比线性筛快,主要这个范围太大了 https://www.luogu.com.cn/problem/P1218 #include<bits/stdc.h> using namespace std; #define N 100011 typedef long long ll; typed…...
智能体开发实战指南:提示词设计、开发框架与工作流详解
在大语言模型(LLM)驱动的智能体(Agent)快速发展的今天,构建一个实用、智能的Agent已不再遥不可及。无论你是开发法律助手、租房合同分析器,还是通用办公自动化助手,理解提示词工程(P…...
jetson orin nano学习(torch+OpenCV+yolov5+)
一:入门第一件事:跟着商家教程配置哈哈 指令:nvidia-smi -h 帮助命令 sudo jtop --查看nvidia的gpu状态 Tip:教程下载的pytorth,cuda,cudnn版本不一定是你项目符合的,要提前想好 1.2 安装虚拟环境包(要安…...
client-go如何监听自定义资源
如何使用 client-go 监听自定义资源 在 Kubernetes 中使用 client-go 监听自定义资源(Custom Resource,简称 CR)需要借助 Dynamic Client 或 Custom Informer,因为 client-go 的标准 Clientset 只支持内置资源(如 Pod…...
【51单片机】3-3【定时器/计数器/中断】超声波测距模块测距
1.硬件 51最小系统超声波测距模块 2.软件 #include "reg52.h"//距离小于10cm,D5亮,D6灭,反之相反现象sbit D5 P3^7;//根据原理图(电路图),设备变量led1指向P3组IO口的第7口 sbit D6 P3^6;//根据原理图&…...
C语言求3到100之间的素数
一、代码展示 二、运行结果 三、感悟思考 注意: 这个题思路他是一个试除法的一个思路 先进入一个for循环 遍历3到100之间的数字 第二个for循环则是 判断他不是素数 那么就直接退出 这里用break 是素数就打印出来 在第一个for循环内 第二个for循环外...
金仓数据库KCM认证考试介绍【2025年4月更新】
KCM(金仓认证大师)认证是金仓KES数据库的顶级认证,学员需通过前置KCA、KCP认证才能考KCM认证。 KCM培训考试一般1-2个月一次,KCM报名费原价为1.8万,当前优惠价格是1万(趋势是:费用越来越高&…...
leetcode每日一题:替换子串得到平衡字符串
引言 今天的每日一题原题是1863. 找出所有子集的异或总和再求和,比较水,直接对于集合中的每一个元素,都有取或者不取2种情况,直接递归进去求和即可。更换成前几天遇到的更有意思的一题来写这个每日一题。 题目 有一个只含有 Q,…...
2025年数字化社会与智能计算国际学术会议 (ICDSIC 2025)
基本信息 官网:www.icdsic.net 时间:2025年4月18-20日 地点:中国-深圳 主题 数字化社会 智能计算 数字化制造、经济 数字化政务、转型 数字化农业、水利、管理 数字化医疗、学习、社区 数字基建、通信、交通 数字…...
BN测试和训练时有什么不同, 在测试时怎么使用?
我们来彻底搞懂 Batch Normalization(BN) 在训练和测试阶段的区别,以及 测试时怎么用。 🧠 一句话总结: 训练时:使用 当前 mini-batch 的均值和方差 测试时:使用 整个训练集估计的“滑动平均均值…...
为什么卷积神经网络适用于图像和视频?
我们常听说“卷积神经网络(CNN)擅长图像和视频”,但其实 CNN 的核心本质远不止图像领域。我们先搞懂它为啥适合图像/视频。 🧠CNN 为什么适用于图像和视频? 主要因为 图像/视频具有空间局部性和结构平移性,…...
python爬虫:DrissionPage实战教程
如果本文章看不懂可以看看上一篇文章,加强自己的基础:爬虫自动化工具:DrissionPage-CSDN博客 案例解析: 前提:我们以ChromiumPage为主,写代码工具使用Pycharm(python环境3.9-3.10) …...
【Python爬虫高级技巧】BeautifulSoup高级教程:数据抓取、性能调优、反爬策略,全方位提升爬虫技能!
大家好,我是唐叔!上期我们聊了 BeautifulSoup的基础用法 ,今天带来进阶篇。我将分享爬虫老司机总结的BeautifulSoup高阶技巧,以及那些官方文档里不会告诉你的实战经验! 文章目录 一、BeautifulSoup性能优化技巧1. 解析…...
【动手学深度学习】卷积神经网络(CNN)入门
【动手学深度学习】卷积神经网络(CNN)入门 1,卷积神经网络简介2,卷积层2.1,互相关运算原理2.2,互相关运算实现2.3,实现卷积层 3,卷积层的简单应用:边缘检测3.1࿰…...
IPSG 功能协议
IPSG(IP Source Guard)即 IP 源保护,是一种基于 IP 地址和 MAC 地址绑定的安全功能,用于防止 IP 地址欺骗和非法的 IP 地址访问。以下是配置 IPSG 功能的一般步骤: 基于端口的 IPSG 配置 进入接口配置模式࿱…...
19.go日志包log
核心功能与接口 基础日志输出 Print 系列:支持 Print()、Println()、Printf(),输出日志不中断程序。 log.Print("常规日志") // 输出: 2025/03/18 14:47:13 常规日志 log.Printf("格式化: %s", "数据") Fatal…...
横扫SQL面试——TopN问题
横扫SQL面试 电商平台的"销量Top10商品"🛍️,内容社区的"热度Top5文章“”🔥,还是金融领域的"交易额Top3客户"💰——TopN问题无处不在! 无论是日常业务分析📊&#x…...
高级:微服务架构面试题全攻略
一、引言 在现代软件开发中,微服务架构被广泛应用于构建复杂、可扩展的应用程序。面试官通过相关问题,考察候选人对微服务架构的理解、拆分原则的掌握、服务治理的能力以及API网关的运用等。本文将深入剖析微服务架构相关的面试题,结合实际开…...
使用MATIO库读取Matlab数据文件中的cell结构数据
使用MATIO库读取Matlab数据文件中的cell结构数据 MATIO是一个用于读写Matlab数据文件(.mat)的C/C库。下面我将展示如何使用MATIO库来读取Matlab文件中的cell结构数据。 示例程序 #include <stdio.h> #include <stdlib.h> #include <matio.h>int main(int …...
pyTorch框架使用CNN进行手写数字识别
目录 1.导包 2.torchvision数据处理的方法 3.下载加载手写数字的训练数据集 4.下载加载手写数字的测试数据集 5. 将训练数据与测试数据 转换成dataloader 6.转成迭代器取数据 7.创建模型 8. 把model拷到GPU上面去 9. 定义损失函数 10. 定义优化器 11. 定义训练…...
新能源汽车电子电气架构设计中的功能安全
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 周末洗了一个澡,换了一身衣服,出了门却不知道去哪儿,不知道去找谁,漫无目的走着,大概这就是成年人最深的孤独吧! 旧人不知我近况,新人不知我过…...
使用binance-connector库获取Binance全市场的币种价格,然后选择一个币种进行下单
一个完整的示例,展示如何使用 api 获取Binance全市场的币种价格,然后选择一个最便宜的币种进行下单操作 代码经过修改,亲测可用,目前只可用于现货,合约的待开发 获取市场价格:使用client.ticker_price()获取所有交易对的当前价格 账户检查:获取账户余额,确保有足够的资…...
HikariCP 源码核心设计解析与 ZKmall开源商城场景调优实践
HikariCP 作为 Spring Boot 默认数据库连接池,其高性能源于独特的无锁设计、轻量级数据结构和精细化生命周期管理。以下从源码解析与 ZKmall开源商城性能调优两个维度展开: 一、HikariCP 源码核心设计解析 无锁并发控制与 ConcurrentBag 容器 Concur…...
P1036 [NOIP 2002 普及组] 选数(DFS)
题目描述 已知 n 个整数 x1,x2,⋯,xn,以及 1 个整数 k(k<n)。从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。例如当 n4,k3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它…...
自然语言处理
自然语言处理基础 什么是自然语言处理:让计算机来理解人类所说的一种语言。自然语言处理实际就是让计算机理解人类说的话,然后像人一样进行交互,去进行对话,去生成自然语言。 自然语言处理的基本任务 词性标注:把给…...
LeetCode刷题常见的Java排序
1. 字符串排序(字母排序) 首先,你的代码实现了根据字母表顺序对字符串中的字母进行排序,忽略了大小写并且保留了非字母字符的位置。关键点是: 提取和排序字母:通过 Character.isLetter() 判断是否为字母,并利用 Character.toLowerCase() 来忽略大小写进行排序。保留非字…...
# 利用OpenCV和Dlib实现疲劳检测:守护安全与专注
利用OpenCV和Dlib实现疲劳检测:守护安全与专注 在当今快节奏的生活中,疲劳和注意力不集中是许多人面临的常见问题,尤其是在驾驶、学习等需要高度集中精力的场景中。疲劳不仅影响个人的健康和安全,还可能导致严重的事故。为了应对…...
python基础-16-处理csv文件和json数据
文章目录 【README】【16】处理csv文件和json数据【16.1】csv模块【16.1.1】reader对象【16.1.2】在for循环中, 从reader对象读取数据【16.1.3】writer对象【16.1.5】DictReader与DictWriter对象 【16.4】json模块【16.4.1】使用loads()函数读取json字符串并转为jso…...
Mysql 数据库编程技术01
一、数据库基础 1.1 认识数据库 为什么学习数据库 瞬时数据:比如内存中的数据,是不能永久保存的。持久化数据:比如持久化至数据库中或者文档中,能够长久保存。 数据库是“按照数据结构来组织、存储和管理数据的仓库”。是一个长…...
基于SSM的车辆管理系统的设计与实现(代码+数据库+LW)
摘要 当下,正处于信息化的时代,许多行业顺应时代的变化,结合使用计算机技术向数字化、信息化建设迈进。以前企业对于车辆信息的管理和控制,采用人工登记的方式保存相关数据,这种以人力为主的管理模式已然落后。本人结…...
BugKu Simple_SSTI_2
这个题很简单,主要是记录一下,做题的原理: 打开环境,提示我们用flag传参,然后我们需要判断是什么模板: 这里有一张图片,可以帮助我们轻松判断是什么模板类型:这个图片找不到出处了&…...
浙考!【触发器逻辑方程推导(电位运算)】
RS触发器是浙江高考通用技术一大考点。“对角线原则”、“置1置0”、“保持”、“不使用”、“记忆功能”…经常让考生云里雾里,非常反直觉。 这篇文章,我想以高中生的视角诠释一下触发器。 1、触发器逻辑方程推导(以或非门触发器为例&…...