SpringMVC源码剖析-SpringMVC初始化,高级java开发工程师面试题

contextConfigLocation

classpath:applicationContext.xml

springmvc

org.springframework.web.servlet.DispatcherServlet

1

springmvc

/

org.springframework.web.context.ContextLoaderListener

  • contextConfigLocation:通过context-param 配置contextConfigLocation ,值就是Spring的配置文件名,ContextLoaderListener会去加载这个配置文件。

  • DispatcherServlet:前端控制器,控制SpringMVC的请求处理过程

  • ContextLoaderListener :上下文监听器,负责在WEB容器启动时,自动装配ApplicationContext信息。

ContextLoaderListener实现了ServletContextListener ,它可以监听到ServletContext的contextInitialized初始化和contextDestroyed销毁事件。ServletContext是Servlet上下文对象,伴随着程序启动而创建,程序销毁而销毁,全局有效。我们也可以自定义ServletContextListener 的实现类来做我们自己的一些全局初始化工作。

ContextLoaderListener主要就是通过监听ServletContext的Initialized初始化,然后创建WebApplicationContext容器工厂,并添加到到ServletContext对象中。下面是ServletContextListener的源码

public interface ServletContextListener extends EventListener {

public default void contextInitialized(ServletContextEvent sce) {

}

public default void contextDestroyed(ServletContextEvent sce) {

}

}

下面是ContextLoaderListerner源码

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

/**

  • Initialize the root web application context.

*/

@Override

public void contextInitialized(ServletContextEvent event) {

//初始化容器

initWebApplicationContext(event.getServletContext());

}

}

初始化容器,并设置到ServletContext ,见:ContextLoader#initWebApplicationContext

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {

…省略…

try {

if (this.context == null) {

//【重要】这里在创建WebApplicationContext

this.context = createWebApplicationContext(servletContext);

}

if (this.context instanceof ConfigurableWebApplicationContext) {

ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;

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 ->

// determine parent for root web application context, if any.

ApplicationContext parent = loadParentContext(servletContext);

cwac.setParent(parent);

}

configureAndRefreshWebApplicationContext(cwac, servletContext);

}

}

…省略…

//【重要】把WebApplicationContext放到servletContext

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

…省略…

return this.context;

}

catch (RuntimeException | Error ex) {

logger.error(“Context initialization failed”, ex);

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);

throw ex;

}

}

ContextLoaderListener 调用 ContextLoader#initWebApplicationContext 器初始化容器对象,这里主要做了两个事情

  • 调用createWebApplicationContext创建上下文对象

  • 把WebApplicationContext设置到ServletContext中

跟进一下createWebApplicationContext方法,看一下是如何创建上下文对象的

//【重要】加载ContextLoader.properties配置文件

private static final String DEFAULT_STRATEGIES_PATH = “ContextLoader.properties”;

private static final Properties defaultStrategies;

static {

// Load default strategy implementations from properties file.

// This is currently strictly internal and not meant to be customized

// by application developers.

try {

//【重要】加载配置文件ContextLoader.properties

ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);

defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);

}

catch (IOException ex) {

throw new IllegalStateException("Could not load ‘ContextLoader.properties’: " + ex.getMessage());

}

}

//【重要】 创建上下文对象

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {

//【重要】这里在拿class

Class<?> contextClass = determineContextClass(sc);

if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {

throw new ApplicationContextException(“Custom context class [” + contextClass.getName() +

“] is not of type [” + ConfigurableWebApplicationContext.class.getName() + “]”);

}

//使用BeanUtils.instantiateClass 根据class反射创建容器对象

return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

}

//找到容器对象的class

protected Class<?> determineContextClass(ServletContext servletContext) {

String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);

if (contextClassName != null) {

try {

return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());

}

catch (ClassNotFoundException ex) {

throw new ApplicationContextException(

“Failed to load custom context class [” + contextClassName + “]”, ex);

}

}

else {

//【重要】从ContextLoader.properties加载class名

contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

try {

return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());

}

catch (ClassNotFoundException ex) {

throw new ApplicationContextException(

“Failed to load default context class [” + contextClassName + “]”, ex);

}

}

}

该方法主要就是从ContextLoader.properties加载容器对象的class和创建对象的过程

  • 先是通过ContextLoader的static静态代码块加载ContextLoader.properties配置得到一个Properties

  • 然后从Properties 中得到ContextClassName(使用的是XmlWebApplicationContext)返回

  • 然后通过BeanUtils.instantiateClass(contextClass)实例化对象

  • 最后把容器对象在设置到ServletContext

ContextLoadeer.properties中使用的是XmlWebApplicationContext ,配置文件内容如下

在这里插入图片描述

DispatcherServlet 初始化


首先来看一下DispatcherServlet的继承体系

在这里插入图片描述

DispatcherServlet是一个Servlet,拥有Servlet的生命周期,在Servlet初始化阶段会调用init方法,见其父类org.springframework.web.servlet.HttpServletBean#init

public final void init() throws ServletException {

if (logger.isDebugEnabled()) {

logger.debug(“Initializing servlet '” + getServletName() + “'”);

}

// Set bean properties from init parameters.

//设置初始化参数,比如:context-param

try {

PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);

ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());

bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));

initBeanWrapper(bw);

//把servlet参数设置到BeanWrapper 对象中

bw.setPropertyValues(pvs, true);

}

catch (BeansException ex) {

logger.error(“Failed to set bean properties on servlet '” + getServletName() + “'”, ex);

throw ex;

}

// Let subclasses do whatever initialization they like.

//【重要】初始化ServletBean

initServletBean();

if (logger.isDebugEnabled()) {

logger.debug(“Servlet '” + getServletName() + “’ configured successfully”);

}

}

init方法中调用initServletBean初始化ServletBean,继续跟踪下去,代码来到org.springframework.web.servlet.FrameworkServlet#initServletBean

protected final void initServletBean() throws ServletException {

getServletContext().log(“Initializing Spring FrameworkServlet '” + getServletName() + “'”);

if (this.logger.isInfoEnabled()) {

this.logger.info(“FrameworkServlet '” + getServletName() + “': initialization started”);

}

long startTime = System.currentTimeMillis();

try {

//【重要】这里在初始化WebApplicationContext

this.webApplicationContext = initWebApplicationContext();

//空方法,让子类来实现

initFrameworkServlet();

}

catch (ServletException ex) {

this.logger.error(“Context initialization failed”, ex);

throw ex;

}

catch (RuntimeException ex) {

this.logger.error(“Context initialization failed”, ex);

throw ex;

}

if (this.logger.isInfoEnabled()) {

long elapsedTime = System.currentTimeMillis() - startTime;

this.logger.info(“FrameworkServlet '” + getServletName() + "': initialization completed in " +

elapsedTime + " ms");

}

}

FrameworkServlet#initServletBean方法中调用initWebApplicationContext初始化IOC容器对象,在ContextLoaderListener中已经创建了WebApplicationContext , 这里只是做初始化。见:org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext

protected WebApplicationContext initWebApplicationContext() {

//【重要】从ServletContext中获取WebApplicationContext,也就是通过ContextLoadListener创建的

// rootContext 根容器,是从ServletContext中拿到的容器对象

WebApplicationContext

WebApplicationContext rootContext =

WebApplicationContextUtils.getWebApplicationContext(getServletContext());

WebApplicationContext wac = null;

if (this.webApplicationContext != null) {

// A context instance was injected at construction time -> use it

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);

}

//【重要】刷新上下文,会走到ioc的 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) {

//[重要]如果wac 为空,到这里还没有WebApplicationContext就会走

//FrameworkServlet#createWebApplicationContext,创建一个XmlWebApplicationContext

//然后执行 wac.setParent(parent); 形成一个父子容器 ,rootContext是针对SpringMVC的容器,wac是针对Spring的容器

//最后会走容器的Refresh刷新方法刷新容器

// 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.

//[重要]初始化SpringMVC核心组件

onRefresh(wac);

}

if (this.publishContext) {

//把context作为ServletContext中的属性

// Publish the context as a servlet context attribute.

String attrName = getServletContextAttributeName();

getServletContext().setAttribute(attrName, wac);

if (this.logger.isDebugEnabled()) {

this.logger.debug(“Published WebApplicationContext of servlet '” + getServletName() +

“’ as ServletContext attribute with name [” + attrName + “]”);

}

}

return wac;

}

initWebApplicationContext方法中从ServletContext中拿到WebApplicationContext作为rootContext根容器,然后会走configureAndRefreshWebApplicationContext方法创建一个新的WebApplicationContext作为子容器形成父子容器,最终调用容器的AbstractApplicationContext#refreshrefresh()刷新容器,这个在Spring源码分析中已经有说道。

刷新完成容器后,会调用 onRefresh(wac)方法; 见org.springframework.web.servlet.DispatcherServlet#onRefresh

/**

  • This implementation calls {@link #initStrategies}.

*/

@Override

protected void onRefresh(ApplicationContext context) {

initStrategies(context);

}

/**

  • Initialize the strategy objects that this servlet uses.

  • May be overridden in subclasses in order to initialize further strategy objects.

*/

protected void initStrategies(ApplicationContext context) {

//多文件上传的组件

initMultipartResolver(context);

//初始化本地语言环境

initLocaleResolver(context);

//初始化模板处理器

initThemeResolver(context);

//初始化handlerMapping

initHandlerMappings(context);

//初始化参数适配器

initHandlerAdapters(context);

//初始化异常拦截器

initHandlerExceptionResolvers(context);

//初始化视图预处理器

initRequestToViewNameTranslator(context);

//初始化视图转换器

initViewResolvers(context);

//FlashMap 管理器

initFlashMapManager(context);

}

initStrategies中初始化了SpringMVC最核心的九大组件

initMultipartResolver(context);

初始化文件上传的组件 ,MultipartResolver作为文件上传解析组件,如果开发中需要使用MultipartResolver需要在xml中配置<bean id="multipartResolver" class="org.Springframework.web.multipart.commons.CommonsMultipartResolver" /> , 这样请求中的multipart属性就会被处理。

private void initMultipartResolver(ApplicationContext context) {

try {

//从容器中查找Bean,如果在xml配置了这里就能获取到

this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);

if (logger.isDebugEnabled()) {

logger.debug(“Using MultipartResolver [” + this.multipartResolver + “]”);

}

}

catch (NoSuchBeanDefinitionException ex) {

// Default is no multipart resolver.

this.multipartResolver = null;

if (logger.isDebugEnabled()) {

logger.debug(“Unable to locate MultipartResolver with name '” + MULTIPART_RESOLVER_BEAN_NAME +

“': no multipart request handling provided”);

}

}

}

initLocaleResolver(context);

初始化本地语言环境 ,Spring国际化支持

private void initLocaleResolver(ApplicationContext context) {

try {

//从容器中查找localeResolver

this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);

if (logger.isDebugEnabled()) {

logger.debug(“Using LocaleResolver [” + this.localeResolver + “]”);

}

}

catch (NoSuchBeanDefinitionException ex) {

// We need to use the default.

//使用一个默认的localeResolver

this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);

if (logger.isDebugEnabled()) {

logger.debug(“Unable to locate LocaleResolver with name '” + LOCALE_RESOLVER_BEAN_NAME +

“': using default [” + this.localeResolver + “]”);

}

}

}

initThemeResolver(context);

初始化模板处理器 ,web开发中可以通过 Theme 来控 网页风格,改善应用程序的视觉

效果。

private void initThemeResolver(ApplicationContext context) {

try {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

Java面试核心知识点笔记

其中囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。

蚂蚁金服(Java研发岗),26岁小伙斩获三面,收获Offer定级P6

Java中高级面试高频考点整理

蚂蚁金服(Java研发岗),26岁小伙斩获三面,收获Offer定级P6

蚂蚁金服(Java研发岗),26岁小伙斩获三面,收获Offer定级P6

最后分享Java进阶学习及面试必备的视频教学

蚂蚁金服(Java研发岗),26岁小伙斩获三面,收获Offer定级P6

术停滞不前!**

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-7erJHDiI-1711962096075)]
[外链图片转存中…(img-XCbsGEBT-1711962096075)]
[外链图片转存中…(img-DotebOMn-1711962096076)]
[外链图片转存中…(img-EwSOYsZC-1711962096076)]
[外链图片转存中…(img-AmGicSyp-1711962096076)]
[外链图片转存中…(img-AUoHUhbR-1711962096077)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-rLC7Tl6l-1711962096077)]

Java面试核心知识点笔记

其中囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。

[外链图片转存中…(img-l1t4LsyH-1711962096077)]

Java中高级面试高频考点整理

[外链图片转存中…(img-rDVsDxoY-1711962096077)]

[外链图片转存中…(img-uxvuZCmu-1711962096078)]

最后分享Java进阶学习及面试必备的视频教学

[外链图片转存中…(img-oFFMnaAA-1711962096078)]

  • 27
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值