Spring webMvc环境准备问题

网上找到spring webmvc的资料都是管理请求流程,没有整应用环境初始化流程,这几天正在看Spring webmvc的源码,所以就想知道使用spring webmvc的时候, 整个环境是怎么初始化的,下边我会采用问答的方式解决我在这个过程中关注的一些问题

先来看看请求处理流程:
在这里插入图片描述

1.其实这个流程大家基本都知道,可是我想知道的是既然是Spring webmvc,那么applicationContext什么时候初始化的?

2.@RequestMapping标注的方法什么时候解析的?

3.为什么我们自己提供的WebApplicationInitializer能被发现?

4.环境中用到各种Resolver在什么时候被初始化的?

先来看第一个问题,applicationContext什么时候初始化的?

以tomcat容器为例,在启动容器的过程中,会调用GenericServlet类的init()方法,而在Springweb中HttpServletBean类继承额GenericServlet,所以实际上这里应该是一个HttpServletBean的实例,所以调用this.init()方法会调用到HttpServletBean的init()方法,在init()方法中调用了initServletBean()方法,而在HttpServletBean的initServletBean();正是整个spingweb环境初始化的入口

   //GenericServlet
	@Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

	//HttpServletBean
	@Override
	public final void init() throws ServletException {

		// Set bean beanfactorypostprocessor from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean beanfactorypostprocessor on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
		// 去执行子类的initServletBean,FrameworkServlet继承了HttpServletBean,这里会
		//去执行它的方法, 在这个方法里,回去刷新上下文 context, 然后初始化 web环境,包括各种转换器
		//所以这个方法容器初始的入口,至关重要
		//		protected void initStrategies(ApplicationContext context) {
		//			initMultipartResolver(context);
		//			initLocaleResolver(context);
		//			initThemeResolver(context);
		//			initHandlerMappings(context);
		//			initHandlerAdapters(context);
		//			initHandlerExceptionResolvers(context);
		//			initRequestToViewNameTranslator(context);
		//			initViewResolvers(context);
		//			initFlashMapManager(context);
		//		}
		initServletBean();
	}

然而initServletBean()方法也只是一个入口而已,正在实现初始化调用还在内部,但是需要注意的是,springweb中FrameworkServlet继承了HttpServletBean,所以上边说的httpServletBean实例也不太准确,这里说是FrameworkServlet实例其实还是不准确,因为在后边我们还会发现,DispathcerServlet继承了FrameworkServlet,没错,就是我们最熟悉的哪个DispatcherServlet,我们看看initServletBean()方法的实现

this.webApplicationContext = initWebApplicationContext();

initFrameworkServlet();

到了这里, 我们终于看到applicationContext的字样了,没错,正是从这里开始去进行容器初始化的,具体内部的实现,我们还需要看看initWebApplicationContext()方法的实现,这个方法我们稍后再看, 先说说initFrameworkServlet()方法的作用

这个方法我们进去看,会发现它是一个空方法,也就是什么都没有做,spring注释告诉我们,这个方法是一个拓展点,也就是我们可以自己实现一个Servelt类,在这个类中重写initFrameworkServlet()方法,这样在容器实例完毕后,我们还可以做一些自己需要的工作,比如说添加一些解析器什么的

/**
	 * Overridden method of {@link HttpServletBean}, invoked after any bean beanfactorypostprocessor
	 * have been set. Creates this servlet's WebApplicationContext.
	 * 初始化servlet bean
	 */
	@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("Initializing Servlet '" + getServletName() + "'");
		}
		long startTime = System.currentTimeMillis();

		try {
			//这个地方非常重要,springmvc就是通过这个地方对整个spring容器进行准备的
			//我们在WebApplicationInitializer接口的onStartUp方法中提供的WebApplication实例
			//只是一个刚来new出来的,在整个spring环境创建过程中,我们知道还有很多准备工作需要做
			//其中最重要的一个方法就是refresh方法
			//这个地方就是入口,通过在这里对整个环境进行初始化
			this.webApplicationContext = initWebApplicationContext();

			//这是一个拓展方法,也就是在整个servlet准备好之后,会调用这个方法
			//现在这个方法没有做任何事,而在DispatcherServlet中也没有对这个方法进行重写
			//所以它只是一个拓展点之一
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isDebugEnabled()) {
			String value = this.enableLoggingRequestDetails ?
					"shown which may lead to unsafe logging of potentially sensitive data" :
					"masked to prevent unsafe logging of potentially sensitive data";
			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
					"': request parameters and headers will be " + value);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}

this.webApplicationContext = initWebApplicationContext();这个方法是applicationContext初始化的入口,看到这个方法首先会判断this.webApplicationContext != null,这个webApplicationContext 在什么时候添加进去的呢?使用过0xml配置的同学一个个都知道, 我们在WebApplicationInitializer的onStartUp方法中设置了一个applicationContext,没错,这里的this.webApplicationContext就是在哪个地方设置的,但是到了这一步我们还没有看到有关refresh()方法的调用,别急, 我们接着深入,在configureAndRefreshWebApplicationContext(cwac);这个方法我们终于看到了关于refresh方法的调用,spring关于名字的定义还是很值得称赞的,这个方法一看就知道是关于配置和刷新webApplicationContext的方法

到了这里关于webApplicationContext初始化的分析就完毕了,refresh()的工作,那可以说是整个Spring容器的核心,这里不做分析

protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			//这个地方的this.webApplicationContext就是我们通过实现WebApplicationInitializer接口的onStartUp()方法
			//时添加进去的
			//DispatcherServlet servlet = new DispatcherServlet(ac);
			//这个地方我们携带了一个WebApplicationContext参数
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					}
					//进行容器配置和刷新,会从这里去执行容器的refresh方法, 我们都知道
					//在spring中refresh()是一个至关重要的方法
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

		/**
		 * 这个地方也很重要
		 */
		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			//实际上是去执行dispatcherServlet的初始化方法
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}



	/**
	 * 配置和刷新web容器
	 * @param wac
	 */
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
			}
		}

		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);
		//applicationContext 初始化后置处理器调用
		//首先去查找所有的初始化后置处理器类 ApplicationContextInitializer
		//然后调用该类的initialize()方法,ApplicationContextInitializer是springweb提供的一个拓展点
		//这里我们可以通过这个initialize()方法对上下文进行更改
		//如果这个方法调用更晚一些,那么context调用了refresh()方法后,在更新context某系更新功能将不会生效
		applyInitializers(wac);

		//这里执行context的刷新方法, 这个与我们单纯分析spring源码的地方进行串联起来了
		//如果使用AnnotationConfigWebApplicationContext, 这个类继承了AbstractApplicationContext
		//它自己并没有实现refresh方法,这里调用直接使用这个类继承了AbstractApplicationContext的refresh方法
		wac.refresh();
	}

环境中用到各种Resolver在什么时候被初始化的?

从上边关于处理请求的图中,我们知道在整个web容器中用到很多转换器,那么DispathcerServlet是在什么时候对他们进行初始化的, 其实刚才我们已经找到了他了,没错,就是在webapplicationContext初始化完毕后进行初始化的,看下边这段代码,就是在这里完成的


		/**
		 * 这个地方也很重要
		 */
		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			//实际上是去执行dispatcherServlet的初始化方法
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

//-----------------------------------------------------------------
	/**
	 * This implementation calls {@link #initStrategies}.
	 */
	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}
//-----------------------------------------------------------------
	
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

问题:为什么我们自己提供的WebApplicationInitializer能被发现?

刚才我们提到管用DispatcherServlet中有一个关于this.webApplicationContext != null的判断,而这个applicationContext是在我们自己提供的WebApplicationInitializer中提供的,那么我们自己提供的这个类为什么会被发现呢,这就要说到 Servlet SPI协议了

什么是SPI协议,就是在容器的更目录下提供MEAT-INF/services/javax.servlet.javax.servlet.ServletContainerInitializer文件,然后文件中提供一个类全路径,那么在容器启动时候就会去加载该类,而spring web真是实现了这一协议,实现这一协议的容器在启动时候会去加载这个类

我们自己提供的WebApplicationInitializer并没有在文件内部,为什么能被加载执行呢?不急接着看。

org.springframework.web.SpringServletContainerInitializer

上边是Springweb在文件中提供的全路径,我们看看springweb关于该类的实现,我们看到@HandlesTypes(WebApplicationInitializer.class)这个注解,这个注解很关键,只要实现WebApplicationInitializer这个接口的类都会被发现。而且我们自己提供的必须要实现WebApplicationInitializer这个接口。

@HandlesTypes(@HandlesTypes(WebApplicationInitializer.class).class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	/**
	 * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
	 * implementations present on the application classpath.
	 * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
	 * Servlet 3.0+ containers will automatically scan the classpath for implementations
	 * of Spring's {@code WebApplicationInitializer} interface and provide the set of all
	 * such types to the {@code webAppInitializerClasses} parameter of this method.
	 * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
	 * this method is effectively a no-op. An INFO-level log message will be issued notifying
	 * the user that the {@code ServletContainerInitializer} has indeed been invoked but that
	 * no {@code WebApplicationInitializer} implementations were found.
	 * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
	 * they will be instantiated (and <em>sorted</em> if the @{@link
	 * org.springframework.core.annotation.Order @Order} annotation is present or
	 * the {@link org.springframework.core.Ordered Ordered} interface has been
	 * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
	 * method will be invoked on each instance, delegating the {@code ServletContext} such
	 * that each instance may register and configure servlets such as Spring's
	 * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
	 * or any other Servlet API componentry such as filters.
	 * @param webAppInitializerClasses all implementations of
	 * {@link WebApplicationInitializer} found on the application classpath
	 * @param servletContext the servlet context to be initialized
	 * @see WebApplicationInitializer#onStartup(ServletContext)
	 * @see AnnotationAwareOrderComparator
	 */
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		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...
				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);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}
}

问题:@RequestMapping标注的方法什么时候解析的

我们知道请求映射关系使用hanlerMapping进行处理的,每个请求对应应该调用我们的哪个方法都是由handlerMapping进行处理的,spring默认提供到了三个handlerMapping,分别是

  • RequestMappingHandlerMapping
  • BeanNameUrlHandlerMapping
  • SimpleUrlHandlerMapping

这里我们只分析RequestMappingHandlerMapping,其他的后续在分析

其实在这里问题又来了,spring怎么知道有这三个handlerMapping,这就要说到@EnableWebMvc注解,我们看到这个注解引入DelegatingWebMvcConfiguration类,这个类非常关键,我们看看他的父类WebMvcConfigurationSupport,这就知道了RequestMappingHandlerMapping是怎么来的了

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

//WebMvcConfigurationSupport 静态代码块
	static {
		ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
		romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
		jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
		jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
				ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
		jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
		jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
		jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
		gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
		jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
	}

@Bean
	public RequestMappingHandlerMapping requestMappingHandlerMapping(
			ContentNegotiationManager mvcContentNegotiationManager,
			FormattingConversionService mvcConversionService,
			ResourceUrlProvider mvcResourceUrlProvider) {
		RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
		mapping.setOrder(0);
		mapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
		mapping.setContentNegotiationManager(mvcContentNegotiationManager);
		mapping.setCorsConfigurations(getCorsConfigurations());

		PathMatchConfigurer configurer = getPathMatchConfigurer();

		Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
		if (useSuffixPatternMatch != null) {
			mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
		}
		Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
		if (useRegisteredSuffixPatternMatch != null) {
			mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
		}
		Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
		if (useTrailingSlashMatch != null) {
			mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
		}

		UrlPathHelper pathHelper = configurer.getUrlPathHelper();
		if (pathHelper != null) {
			mapping.setUrlPathHelper(pathHelper);
		}
		PathMatcher pathMatcher = configurer.getPathMatcher();
		if (pathMatcher != null) {
			mapping.setPathMatcher(pathMatcher);
		}
		Map<String, Predicate<Class<?>>> pathPrefixes = configurer.getPathPrefixes();
		if (pathPrefixes != null) {
			mapping.setPathPrefixes(pathPrefixes);
		}

		return mapping;
	}

这里我们知道了RequestMappingHandlerMapping这个类的由来,那么回归正题,这和@RequestMapping有什么关系,不急,我们接着看

RequestMappingHandlerMapping 实现了 InitializingBean方法,而关于@RequestMapping的处理也正是通过这个接口的afterPropertiesSet()进行处理的

这个过程非常复杂,最终我们找到如下两个方法:这个两个方法才是正在进行判断处理的, 当然, 这里边还有很多其他判断,这里就不说了,感兴趣可以自己跟一下源码

	/**
	 * Uses method and type-level @{@link RequestMapping} annotations to create
	 * the RequestMappingInfo.
	 * 检测 handler method 方法的实际操作,这个地方是给出已经检测出的方法(method),然后将转换为
	 * RequestMappingInfo
	 * @return the created RequestMappingInfo, or {@code null} if the method
	 * does not have a {@code @RequestMapping} annotation.
	 * @see #getCustomMethodCondition(Method)
	 * @see #getCustomTypeCondition(Class)
	 */
	@Override
	@Nullable
	protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
		//判断传入的方法是否标注了@RequestMapping注解
		//如果标注了这个注解, 那么这个方法将会被封装成RequestMappingInfo
		//否则这里放回的是null, 并且这里的RequestMappingInfo就实现RequestCondition接口
		RequestMappingInfo info = createRequestMappingInfo(method);
		if (info != null) {
			RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
			if (typeInfo != null) {
				info = typeInfo.combine(info);
			}
			String prefix = getPathPrefix(handlerType);
			if (prefix != null) {
				info = RequestMappingInfo.paths(prefix).build().combine(info);
			}
		}
		return info;
	}



		@Nullable
	private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
		RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);

		//做条件处理,但是这里条件返回都是null
		//CompositeRequestCondition 可以提供多条件组合判断
		//这里的条件应用在类上,方法级别没有
		RequestCondition<?> condition = (element instanceof Class ?
				getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
		return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
	}

其实还有问题, 我们知道这两个方法是最终处理的, 但是spring又是怎么在初始化的时候调到这两个方法的, 刚才说了是通过InitializingBean接口的afterPropertiesSet()方法实现的?

读过Spring refresh()方法的应该知道, 在bean实例化后,会执行属性装配,然后就会执行invokeInitMethods,也就是这个方法去调用了InitialingBean的afterPropertiesSet()方法,

这也就说清楚了@RequestMapping处理的地方,这个其实与Bean的生命周期回调有关,关于生命周期回调可以查看【

	try {
			//这里执行实现InitializingBean接口的方法
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}


protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
			throws Throwable {

		//判断bean是否实现了InitializingBean
		boolean isInitializingBean = (bean instanceof InitializingBean);
		if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
			if (logger.isTraceEnabled()) {
				logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
			}
			if (System.getSecurityManager() != null) {
				try {
					AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
						//具体的方法调用
						((InitializingBean) bean).afterPropertiesSet();
						return null;
					}, getAccessControlContext());
				}
				catch (PrivilegedActionException pae) {
					throw pae.getException();
				}
			}
			else {
				//具体执行InitializingBean的方法调用afterPropertiesSet
				//需要注意的是,在查找Spring webmvc @requestMapping方法匹配的时候, 找了很久
				//最后发现请求到DispatcherServlet,到doDispatch(),然后里边的请求交给
				//HandlerMapping处理, 然后在spring内部有RequestMappingHandlerMapping这样一个类
				//这个类实现了InitilizingBean,所以在这里会执行RequestMappingHandlerMapping的afterPropertiesSet()方法
				//还有一点没有讲到, 在RequestMappingHandlerMapping中,映射方法数据存放在mappingRegistry 集合中,
				//所以在这个方法里边会去查找所有的@RequestMapping方法
				((InitializingBean) bean).afterPropertiesSet();
			}
		}
发布了113 篇原创文章 · 获赞 62 · 访问量 19万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章