基于web.xml方式
最常用配置如下:
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param> <!-- 配置DispatcherServlet --> <servlet> <servlet-name>springMvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定spring mvc配置文件位置 不指定使用默认情况 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <!-- 设置启动顺序 1表示立即启动,而不是首次访问再启动--> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>springMvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
注意:如果没有指定spring-mvc.xml 配置,则默认使用DispatcherServlet的默认配置DispatcherServlet.properties
# Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
有了上面基于注解的分析,这里就非常容易了。注解驱动,容器都是它自己根据配置类进行创建的,而此处基于xml的形式,我们只需要区别的看两个创建方法即可,上面也已经提到了:
ContextLoader#createWebApplicationContext() 根据xml创建根容器
protected WebApplicationContext createWebApplicationContext(ServletContext sc) { // 找到上下文类型。若自己没有配置实现类,那就是XmlApplicationContext Class<?> contextClass = determineContextClass(sc); // 由此看出 用户diy的也必须是ConfigurableWebApplicationContext的子类才行 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); }
determineContextClass方法如下:
protected Class<?> determineContextClass(ServletContext servletContext) { // 显然,一般情况下我们都不会自己配置一个容器类,自己去实现~ 所有走else String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } } else { // 采用默认配置文件里的XmlApplicationContext来初始化上下文 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } } }
FrameworkServlet#initWebApplicationContext
涉及到这两个方法:
protected WebApplicationContext findWebApplicationContext() { // 只有调用过setContextAttribute(String contextAttribute)这里才有值,否则为null 或者web.xml里配置:contextAttribute为key的属性值 String attrName = getContextAttribute(); if (attrName == null) { return null; } //按照这个attr去Servlet容器里面找 WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } return wac; }
这里一般都会返回null,因此此处继续创建:
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { // 没有配置的话,默认值为public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; Class<?> contextClass = getContextClass(); // 校验必须是ConfigurableWebApplicationContext的子类 if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException( "Fatal initialization error in servlet with name '" + getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } // 创建一个容器实例 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); // 设置好父容器、Enviroment等等 wac.setEnvironment(getEnvironment()); wac.setParent(parent); //看看有没有配置配置文件的位置 String configLocation = getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } // 这个是重点,如完善、初始化、刷新容器 configureAndRefreshWebApplicationContext(wac); return wac; }
configureAndRefreshWebApplicationContext()如下
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { if (this.contextId != null) { wac.setId(this.contextId); } else { // 默认的id 这里面和contextpath有关了 wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName()); } } // 关联到了Namespace/servlet等等 wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); //添加了一个容器监听器 此监听器SourceFilteringListener在后面还会碰到 wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); // 同之前~ ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } //留给子类,可以复写此方法,做一些初始化时候自己的实行 postProcessWebApplicationContext(wac); //同样的 执行一些初始化类们 一般也是用不着。备注:用到了这个常量ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM applyInitializers(wac); wac.refresh(); }
至此,web子容器就全部创建、刷新完成了~
Spring父子容器的优缺点
优点:能让web环境和普通的Spring环境达到隔离的效果。web容器专注于管理web相关Bean,其余的交给父容器即可。 这样子强制隔离,也能驱动我们在编码过程中注重分层,使得层次结构更加的明晰
缺点:父子容器的设计提高了Spring初始化、管理Bean的复杂度(虽然对我们使用者一般都无感),我们万一要用到相关功能的时候,若不理解原理会有莫名其妙的一些问题,提高了复杂性
理论上我们可以有任意多个容器(只是我们一般其它的都只放进主容器统一管理上,但Spring是提供了这样的功能的),比如
主容器:applicationContext.xml(主文件,包括JDBC配置,hibernate.cfg.xml,与所有的Service与DAO基类)
web子容器:application-servlet.xml(管理Spring MVC9打组件以及相关的Bean)
cache子容器:applicationContext-cache.xml(cache策略配置,管理和缓存相关的Bean)
JMX子容器:applicationContext-jmx.xml(JMX相关的Bean)
…
总之:通过父子容器分层管理是好的设计思想,但是在编码使用中,大道至简才是我们追求的方式。所以我个人觉得:父子容器的设计并不是一根很好的设计(理想是好的,但实施上却不见得是好的),估计这也是为何Spring Boot中只采用一个容器的原因吧,简单的或许就是最好的(仅个人意见,不喜勿喷)
总结
本篇文章基本介绍了Spring容器以及Spring MVC容器的一个初始化过程,包括了web.xml和注解驱动两种方式。
然后里面涉及到的Spring容器刷新过程的核心逻辑,以及Dispatcher的9打组件,以及处理请求的过程,请关注接下来的博文~
值得注意的是,springMVC在调用HandlerMapper进行url到controller函数方法映射解析的时候,HandlerMapper会在springMVC容器中寻找controller,也就是在子容器中寻找,不会去父容器spring容器中寻找的。
所以如果用父容器来管理controller的话,子容器不去管理,在访问页面的时候会出现404错误。
所以我们姑且可以这么认为:我们可以用子容器统一管理Bean(包括父容器的Bean),但是不能用父容器管理所有Bean