​上下文在web容器中的启动

1,IoC容器的启动过程

IoC的启动过程就是建立上下文的过程。该上下文是与ServletContext相伴。在Spring中存在一个核心控制分发器,DispatcherServlet,这是Spring的核心。在web容器启动Spring应用程序时。首先建立根上下文,然后ContextLoader建立WebApplicationContext。

Web容器中启动Spring步骤例如以下:

在web.xml中。已经配置了ContextLoadListener,该类实现了ServletLoaderListener接口。

ServletLoaderListener是在Servlet API中定义的接口,接口提供Servlet生命周期。而详细的IoC加载过程是ContextLoadListener交由ContextLoader完毕,ContextLoader是ContextLoadListener的父类。三者关系例如以下:

在ContextLoader中。完毕了两个IoC建立的基本过程,一是在Web容器中建立起双亲IoC容器,还有一个是生成对应的WebApplicationContext并将其初始化。

2。Web容器中的上下文设计。

Spring为Web提供了上下文扩展接口WebApplicationContext来满足启动过程的须要。

该接口主要提供getServletContext方法,通过这种方法能够获取容器的Web上下文。

  1. /**
  2. * Return the standard Servlet API ServletContext for this application.
  3. * <p>Also available for a Portlet application, in addition to the PortletContext.
  4. */
  5. ServletContext getServletContext();

在启动过程中,Spring使用XmlWebApplicationContext作为默认的实现。

XmlWebApplicationContext定义了一系列常量,定义了默认的配置文件,文件位置以及文件类型。

  1. /** Default config location for the root context */
  2. public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
  3. /** Default prefix for building a config location for a namespace */
  4. public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
  5. /** Default suffix for building a config location for a namespace */
  6. public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

通时。该类的方法中。定义了通过xml启动IoC的过程:

  1. @Override
  2. protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
  3. // Create a new XmlBeanDefinitionReader for the given BeanFactory.
  4. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
  5. // Configure the bean definition reader with this context's
  6. // resource loading environment.
  7. beanDefinitionReader.setEnvironment(getEnvironment());
  8. beanDefinitionReader.setResourceLoader(this);
  9. beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
  10. // Allow a subclass to provide custom initialization of the reader,
  11. // then proceed with actually loading the bean definitions.
  12. initBeanDefinitionReader(beanDefinitionReader);
  13. loadBeanDefinitions(beanDefinitionReader);
  14. }

完毕对配置文件的读取:

  1. protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
  2. String[] configLocations = getConfigLocations();
  3. if (configLocations != null) {
  4. for (String configLocation : configLocations) {
  5. reader.loadBeanDefinitions(configLocation);
  6. }
  7. }
  8. }

获取配置文件位置。

  1. protected String[] getDefaultConfigLocations() {
  2. if (getNamespace() != null) {
  3. return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
  4. }
  5. else {
  6. return new String[] {DEFAULT_CONFIG_LOCATION};
  7. }
  8. }

XmlWebApplicationContext类代码中能够看到,该类仅仅是实现了基础的基于XML文件的部分操作。详细的Servlet上下文,则默认取自其父类。

3。ContextLoader的设计与实现

ContextLoaderListener通过ContextLoader完毕IoC的初始化。

ContextLoaderListener的详细实现思路例如以下:ContextLoaderListener监听器启动根IoC容器并把它加载到Web容器的主要功能模块,这也是这个SpringWeb应用加载IoC的第一个地方。

从加载过程能够看到,首先从Servelt事件中得到ServletContext,然后能够读取配置在web.xml中的各个相关属性。记着ContextLoader会实例化WebApplicationContext,并完毕其加载和初始化过程。这个被初始化的第一个上下文作为根上下文存在,这个根上下文加载后。被绑定到Web应用程序的ServletCOntext上。怎样须要訪问根上下文的应用程序代码都能够通过WebApplicationContextUtils类的静态方法中得到。该根上下文存储至:ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

在ContextLoaderListener中,会监听ServletContext的详细变化,如创建和销毁,并再监听器中定义了对应的回调方法。

  1. public ContextLoaderListener(WebApplicationContext context) {
  2. super(context);
  3. }
  4. /**
  5. * Initialize the root web application context.
  6. */
  7. @Override
  8. public void contextInitialized(ServletContextEvent event) {
  9. initWebApplicationContext(event.getServletContext());
  10. }
  11. /**
  12. * Close the root web application context.
  13. */
  14. @Override
  15. public void contextDestroyed(ServletContextEvent event) {
  16. closeWebApplicationContext(event.getServletContext());
  17. ContextCleanupListener.cleanupAttributes(event.getServletContext());
  18. }

详细的调用。交由ContextLoader实现。例如以下。ContextLoader的初始化容器方法:

  1. /**
  2. * Initialize Spring's web application context for the given servlet context,
  3. * using the application context provided at construction time, or creating a new one
  4. * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
  5. * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
  6. * @param servletContext current servlet context
  7. * @return the new WebApplicationContext
  8. * @see #ContextLoader(WebApplicationContext)
  9. * @see #CONTEXT_CLASS_PARAM
  10. * @see #CONFIG_LOCATION_PARAM
  11. */
  12. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
  13. if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {//是否已存在根上下文
  14. throw new IllegalStateException(
  15. "Cannot initialize context because there is already a root application context present - " +
  16. "check whether you have multiple ContextLoader* definitions in your web.xml!");
  17. }
  18. Log logger = LogFactory.getLog(ContextLoader.class);
  19. servletContext.log("Initializing Spring root WebApplicationContext");
  20. if (logger.isInfoEnabled()) {
  21. logger.info("Root WebApplicationContext: initialization started");
  22. }
  23. long startTime = System.currentTimeMillis();
  24. try {
  25. // Store context in local instance variable, to guarantee that
  26. // it is available on ServletContext shutdown.
  27. if (this.context == null) {//创建上下文
  28. this.context = createWebApplicationContext(servletContext);
  29. }
  30. if (this.context instanceof ConfigurableWebApplicationContext) {
  31. ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
  32. if (!cwac.isActive()) {
  33. // The context has not yet been refreshed -> provide services such as
  34. // setting the parent context, setting the application context id, etc
  35. if (cwac.getParent() == null) {
  36. // The context instance was injected without an explicit parent ->
  37. // determine parent for root web application context, if any.加载双亲上下文
  38. ApplicationContext parent = loadParentContext(servletContext);
  39. cwac.setParent(parent);
  40. }
  41. //配置各项參数。以及对应的初始化方法
  42. configureAndRefreshWebApplicationContext(cwac, servletContext);
  43. }
  44. }
  45. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
  46. ClassLoader ccl = Thread.currentThread().getContextClassLoader();
  47. if (ccl == ContextLoader.class.getClassLoader()) {
  48. currentContext = this.context;
  49. }
  50. else if (ccl != null) {
  51. currentContextPerThread.put(ccl, this.context);
  52. }
  53. if (logger.isDebugEnabled()) {
  54. logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
  55. WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
  56. }
  57. if (logger.isInfoEnabled()) {
  58. long elapsedTime = System.currentTimeMillis() - startTime;
  59. logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
  60. }
  61. return this.context;
  62. }
  63. catch (RuntimeException ex) {
  64. logger.error("Context initialization failed", ex);
  65. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
  66. throw ex;
  67. }
  68. catch (Error err) {
  69. logger.error("Context initialization failed", err);
  70. servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
  71. throw err;
  72. }
  73. }

createWebApplicationContext是详细的创建根上下文的方法。

  1. protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
  2. Class<?> contextClass = determineContextClass(sc);//Return the WebApplicationContext implementation class to use
  3. if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
  4. throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
  5. "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
  6. }
  7. return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  8. }

创建好上下文的后。调用了configureAndRefreshWebApplicationContext方法:

  1. protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
  2. if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
  3. // The application context id is still set to its original default value
  4. // -> assign a more useful id based on available information
  5. String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
  6. if (idParam != null) {
  7. wac.setId(idParam);
  8. }
  9. else {
  10. // Generate default id...
  11. wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
  12. ObjectUtils.getDisplayString(sc.getContextPath()));
  13. }
  14. }
  15. wac.setServletContext(sc);
  16. String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
  17. if (configLocationParam != null) {
  18. wac.setConfigLocation(configLocationParam);
  19. }
  20. // The wac environment's #initPropertySources will be called in any case when the context
  21. // is refreshed; do it eagerly here to ensure servlet property sources are in place for
  22. // use in any post-processing or initialization that occurs below prior to #refresh
  23. ConfigurableEnvironment env = wac.getEnvironment();
  24. if (env instanceof ConfigurableWebEnvironment) {
  25. ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
  26. }
  27. customizeContext(sc, wac);
  28. wac.refresh();
  29. }

determineContextClass方法中。详细了详细实现什么上下文对象。默觉得XmlWebApplicationContext。

  1. protected Class<?> determineContextClass(ServletContext servletContext) {
  2. //配置參数
  3. String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
  4. if (contextClassName != null) {
  5. try {
  6. return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
  7. }
  8. catch (ClassNotFoundException ex) {
  9. throw new ApplicationContextException(
  10. "Failed to load custom context class [" + contextClassName + "]", ex);
  11. }
  12. }
  13. else {//没有额外配置则採用默认的配置
  14. contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
  15. try {
  16. return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
  17. }
  18. catch (ClassNotFoundException ex) {
  19. throw new ApplicationContextException(
  20. "Failed to load default context class [" + contextClassName + "]", ex);
  21. }
  22. }
  23. }

以上就是IoC容器在Web容器中的启动过程,与应用中启动IoC基本同样,唯一不同的是须要考虑web容器特点。设置相应web容器须要的參数。

Spring MVC的设计与实现

Spring MVC出了配置ContextListener之类,还需配置核心分发器DispatcherServlet。DispatcherServlet作为一个前端控制器,全部web请求都要经过他来处理,其对请求进行转发。匹配,数据处理后。再由页面处理。DispatcherServlet是Spring MVC的核心。

1,概览

完毕ContextLoaderListener初始化之后,Web容器開始初始化DispatcherServlet。DispatcherServlet会建立自己的上下文来持有Spring MVC的Bean对象。在建立自己的上下文时,会从ServletContext中得到根上下文作为DispatcherServlet持有的上下文的双亲上下文。有了根上下文。在对自己持有上下文进行初始化,最后把自己的上下文保存到ServletContex,供以后使用。

DispatcherServlet继承自FramworkServlet,FramworkServlet继承HttpServletBean。HttpServletBean继承HttpServlet,通过Servlet API对请求对应。

DispatcherServlet的工作大致分为两个:-1,初始化部分。由initServletBean启动。通过initWebApplicationContext()方法终于抵用DispatcherServlet的initStraegies方法,在这种方法里,DispatcherServlet对MVC的其它模块进行了初始化。比方handlerMapping,ViewResolver。-2,对HTTP请求进行响应。作为一个Servlet,Web容器会调用Servlet的doGet和doPost方法,在经过FraeWorkServlet处理后。会调用DispatcherServlet的doService()方法,这种方法中封装了doDispatch()方法,doDispatch()是DIspatcher实现MVC的主要部分。

2。DispatcherServlet的启动和初始化

DispatcherServlet的初始化方法的開始。是由HttpServletBean的init()方法開始的。该方法负责获取相应的各项配置參数:

  1. /**
  2. * Map config parameters onto bean properties of this servlet, and
  3. * invoke subclass initialization.
  4. * @throws ServletException if bean properties are invalid (or required
  5. * properties are missing), or if subclass initialization fails.
  6. */
  7. @Override
  8. public final void init() throws ServletException {
  9. if (logger.isDebugEnabled()) {
  10. logger.debug("Initializing servlet '" + getServletName() + "'");
  11. }
  12. // Set bean properties from init parameters.
  13. try {
  14. PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
  15. BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
  16. ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
  17. bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
  18. initBeanWrapper(bw);
  19. bw.setPropertyValues(pvs, true);
  20. }
  21. catch (BeansException ex) {
  22. logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
  23. throw ex;
  24. }
  25. // Let subclasses do whatever initialization they like.
  26. initServletBean();
  27. if (logger.isDebugEnabled()) {
  28. logger.debug("Servlet '" + getServletName() + "' configured successfully");
  29. }
  30. }

在该方法中。调用了子类的initServletBean()方法,即FrameworkServlet中的initServletBean()方法:

  1. /**
  2. * Overridden method of {@link HttpServletBean}, invoked after any bean properties
  3. * have been set. Creates this servlet's WebApplicationContext.
  4. */
  5. @Override
  6. protected final void initServletBean() throws ServletException {
  7. getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
  8. if (this.logger.isInfoEnabled()) {
  9. this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
  10. }
  11. long startTime = System.currentTimeMillis();
  12. try {//初始化上下文
  13. this.webApplicationContext = initWebApplicationContext();
  14. initFrameworkServlet();
  15. }
  16. catch (ServletException ex) {
  17. this.logger.error("Context initialization failed", ex);
  18. throw ex;
  19. }
  20. catch (RuntimeException ex) {
  21. this.logger.error("Context initialization failed", ex);
  22. throw ex;
  23. }
  24. if (this.logger.isInfoEnabled()) {
  25. long elapsedTime = System.currentTimeMillis() - startTime;
  26. this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
  27. elapsedTime + " ms");
  28. }
  29. }

该方法中,通过initWebApplicationContext()完毕初始化上下文操作:

  1. protected WebApplicationContext initWebApplicationContext() {
  2. WebApplicationContext rootContext =
  3. WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  4. WebApplicationContext wac = null;
  5. if (this.webApplicationContext != null) {
  6. // A context instance was injected at construction time -> use it
  7. wac = this.webApplicationContext;
  8. if (wac instanceof ConfigurableWebApplicationContext) {
  9. ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
  10. if (!cwac.isActive()) {
  11. // The context has not yet been refreshed -> provide services such as
  12. // setting the parent context, setting the application context id, etc
  13. if (cwac.getParent() == null) {
  14. // The context instance was injected without an explicit parent -> set
  15. // the root application context (if any; may be null) as the parent
  16. cwac.setParent(rootContext);
  17. }
  18. configureAndRefreshWebApplicationContext(cwac);
  19. }
  20. }
  21. }
  22. if (wac == null) {
  23. // No context instance was injected at construction time -> see if one
  24. // has been registered in the servlet context. If one exists, it is assumed
  25. // that the parent context (if any) has already been set and that the
  26. // user has performed any initialization such as setting the context id
  27. wac = findWebApplicationContext();
  28. }
  29. if (wac == null) {
  30. // No context instance is defined for this servlet -> create a local one
  31. wac = createWebApplicationContext(rootContext);
  32. }
  33. if (!this.refreshEventReceived) {
  34. // Either the context is not a ConfigurableApplicationContext with refresh
  35. // support or the context injected at construction time had already been
  36. // refreshed -> trigger initial onRefresh manually here.
  37. onRefresh(wac);
  38. }
  39. if (this.publishContext) {
  40. // Publish the context as a servlet context attribute.
  41. String attrName = getServletContextAttributeName();
  42. getServletContext().setAttribute(attrName, wac);
  43. if (this.logger.isDebugEnabled()) {
  44. this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
  45. "' as ServletContext attribute with name [" + attrName + "]");
  46. }
  47. }
  48. return wac;
  49. }

通过WebApplicationContextUtils.getWebApplicationContext获取根上下文。

  1. public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
  2. return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
  3. }
  4. public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
  5. Assert.notNull(sc, "ServletContext must not be null");
  6. Object attr = sc.getAttribute(attrName);
  7. if (attr == null) {
  8. return null;
  9. }
  10. if (attr instanceof RuntimeException) {
  11. throw (RuntimeException) attr;
  12. }
  13. if (attr instanceof Error) {
  14. throw (Error) attr;
  15. }
  16. if (attr instanceof Exception) {
  17. throw new IllegalStateException((Exception) attr);
  18. }
  19. if (!(attr instanceof WebApplicationContext)) {
  20. throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
  21. }
  22. return (WebApplicationContext) attr;
  23. }

建立好根上下文后,在通过createWebApplicationContext建立DispatcherServlet的上下文。

  1. protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
  2. Class<?> contextClass = getContextClass();
  3. if (this.logger.isDebugEnabled()) {
  4. this.logger.debug("Servlet with name '" + getServletName() +
  5. "' will try to create custom WebApplicationContext context of class '" +
  6. contextClass.getName() + "'" + ", using parent context [" + parent + "]");
  7. }
  8. if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
  9. throw new ApplicationContextException(
  10. "Fatal initialization error in servlet with name '" + getServletName() +
  11. "': custom WebApplicationContext class [" + contextClass.getName() +
  12. "] is not of type ConfigurableWebApplicationContext");
  13. }
  14. ConfigurableWebApplicationContext wac =
  15. (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  16. wac.setEnvironment(getEnvironment());
  17. wac.setParent(parent);
  18. wac.setConfigLocation(getContextConfigLocation());
  19. configureAndRefreshWebApplicationContext(wac);
  20. return wac;
  21. }

再通过configureAndRefreshWebApplicationContext完毕各项配置以及初始化:

  1. protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
  2. if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
  3. // The application context id is still set to its original default value
  4. // -> assign a more useful id based on available information
  5. if (this.contextId != null) {
  6. wac.setId(this.contextId);
  7. }
  8. else {
  9. // Generate default id...
  10. wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
  11. ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
  12. }
  13. }
  14. wac.setServletContext(getServletContext());
  15. wac.setServletConfig(getServletConfig());
  16. wac.setNamespace(getNamespace());
  17. wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
  18. // The wac environment's #initPropertySources will be called in any case when the context
  19. // is refreshed; do it eagerly here to ensure servlet property sources are in place for
  20. // use in any post-processing or initialization that occurs below prior to #refresh
  21. ConfigurableEnvironment env = wac.getEnvironment();
  22. if (env instanceof ConfigurableWebEnvironment) {
  23. ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
  24. }
  25. postProcessWebApplicationContext(wac);
  26. applyInitializers(wac);
  27. wac.refresh();//初始化
  28. }

以上完毕了DispatcherServlet的IoC容器的建立。

在Spring MVC DispatcherServlet的初始化过程中。以对HandlerMapping的初始化作为開始。该初始化过程。以HttpServletbean的init方法開始,到FrameWorkServlet的initServletBean,再到DispatcherServlet的onRefresh方法。最后到initStrategies(),启动整个MVC的初始化。

  1. /**
  2. * This implementation calls {@link #initStrategies}.
  3. */
  4. @Override
  5. protected void onRefresh(ApplicationContext context) {
  6. initStrategies(context);
  7. }
  8. /**
  9. * Initialize the strategy objects that this servlet uses.
  10. * <p>May be overridden in subclasses in order to initialize further strategy objects.
  11. */
  12. protected void initStrategies(ApplicationContext context) {
  13. initMultipartResolver(context);
  14. initLocaleResolver(context);
  15. initThemeResolver(context);
  16. initHandlerMappings(context);
  17. initHandlerAdapters(context);
  18. initHandlerExceptionResolvers(context);
  19. initRequestToViewNameTranslator(context);
  20. initViewResolvers(context);
  21. initFlashMapManager(context);
  22. }

HandlerMappings的初始化例如以下:

  1. private void initHandlerMappings(ApplicationContext context) {
  2. this.handlerMappings = null;
  3. if (this.detectAllHandlerMappings) {
  4. // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
  5. Map<String, HandlerMapping> matchingBeans =
  6. BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
  7. if (!matchingBeans.isEmpty()) {
  8. this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
  9. // We keep HandlerMappings in sorted order.
  10. OrderComparator.sort(this.handlerMappings);
  11. }
  12. }
  13. else {
  14. try {
  15. HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
  16. this.handlerMappings = Collections.singletonList(hm);
  17. }
  18. catch (NoSuchBeanDefinitionException ex) {
  19. // Ignore, we'll add a default HandlerMapping later.
  20. }
  21. }
  22. // Ensure we have at least one HandlerMapping, by registering
  23. // a default HandlerMapping if no other mappings are found.
  24. if (this.handlerMappings == null) {
  25. this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
  26. if (logger.isDebugEnabled()) {
  27. logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
  28. }
  29. }
  30. }

以上即为HandlerMappings的读取过程。

MVC处理HTTP分发请求

1。HandlerMapping的配置和设计原理

在初始化完毕时。在上下文环境中已经定义的全部HandlerMapping都已经被载入了,这些HandlerMapping被放入List并排序。存储着HTTP请求的相应映射数据。Spring提供了一系列HandlerMapping实现。SimpleUrlHandlerMapping为例。在SimpleUrlHandlerMapping中定义了一个Map持有一系列的映射关系,这些映射关系使SpringMVC能够相应到Controller中。

这些映射关系通过HandlerMapping接口封装。通过getHandler获取相应的HandlerExcutionChain,在HandlerExcutionChain中封装详细的Controller。

HandlerMapping接口的方法例如以下:

  1. HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

此处是典型的命令模式。

HandlerExecutionChain中持有一个Inteceptor链和一个handler对象。该handler实际就是Controller对象。

  1. public class HandlerExecutionChain {
  2. private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);
  3. private final Object handler;
  4. private HandlerInterceptor[] interceptors;
  5. private List<HandlerInterceptor> interceptorList;
  6. private int interceptorIndex = -1;

构造函数:

  1. /**
  2. * Create a new HandlerExecutionChain.
  3. * @param handler the handler object to execute
  4. * @param interceptors the array of interceptors to apply
  5. * (in the given order) before the handler itself executes
  6. */
  7. public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) {
  8. if (handler instanceof HandlerExecutionChain) {
  9. HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
  10. this.handler = originalChain.getHandler();
  11. this.interceptorList = new ArrayList<HandlerInterceptor>();
  12. CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
  13. CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
  14. }
  15. else {
  16. this.handler = handler;
  17. this.interceptors = interceptors;
  18. }
  19. }

HandlerExecutionChain中定义的Handler和Interceptor须要在定义HandlerMapping的时候配置好。

以SimpleUrlHandlerMapping为例。

在初始化时。会调用。

  1. /**
  2. * Calls the {@link #registerHandlers} method in addition to the
  3. * superclass's initialization.
  4. */
  5. @Override
  6. public void initApplicationContext() throws BeansException {
  7. super.initApplicationContext();
  8. registerHandlers(this.urlMap);
  9. }
  10. /**
  11. * Register all handlers specified in the URL map for the corresponding paths.
  12. * @param urlMap Map with URL paths as keys and handler beans or bean names as values
  13. * @throws BeansException if a handler couldn't be registered
  14. * @throws IllegalStateException if there is a conflicting handler registered
  15. */
  16. protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
  17. if (urlMap.isEmpty()) {
  18. logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
  19. }
  20. else {
  21. for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
  22. String url = entry.getKey();
  23. Object handler = entry.getValue();
  24. // Prepend with slash if not already present.
  25. if (!url.startsWith("/")) {
  26. url = "/" + url;
  27. }
  28. // Remove whitespace from handler bean name.
  29. if (handler instanceof String) {
  30. handler = ((String) handler).trim();
  31. }
  32. registerHandler(url, handler);
  33. }
  34. }
  35. }

部分发放基于其父类,AbstractUrlhandlerMapping。

  1. /**
  2. * Register the specified handler for the given URL path.
  3. * @param urlPath the URL the bean should be mapped to
  4. * @param handler the handler instance or handler bean name String
  5. * (a bean name will automatically be resolved into the corresponding handler bean)
  6. * @throws BeansException if the handler couldn't be registered
  7. * @throws IllegalStateException if there is a conflicting handler registered
  8. */
  9. protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
  10. Assert.notNull(urlPath, "URL path must not be null");
  11. Assert.notNull(handler, "Handler object must not be null");
  12. Object resolvedHandler = handler;
  13. // Eagerly resolve handler if referencing singleton via name.
  14. if (!this.lazyInitHandlers && handler instanceof String) {
  15. String handlerName = (String) handler;
  16. if (getApplicationContext().isSingleton(handlerName)) {
  17. resolvedHandler = getApplicationContext().getBean(handlerName);
  18. }
  19. }
  20. Object mappedHandler = this.handlerMap.get(urlPath);
  21. if (mappedHandler != null) {
  22. if (mappedHandler != resolvedHandler) {
  23. throw new IllegalStateException(
  24. "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
  25. "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
  26. }
  27. }
  28. else {
  29. if (urlPath.equals("/")) {
  30. if (logger.isInfoEnabled()) {
  31. logger.info("Root mapping to " + getHandlerDescription(handler));
  32. }
  33. setRootHandler(resolvedHandler);
  34. }
  35. else if (urlPath.equals("/*")) {
  36. if (logger.isInfoEnabled()) {
  37. logger.info("Default mapping to " + getHandlerDescription(handler));
  38. }
  39. setDefaultHandler(resolvedHandler);
  40. }
  41. else {
  42. this.handlerMap.put(urlPath, resolvedHandler);
  43. if (logger.isInfoEnabled()) {
  44. logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
  45. }
  46. }
  47. }
  48. }

hadnlerMap保存了URL和controller的映射关系。

  1. private final Map<String, Object> handlerMap = new LinkedHashMap<String, Object>();

2,使用HandlerMapping完毕请求的处理。

在HandlerExecutionChain的启动过程中,调用了getHandler方法,该方法就是完毕请求映射处理的地方。AbstractHadnlerMapping的getHandler方法例如以下:

  1. /**
  2. * Look up a handler for the given request, falling back to the default
  3. * handler if no specific one is found.
  4. * @param request current HTTP request
  5. * @return the corresponding handler instance, or the default handler
  6. * @see #getHandlerInternal
  7. */
  8. @Override
  9. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  10. Object handler = getHandlerInternal(request);
  11. if (handler == null) {
  12. handler = getDefaultHandler();
  13. }
  14. if (handler == null) {
  15. return null;
  16. }
  17. // Bean name or resolved handler?
  18. if (handler instanceof String) {
  19. String handlerName = (String) handler;
  20. handler = getApplicationContext().getBean(handlerName);
  21. }
  22. return getHandlerExecutionChain(handler, request);
  23. }
  1. /**
  2. * Build a {@link HandlerExecutionChain} for the given handler, including
  3. * applicable interceptors.
  4. * <p>The default implementation builds a standard {@link HandlerExecutionChain}
  5. * with the given handler, the handler mapping's common interceptors, and any
  6. * {@link MappedInterceptor}s matching to the current request URL. Subclasses
  7. * may override this in order to extend/rearrange the list of interceptors.
  8. * <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a
  9. * pre-built {@link HandlerExecutionChain}. This method should handle those
  10. * two cases explicitly, either building a new {@link HandlerExecutionChain}
  11. * or extending the existing chain.
  12. * <p>For simply adding an interceptor in a custom subclass, consider calling
  13. * {@code super.getHandlerExecutionChain(handler, request)} and invoking
  14. * {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
  15. * @param handler the resolved handler instance (never {@code null})
  16. * @param request current HTTP request
  17. * @return the HandlerExecutionChain (never {@code null})
  18. * @see #getAdaptedInterceptors()
  19. */
  20. protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
  21. HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
  22. (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
  23. chain.addInterceptors(getAdaptedInterceptors());
  24. String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
  25. for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
  26. if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
  27. chain.addInterceptor(mappedInterceptor.getInterceptor());
  28. }
  29. }
  30. return chain;
  31. }

取得Handler的详细方法在getHandlerInternal()。该方法的详细实如今AbstractUrlHandlerMapping中:

  1. /**
  2. * Look up a handler for the URL path of the given request.
  3. * @param request current HTTP request
  4. * @return the handler instance, or {@code null} if none found
  5. */
  6. @Override
  7. protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
  8. //从request获取请求路径
  9. String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
  10. //请求路径与Handler匹配
  11. Object handler = lookupHandler(lookupPath, request);
  12. if (handler == null) {
  13. // We need to care for the default handler directly, since we need to
  14. // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
  15. Object rawHandler = null;
  16. if ("/".equals(lookupPath)) {
  17. rawHandler = getRootHandler();
  18. }
  19. if (rawHandler == null) {
  20. rawHandler = getDefaultHandler();
  21. }
  22. if (rawHandler != null) {
  23. // Bean name or resolved handler?
  24. if (rawHandler instanceof String) {
  25. String handlerName = (String) rawHandler;
  26. rawHandler = getApplicationContext().getBean(handlerName);
  27. }
  28. validateHandler(rawHandler, request);
  29. handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
  30. }
  31. }
  32. if (handler != null && logger.isDebugEnabled()) {
  33. logger.debug("Mapping [" + lookupPath + "] to " + handler);
  34. }
  35. else if (handler == null && logger.isTraceEnabled()) {
  36. logger.trace("No handler mapping found for [" + lookupPath + "]");
  37. }
  38. return handler;
  39. }
  1. /**
  2. * Look up a handler instance for the given URL path.
  3. * <p>Supports direct matches, e.g. a registered "/test" matches "/test",
  4. * and various Ant-style pattern matches, e.g. a registered "/t*" matches
  5. * both "/test" and "/team". For details, see the AntPathMatcher class.
  6. * <p>Looks for the most exact pattern, where most exact is defined as
  7. * the longest path pattern.
  8. * @param urlPath URL the bean is mapped to
  9. * @param request current HTTP request (to expose the path within the mapping to)
  10. * @return the associated handler instance, or {@code null} if not found
  11. * @see #exposePathWithinMapping
  12. * @see org.springframework.util.AntPathMatcher
  13. */
  14. protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
  15. // Direct match?
  16. Object handler = this.handlerMap.get(urlPath);
  17. if (handler != null) {
  18. // Bean name or resolved handler?
  19. if (handler instanceof String) {
  20. String handlerName = (String) handler;
  21. handler = getApplicationContext().getBean(handlerName);
  22. }
  23. validateHandler(handler, request);
  24. return buildPathExposingHandler(handler, urlPath, urlPath, null);
  25. }
  26. // Pattern match?
  27. List<String> matchingPatterns = new ArrayList<String>();
  28. for (String registeredPattern : this.handlerMap.keySet()) {
  29. if (getPathMatcher().match(registeredPattern, urlPath)) {
  30. matchingPatterns.add(registeredPattern);
  31. }
  32. }
  33. String bestPatternMatch = null;
  34. Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
  35. if (!matchingPatterns.isEmpty()) {
  36. Collections.sort(matchingPatterns, patternComparator);
  37. if (logger.isDebugEnabled()) {
  38. logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
  39. }
  40. bestPatternMatch = matchingPatterns.get(0);
  41. }
  42. if (bestPatternMatch != null) {
  43. handler = this.handlerMap.get(bestPatternMatch);
  44. // Bean name or resolved handler?
  45. if (handler instanceof String) {
  46. String handlerName = (String) handler;
  47. handler = getApplicationContext().getBean(handlerName);
  48. }
  49. validateHandler(handler, request);
  50. String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath);
  51. // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
  52. // for all of them
  53. Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
  54. for (String matchingPattern : matchingPatterns) {
  55. if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
  56. Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
  57. Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
  58. uriTemplateVariables.putAll(decodedVars);
  59. }
  60. }
  61. if (logger.isDebugEnabled()) {
  62. logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
  63. }
  64. return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
  65. }
  66. // No handler found...
  67. return null;
  68. }

经过一系列对HTTP请求进行解析和匹配handler的过程,得到了与请求对一个的handler处理器。在返回的handler中。已经完毕了在HandlerExecutionChain中的封装工作,为handler对HTTP请求的响应做好了准备。

3,Spring对HTTP请求的分发处理。

DispatcherServlet是Servlet的子类,通过doService来响应HTTP请求。

  1. /**
  2. * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
  3. * for the actual dispatching.
  4. */
  5. @Override
  6. protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
  7. if (logger.isDebugEnabled()) {
  8. String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
  9. logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
  10. " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
  11. }
  12. // Keep a snapshot of the request attributes in case of an include,
  13. // to be able to restore the original attributes after the include.
  14. Map<String, Object> attributesSnapshot = null;
  15. if (WebUtils.isIncludeRequest(request)) {
  16. attributesSnapshot = new HashMap<String, Object>();
  17. Enumeration<? > attrNames = request.getAttributeNames();
  18. while (attrNames.hasMoreElements()) {
  19. String attrName = (String) attrNames.nextElement();
  20. if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
  21. attributesSnapshot.put(attrName, request.getAttribute(attrName));
  22. }
  23. }
  24. }
  25. // Make framework objects available to handlers and view objects.
  26. request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
  27. request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
  28. request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
  29. request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
  30. FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
  31. if (inputFlashMap != null) {
  32. request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
  33. }
  34. request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
  35. request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
  36. try {
  37. doDispatch(request, response);
  38. }
  39. finally {
  40. if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
  41. return;
  42. }
  43. // Restore the original attribute snapshot, in case of an include.
  44. if (attributesSnapshot != null) {
  45. restoreAttributesAfterInclude(request, attributesSnapshot);
  46. }
  47. }
  48. }

doDispatch方法:

  1. /**
  2. * Process the actual dispatching to the handler.
  3. * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
  4. * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
  5. * to find the first that supports the handler class.
  6. * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
  7. * themselves to decide which methods are acceptable.
  8. * @param request current HTTP request
  9. * @param response current HTTP response
  10. * @throws Exception in case of any kind of processing failure
  11. */
  12. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  13. HttpServletRequest processedRequest = request;
  14. HandlerExecutionChain mappedHandler = null;
  15. boolean multipartRequestParsed = false;
  16. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  17. try {
  18. ModelAndView mv = null;
  19. Exception dispatchException = null;
  20. try {
  21. processedRequest = checkMultipart(request);
  22. multipartRequestParsed = (processedRequest != request);
  23. // Determine handler for the current request.
  24. mappedHandler = getHandler(processedRequest);
  25. if (mappedHandler == null || mappedHandler.getHandler() == null) {
  26. noHandlerFound(processedRequest, response);
  27. return;
  28. }
  29. // Determine handler adapter for the current request.
  30. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  31. // Process last-modified header, if supported by the handler.
  32. String method = request.getMethod();
  33. boolean isGet = "GET".equals(method);
  34. if (isGet || "HEAD".equals(method)) {
  35. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  36. if (logger.isDebugEnabled()) {
  37. logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
  38. }
  39. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  40. return;
  41. }
  42. }
  43. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  44. return;
  45. }
  46. try {
  47. // Actually invoke the handler.
  48. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  49. }
  50. finally {
  51. if (asyncManager.isConcurrentHandlingStarted()) {
  52. return;
  53. }
  54. }
  55. applyDefaultViewName(request, mv);
  56. mappedHandler.applyPostHandle(processedRequest, response, mv);
  57. }
  58. catch (Exception ex) {
  59. dispatchException = ex;
  60. }
  61. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  62. }
  63. catch (Exception ex) {
  64. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  65. }
  66. catch (Error err) {
  67. triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
  68. }
  69. finally {
  70. if (asyncManager.isConcurrentHandlingStarted()) {
  71. // Instead of postHandle and afterCompletion
  72. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  73. return;
  74. }
  75. // Clean up any resources used by a multipart request.
  76. if (multipartRequestParsed) {
  77. cleanupMultipart(processedRequest);
  78. }
  79. }
  80. }
  1. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  2. for (HandlerMapping hm : this.handlerMappings) {
  3. if (logger.isTraceEnabled()) {
  4. logger.trace(
  5. "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
  6. }
  7. HandlerExecutionChain handler = hm.getHandler(request);
  8. if (handler != null) {
  9. return handler;
  10. }
  11. }
  12. return null;
  13. }
  1. protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  2. for (HandlerAdapter ha : this.handlerAdapters) {
  3. if (logger.isTraceEnabled()) {
  4. logger.trace("Testing handler adapter [" + ha + "]");
  5. }
  6. if (ha.supports(handler)) {
  7. return ha;
  8. }
  9. }
  10. throw new ServletException("No adapter for handler [" + handler +
  11. "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
  12. }

HandlerAdapter的基本实现。SimpleControllerHandlerAddapter。

  1. public class SimpleControllerHandlerAdapter implements HandlerAdapter {
  2. @Override
  3. public boolean supports(Object handler) {
  4. return (handler instanceof Controller);
  5. }
  6. @Override
  7. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
  8. throws Exception {
  9. return ((Controller) handler).handleRequest(request, response);
  10. }
  11. @Override
  12. public long getLastModified(HttpServletRequest request, Object handler) {
  13. if (handler instanceof LastModified) {
  14. return ((LastModified) handler).getLastModified(request);
  15. }
  16. return -1L;
  17. }
  18. }

经过上面的处理。获取到Controller对象,開始调用Handler对象的HTTP响应动作。

运行完获取到视图,并将视图返回。

Spring MVC视图的呈现

1,DispatcherServlet视图呈现设计

在DIspatchServlet的doDispatch()方法中,获取到视图后调用了processDispatchResult()方法处理结果。视图的处理採用render方法。

  1. /**
  2. * Render the given ModelAndView.
  3. * <p>This is the last stage in handling a request. It may involve resolving the view by name.
  4. * @param mv the ModelAndView to render
  5. * @param request current HTTP servlet request
  6. * @param response current HTTP servlet response
  7. * @throws ServletException if view is missing or cannot be resolved
  8. * @throws Exception if there's a problem rendering the view
  9. */
  10. protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
  11. // Determine locale for request and apply it to the response.
  12. Locale locale = this.localeResolver.resolveLocale(request);
  13. response.setLocale(locale);
  14. View view;
  15. if (mv.isReference()) {
  16. // We need to resolve the view name.
  17. view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
  18. if (view == null) {
  19. throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
  20. "' in servlet with name '" + getServletName() + "'");
  21. }
  22. }
  23. else {
  24. // No need to lookup: the ModelAndView object contains the actual View object.
  25. view = mv.getView();
  26. if (view == null) {
  27. throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
  28. "View object in servlet with name '" + getServletName() + "'");
  29. }
  30. }
  31. // Delegate to the View object for rendering.
  32. if (logger.isDebugEnabled()) {
  33. logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
  34. }
  35. try {
  36. view.render(mv.getModelInternal(), request, response);
  37. }
  38. catch (Exception ex) {
  39. if (logger.isDebugEnabled()) {
  40. logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
  41. getServletName() + "'", ex);
  42. }
  43. throw ex;
  44. }
  45. }

resolveViewName通过对视图名称对象解析,获取视图。

  1. protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
  2. HttpServletRequest request) throws Exception {
  3. for (ViewResolver viewResolver : this.viewResolvers) {
  4. View view = viewResolver.resolveViewName(viewName, locale);
  5. if (view != null) {
  6. return view;
  7. }
  8. }
  9. return null;
  10. }

ViewResolver的resolveViewName方法,BeanNameViewresolver是其经常使用实现。

  1. @Override
  2. public View resolveViewName(String viewName, Locale locale) throws BeansException {
  3. ApplicationContext context = getApplicationContext();
  4. if (!context.containsBean(viewName)) {
  5. // Allow for ViewResolver chaining.
  6. return null;
  7. }
  8. return context.getBean(viewName, View.class);
  9. }

Spring为了实现视图的灵活性,方便应用使用各种视图。在View接口下实现了不同View对象。各View对象依据其详细的使用,氛围不同的视图。

2,JSP视图的实现

使用jsp作为视图,Sprinng採用JstlView来作为View对象,而其render方法继承自父类AbstractView。

  1. /**
  2. * Prepares the view given the specified model, merging it with static
  3. * attributes and a RequestContext attribute, if necessary.
  4. * Delegates to renderMergedOutputModel for the actual rendering.
  5. * @see #renderMergedOutputModel
  6. */
  7. @Override
  8. public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  9. if (logger.isTraceEnabled()) {
  10. logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
  11. " and static attributes " + this.staticAttributes);
  12. }
  13. Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
  14. prepareResponse(request, response);
  15. renderMergedOutputModel(mergedModel, request, response);
  16. }

createMergedOutputModel方法:

  1. /**
  2. * Creates a combined output Map (never {@code null}) that includes dynamic values and static attributes.
  3. * Dynamic values take precedence over static attributes.
  4. */
  5. protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
  6. HttpServletResponse response) {
  7. @SuppressWarnings("unchecked")
  8. Map<String, Object> pathVars = (this.exposePathVariables ?
  9. (Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
  10. // Consolidate static and dynamic model attributes.
  11. int size = this.staticAttributes.size();
  12. size += (model != null) ? model.size() : 0;
  13. size += (pathVars != null) ? pathVars.size() : 0;
  14. Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
  15. mergedModel.putAll(this.staticAttributes);
  16. if (pathVars != null) {
  17. mergedModel.putAll(pathVars);
  18. }
  19. if (model != null) {
  20. mergedModel.putAll(model);
  21. }
  22. // Expose RequestContext?
  23. if (this.requestContextAttribute != null) {
  24. mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
  25. }
  26. return mergedModel;
  27. }

renderMergedOutputModel,在InternalResourceView中完毕,InternalResourceView也属于JstlView的基类。

  1. /**
  2. * Render the internal resource given the specified model.
  3. * This includes setting the model as request attributes.
  4. */
  5. @Override
  6. protected void renderMergedOutputModel(
  7. Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  8. // Determine which request handle to expose to the RequestDispatcher.
  9. HttpServletRequest requestToExpose = getRequestToExpose(request);
  10. // Expose the model object as request attributes.
  11. exposeModelAsRequestAttributes(model, requestToExpose);
  12. // Expose helpers as request attributes, if any.
  13. exposeHelpers(requestToExpose);
  14. // Determine the path for the request dispatcher.
  15. String dispatcherPath = prepareForRendering(requestToExpose, response);
  16. // Obtain a RequestDispatcher for the target resource (typically a JSP).
  17. RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
  18. if (rd == null) {
  19. throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
  20. "]: Check that the corresponding file exists within your web application archive!");
  21. }
  22. // If already included or response already committed, perform include, else forward.
  23. if (useInclude(requestToExpose, response)) {
  24. response.setContentType(getContentType());
  25. if (logger.isDebugEnabled()) {
  26. logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
  27. }
  28. rd.include(requestToExpose, response);
  29. }
  30. else {
  31. // Note: The forwarded resource is supposed to determine the content type itself.
  32. if (logger.isDebugEnabled()) {
  33. logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
  34. }
  35. rd.forward(requestToExpose, response);
  36. }
  37. }

AbstractView的exposeModelAsRequestAttributes()方法:

  1. /**
  2. * Expose the model objects in the given map as request attributes.
  3. * Names will be taken from the model Map.
  4. * This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}.
  5. * @param model Map of model objects to expose
  6. * @param request current HTTP request
  7. */
  8. protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
  9. for (Map.Entry<String, Object> entry : model.entrySet()) {
  10. String modelName = entry.getKey();
  11. Object modelValue = entry.getValue();
  12. if (modelValue != null) {
  13. request.setAttribute(modelName, modelValue);
  14. if (logger.isDebugEnabled()) {
  15. logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
  16. "] to request in view with name '" + getBeanName() + "'");
  17. }
  18. }
  19. else {
  20. request.removeAttribute(modelName);
  21. if (logger.isDebugEnabled()) {
  22. logger.debug("Removed model object '" + modelName +
  23. "' from request in view with name '" + getBeanName() + "'");
  24. }
  25. }
  26. }
  27. }
  1. /**
  2. * Prepare for rendering, and determine the request dispatcher path
  3. * to forward to (or to include).
  4. * <p>This implementation simply returns the configured URL.
  5. * Subclasses can override this to determine a resource to render,
  6. * typically interpreting the URL in a different manner.
  7. * @param request current HTTP request
  8. * @param response current HTTP response
  9. * @return the request dispatcher path to use
  10. * @throws Exception if preparations failed
  11. * @see #getUrl()
  12. */
  13. protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
  14. throws Exception {
  15. String path = getUrl();
  16. if (this.preventDispatchLoop) {
  17. String uri = request.getRequestURI();
  18. if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
  19. throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
  20. "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
  21. "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
  22. }
  23. }
  24. return path;
  25. }

视图的解析过程大概就例如以下代码。

总而言之。Spring MVC的建立有下面几个过程:

-1。须要建立Controller和HTTP请求之间的映射关系。在HandlerMapping中封装HandlerExecutionChain对象完毕。而对Controller和HTTP请求关系的描写叙述是在Bean定义的描写叙述。并在IoC初始化时。通过初始化HandlerMapping来完毕,这些映射关系存储在handlerMap中。

-2,在MVC接收请求的时候,DispatcherServlet会依据详细的URL请求信息。在HandlerMapping中查询,从而得到HandlerExecutionChain。在HandlerExecutionChain中封装了Controller,这个请求的COntroller会完毕请求对应,以及生成须要的ModelAndView对象。

-3。得到ModelAndView对象后,DispatcherServlet将ModelAndView对象交给特定的视图对象。通过视图对象完毕数据呈现工作。

《Spring技术内幕》笔记-第四章 Spring MVC与web环境的更多相关文章

  1. Spring技术内幕笔记2--我懒不写了哈哈哈哈。

    目录 1.1 关于IOC容器设计的线路区别 1.1.1 BeanFactory 1.1.2 ApplicationContext 2.1 FileSystemXmlApplicationContext ...

  2. Spring Boot实战笔记(四)-- Spring常用配置(事件Application Event)

    一.事件(Application Event) Spring的事件为Bean和Bean之间的消息通信提供了支持.当一个Bean处理完一个任务之后,希望另一个Bean知道并能做相应的处理,这时我们就需要 ...

  3. Mysql技术内幕-笔记-第三章 查询处理

    第三章 查询处理 逻辑查询处理:(8) SELECT (9) DISTINCT <select_list> (1) FROM <left_table> (3) <join ...

  4. Javac编译原理 《深入分析java web 技术内幕》第四章

    javac编译的四个主要的流程: 词法分析器:将源码转换为Token流 将源代码划分成一个个Token(找出java语言中的关键字) 语法分析器:将Token流转化为语法树 将上述的一个个Token组 ...

  5. 《spring技术内幕》读书笔记(1)——什么是POJO模式

    今天在看<spring技术内幕>,第一章中多次提到了使用POJO来完成开发,就百度了一下,在此保留 1.     什么是POJO POJO的名称有多种,pure old java obje ...

  6. Spring技术内幕:SpringIOC原理学习总结

    前一段时候我把Spring技术内幕的关于IOC原理一章看完,感觉代码太多,不好掌握,我特意又各方搜集了一些关于IOC原理的资料,特加深一下印象,以便真正掌握IOC的原理. IOC的思想是:Spring ...

  7. Spring技术内幕:设计理念和整体架构概述(转)

    程序员都很崇拜技术大神,很大一部分是因为他们发现和解决问题的能力,特别是线上出现紧急问题时,总是能够快速定位和解决. 一方面,他们有深厚的技术基础,对应用的技术知其所以然,另一方面,在采坑的过程中不断 ...

  8. SQL Server技术内幕笔记合集

    SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...

  9. Android群英传笔记——第四章:ListView使用技巧

    Android群英传笔记--第四章:ListView使用技巧 最近也是比较迷茫,但是有一点点还是要坚持的,就是学习了,最近离职了,今天也是继续温习第四章ListView,也拖了其实也挺久的了,list ...

随机推荐

  1. vector ----- size函数注意事项

    vector 的size函数返回vector大小,返回值类型为size_type,Member type size_type is an unsigned integral type,即无符号整数: ...

  2. .NET 拼音汉字转化(全面)

    引言 这是一个.NET 用C#语言编写的  拼音转汉字类,考虑到有很多拼音转汉字,但是试用过发现大部分有很多生僻字都没有办法显示.在此分享一个支持绝大多数的较为全面的拼音汉字转化帮助类.不多说,代码附 ...

  3. 紫书 习题 11-16 UVa 1669(树形dp)

    想了很久, 以为是网络流最大流, 后来建模建不出来, 无奈. 后来看了 https://blog.csdn.net/hao_zong_yin/article/details/79441180 感觉思路 ...

  4. ActiveMQ maven

    http://outofmemory.cn/java/mq/apache-activemq-demo

  5. python __future__ 的几种特性

    今天看tensorflow的代码,看到python里面有这么几句: from __future__ import absolute_import from __future__ import divi ...

  6. nyoj33 蛇形填数

    蛇形填数 时间限制:3000 ms  |  内存限制:65535 KB 难度:3 描写叙述 在n*n方陈里填入1,2,...,n*n,要求填成蛇形.比如n=4时方陈为: 10 11 12 1 9 16 ...

  7. Android Camera子系统之Linux C应用开发人员View

    Android Camera HAL通过V4L2接口与内核Camera Driver交互.本文从Linux应用开发人员的角度审视Android Camera子系统. V4L2应用开发一般流程: 1. ...

  8. HDU-3577-Fast Arrangement-区间更新

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3577 好吧,我认为这道题有必要说一下题目意思:毕竟我刚開始是没有看太懂,原谅我这个英语渣渣...ORZ ...

  9. 删除heroku上的数据库记录

    部署本地项目到heroku上.在线上插入数据到数据库,本地代码再次更新到heroku,线上的数据记录还存在单是图片丢失.问题还没有解决: 本地代码和heroku代码怎样同步? heroku使用的pg和 ...

  10. bzoj1010: [HNOI2008]玩具装箱toy(DP+斜率优化)

    1010: [HNOI2008]玩具装箱toy 题目:传送门 题解: 很明显的一题动态规划... f[i]表示1~i的最小花费 那么方程也是显而易见的:f[i]=min(f[j]+(sum[i]-su ...