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