網上找到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();
}
}