一、Spring整合SpringMVC

Spring利用父子容器整合SpringMVC。(父容器:Spring,子容器:mvc)。
父容器管理Service、Dao层实例对象;子容器管理Controller实例对象。
子容器可以访问父容器Bean。反之不可

1.利用web.xml整合

本质:Web容器解析web.xml。启动Spring容器,加载DispatcherServlet。
重点:注册ContextLoaderListener、DispatcherServlet

  1. <!--spring 基于web应用的启动-->
  2. <listener>
  3. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  4. </listener>
  5. <!--全局参数:spring配置文件-->
  6. <context-param>
  7. <param-name>contextConfigLocation</param-name>
  8. <param-value>classpath:spring-core.xml</param-value>
  9. </context-param>
  10. <!--前端调度器servlet-->
  11. <servlet>
  12. <servlet-name>dispatcherServlet</servlet-name>
  13. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  14. <!--设置配置文件的路径-->
  15. <init-param>
  16. <param-name>contextConfigLocation</param-name>
  17. <param-value>classpath:spring-mvc.xml</param-value>
  18. </init-param>
  19. <!--设置启动即加载-->
  20. <load-on-startup>1</load-on-startup>
  21. </servlet>
  22. <!--dispatcherServlet管理的请求路径-->
  23. <servlet-mapping>
  24. <servlet-name>dispatcherServlet</servlet-name>
  25. <url-pattern>/</url-pattern>
  26. <servlet-mapping>

2.零配置整合

本质:把配置利用代码实现,并且利用SPI机制机制,在WEB容器启动时进行加载。
核心类:SpringServletContainerInitializer,重点完成:注册ContextLoaderListener、DispatcherServlet

执行流程

  1. Tomcat在启动时会通过SPI注册 ContextLoaderListener和DispatcherServlet对象
    1. 同时创建父子容器
      1. 分别创建在ContextLoaderListener初始化时创建父容器设置配置类
      2. 在DispatcherServlet初始化时创建子容器 即2个ApplicationContext实例设置配置类
  2. Tomcat在启动时执行ContextLoaderListener和DispatcherServlet对象的初始化方法, 执行容器refresh进行加载
  3. 在子容器加载时 创建SpringMVC所需的Bean和预准备的数据:(通过配置类+@EnableWebMvc配置(DelegatingWebMvcConfiguration)——可实现WebMvcConfigurer进行定制扩展)
    1. RequestMappingHandlerMapping,它会处理@RequestMapping 注解
    2. RequestMappingHandlerAdapter,则是处理请求的适配器,确定调用哪个类的哪个方法,并且构造方法参数,返回值。
    3. HandlerExceptionResolver 错误视图解析器
    4. addDefaultHttpMessageConverters 添加默认的消息转换器(解析json、解析xml)
  4. 子容器需要注入父容器的Bean时(比如Controller中需要@Autowired Service的Bean); 会先从子容器中找,没找到会去父容器中找

image.png
Web容器利用SPI机制,调用SpringServletContainerInitializer。取代在xml中配置的操作。具体流程

  1. tomcat启动时,利用SPI机制的ServiceLoader#load加载接口(ServletContainerInitializer)注册的所有实现类。
  2. SpringMVC在实现类SpringServletContainerInitializer上标注@HandlesTypes({WebApplicationInitializer.class})。
  3. tomcat会从classpath下找到所有的WebApplicationInitializer实现类,将所有的WebApplicationInitializer实现类作为第一个参数传入SpringServletContainerInitializer#onStartup方法,调用方法。
  4. 在SpringServletContainerInitializer#onStartup方法中,调用所有的WebApplicationInitializer实现类的onStartup方法。

@HandlesTypes注解由Servlet容器提供支持(实现),参数中指定的所有实现类。WEB容器(例如tomcat)利用字节码扫描框架(例如ASM、BCEL)从classpath中扫描出来,放入集合,传给回调方法onStartup的第一个参数。

  1. @HandlesTypes(WebApplicationInitializer.class)
  2. public class SpringServletContainerInitializer implements ServletContainerInitializer {
  3. /**
  4. * 容器启动时,调用接口方法。并将@HandlesTypes标记的类型,传入方法。
  5. *
  6. * 调用onStartup前,web容器会查找到@HandlesTypes标记的类
  7. * @param webAppInitializerClasses
  8. * @param servletContext web容器(例如tomcat)传入。可以通过servlet上下文对象动态注册三大组件
  9. * @throws ServletException
  10. */
  11. @Override
  12. public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
  13. throws ServletException {
  14. // 1.tomcat通过SPI机制,调用到此方法中
  15. // 2.tomcat启动后 ,传入实现WebApplicationInitializer接口的类
  16. List<WebApplicationInitializer> initializers = new LinkedList<>();
  17. if (webAppInitializerClasses != null) {
  18. for (Class<?> waiClass : webAppInitializerClasses) {
  19. // Be defensive: Some servlet containers provide us with invalid classes,
  20. // no matter what @HandlesTypes says...
  21. //排除接口和抽象类。(Springmvc中实现WebApplicationInitializer接口的都是抽象类)
  22. if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
  23. WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
  24. try {
  25. //进行反射利用实例化后,添加到集合中。为调用做准备
  26. initializers.add((WebApplicationInitializer)
  27. ReflectionUtils.accessibleConstructor(waiClass).newInstance());
  28. }
  29. catch (Throwable ex) {
  30. throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
  31. }
  32. }
  33. }
  34. }
  35. if (initializers.isEmpty()) {
  36. servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
  37. return;
  38. }
  39. servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
  40. AnnotationAwareOrderComparator.sort(initializers);
  41. //逐个调用WebApplicationInitializer类型的onStartup()
  42. for (WebApplicationInitializer initializer : initializers) {
  43. initializer.onStartup(servletContext);
  44. }
  45. }

image.png

AbstractDispatcherServletInitializer

  1. @Override
  2. public void onStartup(ServletContext servletContext) throws ServletException {
  3. //以下步骤,可以在web.xml配置实现
  4. //注册ContextLoaderListener(创建父容器)
  5. super.onStartup(servletContext);
  6. //注册DispatcherServlet(创建子容器)
  7. registerDispatcherServlet(servletContext);
  8. }

AbstractContextLoaderInitializer

  1. @Override
  2. public void onStartup(ServletContext servletContext) throws ServletException {
  3. registerContextLoaderListener(servletContext);
  4. }
  5. protected void registerContextLoaderListener(ServletContext servletContext) {
  6. //创建父容器。方法实现类:AbstractAnnotationConfigDispatcherServletInitializer
  7. // 1.调用getRootConfigClasses();方法获取父容器配置类(此抽象方法在我们自定义的子类中实现提供我们自定义的映射路径 )
  8. // 2.创建父容器,注册配置类
  9. WebApplicationContext rootAppContext = createRootApplicationContext();
  10. if (rootAppContext != null) {
  11. ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
  12. //将监听器加入上下文中。监听mvc启动完成。完成后开始初始化DispatchSevlet
  13. listener.setContextInitializers(getRootApplicationContextInitializers());
  14. servletContext.addListener(listener);
  15. }
  16. else {
  17. logger.debug("No ContextLoaderListener registered, as " +
  18. "createRootApplicationContext() did not return an application context");
  19. }
  20. }
  1. protected void registerDispatcherServlet(ServletContext servletContext) {
  2. //获取DispatchServlet名称(默认:dispatcher)
  3. String servletName = getServletName();
  4. Assert.hasLength(servletName, "getServletName() must not return null or empty");
  5. //创建子容器。实现类:AbstractAnnotationConfigDispatcherServletInitializer
  6. WebApplicationContext servletAppContext = createServletApplicationContext();
  7. Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
  8. //实例化DispatcherServlet。tomcat会管理DispatchServlet生命周期
  9. FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
  10. Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
  11. //初始化器
  12. dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
  13. //注册dispatcherServlet
  14. ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
  15. if (registration == null) {
  16. throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
  17. "Check if there is another servlet registered under the same name.");
  18. }
  19. //启动 时加载
  20. registration.setLoadOnStartup(1);
  21. //映射
  22. registration.addMapping(getServletMappings());
  23. //是否支持异步
  24. registration.setAsyncSupported(isAsyncSupported());
  25. Filter[] filters = getServletFilters();
  26. if (!ObjectUtils.isEmpty(filters)) {
  27. for (Filter filter : filters) {
  28. registerServletFilter(servletContext, filter);
  29. }
  30. }
  31. customizeRegistration(registration);
  32. }

初始化ContextLoaderListener

tomcat会调用ContextLoaderListener#contextInitialized 进行初始化

  1. public void contextInitialized(ServletContextEvent event) {
  2. initWebApplicationContext(event.getServletContext());
  3. }
  4. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
  5. if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
  6. throw new IllegalStateException(
  7. "Cannot initialize context because there is already a root application context present - " +
  8. "check whether you have multiple ContextLoader* definitions in your web.xml!");
  9. }
  10. servletContext.log("Initializing Spring root WebApplicationContext");
  11. Log logger = LogFactory.getLog(ContextLoader.class);
  12. if (logger.isInfoEnabled()) {
  13. logger.info("Root WebApplicationContext: initialization started");
  14. }
  15. long startTime = System.currentTimeMillis();
  16. try {
  17. // Store context in local instance variable, to guarantee that
  18. // it is available on ServletContext shutdown.
  19. if (this.context == null) {
  20. this.context = createWebApplicationContext(servletContext);
  21. }
  22. if (this.context instanceof ConfigurableWebApplicationContext) {
  23. ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
  24. if (!cwac.isActive()) {
  25. // The context has not yet been refreshed -> provide services such as
  26. // setting the parent context, setting the application context id, etc
  27. if (cwac.getParent() == null) {
  28. // The context instance was injected without an explicit parent ->
  29. // determine parent for root web application context, if any.
  30. ApplicationContext parent = loadParentContext(servletContext);
  31. cwac.setParent(parent);
  32. }
  33. //配置和刷线根容器对象。调用Spring容器refresh方法
  34. configureAndRefreshWebApplicationContext(cwac, servletContext);
  35. }
  36. }
  37. //将Spring上下文,保存到web应用上下文中
  38. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
  39. ClassLoader ccl = Thread.currentThread().getContextClassLoader();
  40. if (ccl == ContextLoader.class.getClassLoader()) {
  41. currentContext = this.context;
  42. }
  43. else if (ccl != null) {
  44. currentContextPerThread.put(ccl, this.context);
  45. }
  46. if (logger.isInfoEnabled()) {
  47. long elapsedTime = System.currentTimeMillis() - startTime;
  48. logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
  49. }
  50. return this.context;
  51. }
  52. catch (RuntimeException | Error ex) {
  53. logger.error("Context initialization failed", ex);
  54. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
  55. throw ex;
  56. }
  57. }

初始化DispatcherServlet

tomcat会调用DispatcherServlet#init()
1.注册监听
2.刷新子容器