一、Spring整合SpringMVC
Spring利用父子容器整合SpringMVC。(父容器:Spring,子容器:mvc)。
父容器管理Service、Dao层实例对象;子容器管理Controller实例对象。
子容器可以访问父容器Bean。反之不可。
1.利用web.xml整合
本质:Web容器解析web.xml。启动Spring容器,加载DispatcherServlet。
重点:注册ContextLoaderListener、DispatcherServlet
<!--spring 基于web应用的启动--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!--全局参数:spring配置文件--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-core.xml</param-value></context-param><!--前端调度器servlet--><servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!--设置配置文件的路径--><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-mvc.xml</param-value></init-param><!--设置启动即加载--><load-on-startup>1</load-on-startup></servlet><!--dispatcherServlet管理的请求路径--><servlet-mapping><servlet-name>dispatcherServlet</servlet-name><url-pattern>/</url-pattern><servlet-mapping>
2.零配置整合
本质:把配置利用代码实现,并且利用SPI机制机制,在WEB容器启动时进行加载。
核心类:SpringServletContainerInitializer,重点完成:注册ContextLoaderListener、DispatcherServlet
执行流程
- Tomcat在启动时会通过SPI注册 ContextLoaderListener和DispatcherServlet对象
- 同时创建父子容器
- 分别创建在ContextLoaderListener初始化时创建父容器设置配置类
- 在DispatcherServlet初始化时创建子容器 即2个ApplicationContext实例设置配置类
- 同时创建父子容器
- Tomcat在启动时执行ContextLoaderListener和DispatcherServlet对象的初始化方法, 执行容器refresh进行加载
- 在子容器加载时 创建SpringMVC所需的Bean和预准备的数据:(通过配置类+@EnableWebMvc配置(DelegatingWebMvcConfiguration)——可实现WebMvcConfigurer进行定制扩展)
- RequestMappingHandlerMapping,它会处理@RequestMapping 注解
- RequestMappingHandlerAdapter,则是处理请求的适配器,确定调用哪个类的哪个方法,并且构造方法参数,返回值。
- HandlerExceptionResolver 错误视图解析器
- addDefaultHttpMessageConverters 添加默认的消息转换器(解析json、解析xml)
- 子容器需要注入父容器的Bean时(比如Controller中需要@Autowired Service的Bean); 会先从子容器中找,没找到会去父容器中找

Web容器利用SPI机制,调用SpringServletContainerInitializer。取代在xml中配置的操作。具体流程
- tomcat启动时,利用SPI机制的ServiceLoader#load加载接口(ServletContainerInitializer)注册的所有实现类。
- SpringMVC在实现类SpringServletContainerInitializer上标注@HandlesTypes({WebApplicationInitializer.class})。
- tomcat会从classpath下找到所有的WebApplicationInitializer实现类,将所有的WebApplicationInitializer实现类作为第一个参数传入SpringServletContainerInitializer#onStartup方法,调用方法。
- 在SpringServletContainerInitializer#onStartup方法中,调用所有的WebApplicationInitializer实现类的onStartup方法。
@HandlesTypes注解由Servlet容器提供支持(实现),参数中指定的所有实现类。WEB容器(例如tomcat)利用字节码扫描框架(例如ASM、BCEL)从classpath中扫描出来,放入集合,传给回调方法onStartup的第一个参数。
@HandlesTypes(WebApplicationInitializer.class)public class SpringServletContainerInitializer implements ServletContainerInitializer {/*** 容器启动时,调用接口方法。并将@HandlesTypes标记的类型,传入方法。** 调用onStartup前,web容器会查找到@HandlesTypes标记的类* @param webAppInitializerClasses* @param servletContext web容器(例如tomcat)传入。可以通过servlet上下文对象动态注册三大组件* @throws ServletException*/@Overridepublic void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {// 1.tomcat通过SPI机制,调用到此方法中// 2.tomcat启动后 ,传入实现WebApplicationInitializer接口的类List<WebApplicationInitializer> initializers = new LinkedList<>();if (webAppInitializerClasses != null) {for (Class<?> waiClass : webAppInitializerClasses) {// Be defensive: Some servlet containers provide us with invalid classes,// no matter what @HandlesTypes says...//排除接口和抽象类。(Springmvc中实现WebApplicationInitializer接口的都是抽象类)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);//逐个调用WebApplicationInitializer类型的onStartup()for (WebApplicationInitializer initializer : initializers) {initializer.onStartup(servletContext);}}
AbstractDispatcherServletInitializer
@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {//以下步骤,可以在web.xml配置实现//注册ContextLoaderListener(创建父容器)super.onStartup(servletContext);//注册DispatcherServlet(创建子容器)registerDispatcherServlet(servletContext);}
AbstractContextLoaderInitializer
@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {registerContextLoaderListener(servletContext);}protected void registerContextLoaderListener(ServletContext servletContext) {//创建父容器。方法实现类:AbstractAnnotationConfigDispatcherServletInitializer// 1.调用getRootConfigClasses();方法获取父容器配置类(此抽象方法在我们自定义的子类中实现提供我们自定义的映射路径 )// 2.创建父容器,注册配置类WebApplicationContext rootAppContext = createRootApplicationContext();if (rootAppContext != null) {ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);//将监听器加入上下文中。监听mvc启动完成。完成后开始初始化DispatchSevletlistener.setContextInitializers(getRootApplicationContextInitializers());servletContext.addListener(listener);}else {logger.debug("No ContextLoaderListener registered, as " +"createRootApplicationContext() did not return an application context");}}
protected void registerDispatcherServlet(ServletContext servletContext) {//获取DispatchServlet名称(默认:dispatcher)String servletName = getServletName();Assert.hasLength(servletName, "getServletName() must not return null or empty");//创建子容器。实现类:AbstractAnnotationConfigDispatcherServletInitializerWebApplicationContext servletAppContext = createServletApplicationContext();Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");//实例化DispatcherServlet。tomcat会管理DispatchServlet生命周期FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");//初始化器dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());//注册dispatcherServletServletRegistration.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);//映射registration.addMapping(getServletMappings());//是否支持异步registration.setAsyncSupported(isAsyncSupported());Filter[] filters = getServletFilters();if (!ObjectUtils.isEmpty(filters)) {for (Filter filter : filters) {registerServletFilter(servletContext, filter);}}customizeRegistration(registration);}
初始化ContextLoaderListener
tomcat会调用ContextLoaderListener#contextInitialized 进行初始化
public void contextInitialized(ServletContextEvent event) {initWebApplicationContext(event.getServletContext());}public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {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!");}servletContext.log("Initializing Spring root WebApplicationContext");Log logger = LogFactory.getLog(ContextLoader.class);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) {this.context = createWebApplicationContext(servletContext);}if (this.context instanceof ConfigurableWebApplicationContext) {ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;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) {// 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);}//配置和刷线根容器对象。调用Spring容器refresh方法configureAndRefreshWebApplicationContext(cwac, servletContext);}}//将Spring上下文,保存到web应用上下文中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.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;}}
初始化DispatcherServlet
tomcat会调用DispatcherServlet#init()
1.注册监听
2.刷新子容器
