返回

Spring5 MVC——启动过程,执行refresh方法的入口

发布时间:2023-01-10 21:05:01 289
# 前端# html# java# 容器# 扫描

SpringMVC的核心流程

  • 建立请求和Controller方法的映射集合的流程。

  • 根据请求查找对应的Controller方法的流程。

  • 请求参数绑定到方法形参,执行方法处理请求,返回结果进行视图渲染的流程。

SpringMVC的处理流程图

SpringMVC的核心组件和请求处理流程:

  • 1、浏览器发出一个url请求,通过Nginx的反向代理,web容器如Tomcat就会接收到这个请求,并委派给其内置的线程池去调用Servlet进行处理,在SpringMVC中这个Servlet的名字就叫做DispatcherServlet。

  • 2、DispatcherServlet在接收到请求之后,将请求派发给处理器映射器HanlerMapping实现类,HanlerMapping会去解析请求向DispatcherServlet返回Handler,HandlerMapping会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象,多个HandlerInterceptor拦截器对象),并找到和请求相对应的Controller和对应的方法实例。

  • 3、随后DispatcherServlet调用处理器适配器去执行Handler,处理器适配器HandlerAdapter将会根据适配的结果去执行Handler调用对应的方法进行处理,Handler执行完成给适配器返回ModelAndView。

  • 4、处理器适配器向DispatcherServlet返回ModelAndView (ModelAndView是springmvc框架的一个底层对象,包括 Model和view)

  • 5、随后DispatcherServlet会将其委派给视图解析器ViewReslover,去完成对视图的解析(根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可。

  • 6、视图解析器向DispatcherServlet返回视图对象View,DispatcherServlet进行视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充到request域)。

  • 7、视图渲染完成之后,DispatcherServlet就会将视图对象放入到响应流中回发给浏览器,浏览器就会将渲染后的视图展现给用户。

SpringMVC的工作机制

在容器初始化时会建立所有url和controller的对应关系,保存到Map中。tomcat启动时会通知spring初始化容器(加载bean的定义信息和初始化所有单例bean),然后springmvc会遍历容器中的bean,获取每一个controller中的所有方法访问的url,然后将url和Controller保存到一个Map中。

 

这样就可以根据request快速定位到Controller,因为最终处理request的是Controller中的方法,Map中只保留了url和Controller中的对应关系,所以要根据request的url进一步确认Controller中的method,这一步工作的原理就是拼接Controller的url(Controller上@RequestMapping的值)和方法的url(method上@RequestMapping的值),与request的url进行匹配,找到匹配的那个方法。

 

确定处理请求的method后,接下来的任务就是参数绑定,把request中参数绑定到方法的形式参数上,这一步是整个请求处理过程中最复杂的一个步骤。SpringMVC提供了两种request参数与方法形参的绑定方法: * ① 通过注解进行绑定 @RequestParam * ② 通过参数名称进行绑定

使用注解进行绑定,我们只要在方法参数前面声明@RequestParam("a"),就可以将request中参数a的值绑定到方法的该参数上。使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法。SpringMVC解决这个问题的方法是用asm框架读取字节码文件,来获取方法的参数名称。asm框架是一个字节码操作框架,关于asm更多介绍可以参考它的官网。个人建议,使用注解来完成参数绑定,这样就可以省去asm框架的读取字节码的操作。

注解方式初始化分析

SpringServletContainerInitializer#onStartup

  • Tomcat容器启动首先会调用SpringServletContainerInitializer类中的onStartup方法
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	@Override
	public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List initializers = new LinkedList<>();

		// webAppInitializerClasses 就是servlet3.0规范中为我们收集的 WebApplicationInitializer 接口的实现类的class
		// 从webAppInitializerClasses中筛选并实例化出合格的相应的类
		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");
		// 这行代码说明我们在实现WebApplicationInitializer可以通过继承Ordered, PriorityOrdered来自定义执行顺序
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			// 迭代每个initializer实现的方法
			initializer.onStartup(servletContext);
		}
	}
}

WebApplicationInitializer#onStartup

  • 随后会调用WebApplicationInitializer实现类中的onStartup方法
  • 在AbstractDispatcherServletInitializer中的onStartup方法中调用registerDispatcherServlet方法去读入相关的配置,去创建Spring web ioc 容器和DispatcherServlet实例等操作。
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		super.onStartup(servletContext);
		registerDispatcherServlet(servletContext);
	}

	protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");
		//创建Spring web ioc 容器
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
		//创建DispatcherServlet实例
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		if (registration == null) {
			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
					"Check if there is another servlet registered under the same name.");
		}

		registration.setLoadOnStartup(1);
		//路径映射
		registration.addMapping(getServletMappings());
		registration.setAsyncSupported(isAsyncSupported());
		//请求拦截器,可以在此处控制编码
		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}

		customizeRegistration(registration);
	}
}

ContextLoaderListener#contextInitialized

  • 之后会调用ContextLoaderListener里面的contextInitialized方法
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
}

这意味着注解配置方式的初始化会先于web.xml配置方式的初始化执行,并且在执行到这里的时候,Spring里面相关的Root容器、子容器和DispatcherServlet实例都已经被创建好了。

ContextLoader#initWebApplicationContext

  • 随后调用ContextLoader类中的initWebApplicationContext方法。
public class ContextLoader {

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		//从ServletContext中查找,是否存在以
		// WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为Key的值
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}

		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			// 创建web上下文,默认是XmlWebApplicationContext
			if (this.context == null) {
				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);
				}
			}
			//将ApplicationContext放入ServletContext中,
			// 其key为
  • 尝试从SpringMVC子容器中获取Root容器,由于在AbstractDispatcherServletInitializer类中的registerDispatcherServlet方法中只是创建了父子容器,并没有做关联。

  • 在该方法中的configureAndRefreshWebApplicationContext方法会调用refresh()方法对Root容器(Root WebApplicationContext)和Servlet容器(dispatcher-servlet mvc子容器)进行刷新。

  • 在dispatcher-servlet子容器调用refresh()方法刷新之前会调用HttpServletBean的init方法和FrameworkServlet类的initServletBean、initWebApplicationContext、configureAndRefreshWebApplicationContext等方法。

  • 而SpringMVC里面只有一个Servlet即DispatcherServlet,因此推测DispatcherServlet和HttpServletBean、FrameworkServlet有着密切相关的关系。

DispatcherServlet继承关系图

  • Servlet初始化的时候会调用init方法,所以这里的dispatcher-servlet子容器的刷新是在DispatcherServlet初始化的时候进行的,即调用的是HttpServletBean的init方法和FrameworkServlet类的initServletBean、initWebApplicationContext、configureAndRefreshWebApplicationContext等方法。

HttpServlet

HttpServlet是Servlet规范中的核心类,实现Servlet接口,继承此类用于处理用户请求

HttpServletBean

HttpServletBean主要配置servlet中初始化参数。继承HttpServlet,并实现无参的init()方法,用于设置在web.xml中配置的contextConfigLocation属性,此属性指定Spring MVC的配置文件地址,默认为WEB-INF/[servlet-name]-servlet.xml。

HttpServletBean#init

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {

	@Override
	public final void init() throws ServletException {

		// Set bean properties from init parameters.
		//获取在web.xml配置的初始化参数,并将其设置到DispatcherServlet中
		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 properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
		//调用子类FrameworkServlet进行初始化
		//模板方法,此方法在HttpServletBean本身是空的,但是因为调用方法的对象是DispatcherServlet
		//所以优先在DispatcherServlet找,找不到再去父类找,最后在FrameworkServlet找
		initServletBean();
	}
}

HttpServletBean的作用:

  • 获取web.xml的中配置DispatcherServlet的初始化参数,存放到一个参数容器ServletConfigPropertyValues中。
  • 根据传进来的this创建BeanWrapper,本质上它就是DispatcherServlet
  • 通过bw.setPropertyValues(pvs, true),把参数设置到bw(即DispatcherServlet)里面去。
  • 最后调用子类的initServletBean()。

FrameworkServlet

FrameworkServlet主要创建WebApplicationContext上下文,重写了HttpServletBean的initServletBean()方法。

initServletBean

  • 该方法只有两句关键代码,其作用一个是调用initWebApplicationContext()方法初始化WebApplicationContext上下文,另一个是调用子类方法initFrameworkServlet()方法。
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

	@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 {
			// 初始化WebApplicationContext,并调用子类(DispatcherServlet)的onRefresh(wac)方法
			this.webApplicationContext = initWebApplicationContext();
			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");
		}
	}
}

initWebApplicationContext

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

	protected WebApplicationContext initWebApplicationContext() {
		// 获取root WebApplicationContext,即web.xml中配置的listener(ContextLoaderListener)
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		// 判断容器是否由编程式传入(即是否已经存在了容器实例),存在的话直接赋值给wac,给springMVC容器设置父容器
		// 最后调用刷新函数configureAndRefreshWebApplicationContext(wac),作用是把Spring MVC配置文件的配置信息加载到容器中去
		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			// context上下文在构造是注入
			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
					// context没有被refreshed,提供一些诸如设置父context、设置应用context id等服务
					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);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		// 在ServletContext中寻找是否有Spring MVC容器,初次运行是没有的,Spring MVC初始化完毕ServletContext就有了Spring MVC容器
		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();
		}
		// 当wac既没有没被编程式注册到容器中的,也没在ServletContext找得到,此时就要新建一个Spring MVC容器
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			// 如果没有WebApplicationContext则创建
			wac = createWebApplicationContext(rootContext);
		}

		// 到这里Spring MVC容器已经创建完毕,接着真正调用DispatcherServlet的初始化方法onRefresh(wac)
		// 此处仍是模板模式的应用
		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.
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}
		// 将Spring MVC容器存放到ServletContext中去,方便下次取出来
		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}
}

createWebApplicationContext

如果没有WebApplicationContext则创建

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

	protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
		return createWebApplicationContext((ApplicationContext) parent);
	}

	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class contextClass = getContextClass();
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}

		// 实例化容器
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
		// 设置容器环境
		wac.setEnvironment(getEnvironment());
		// 设置父容器
		wac.setParent(parent);
		// 加载Spring MVC的配置信息,如:bean注入、注解、扫描等等
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		// 刷新容器,根据Spring MVC配置文件完成初始化操作
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}
}

configureAndRefreshWebApplicationContext

  • 刷新容器,根据Spring MVC配置文件完成初始化操作
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

	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...
				// 生成默认 id
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
			}
		}
		// 设置 servletContext 属性
		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
		// 初始化属性源, 确保 servlet属性源到位并能够在任何 refresh 之前的后期处理和初始化中使用
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
		//刷新容器
		wac.refresh();
	}
}

总结FrameworkServlet的作用:

  • 创建Spring MVC的容器,根据配置文件实例化里面各种bean,并将之与spring的容器进行关联。
  • 把创建出来的Spring MVC容器存放到ServletContext中。
  • 通过模板方法模式调用子类DispatcherServlet的onRefresh()方法。

DispatcherServlet

DispatcherServlet是Spring MVC核心,它是J2EE规范前端控制器的实现,负责拦截用户请求,并解析请求进行转发。

public class DispatcherServlet extends FrameworkServlet {

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	protected void initStrategies(ApplicationContext context) {
		//初始化文件上传处理器
		initMultipartResolver(context);
		
		//初始化国际化配置
		initLocaleResolver(context);

		//初始化主题处理器
		initThemeResolver(context);

		//初始化HanlderMapping
		initHandlerMappings(context);

		//初始化HandlerAdapter
		//HandlerAdapter用来调用具体的方法对用户发来的请求来进行处理
		initHandlerAdapters(context);

		//初始化异常处理器,
		// HandlerExceptionResolver是用来对请求处理过程中产生的异常进行处理
		initHandlerExceptionResolvers(context);

		//RequestToViewNameTranslator用于在视图路径为空的时候,自动解析请求
		//去获取ViewName
		initRequestToViewNameTranslator(context);

		//初始化视图处理器
		//ViewResolvers将逻辑视图转成view对象
		initViewResolvers(context);

		//FlashMapManager用于存储、获取以及管理FlashMap实例
		initFlashMapManager(context);
	}
}

总结:

  • 容器启动时,加载web.xml部署描述文件,扫描到并找到DispatcherServlet核心控制器。

  • 调用HttpServletBean的init()方法,把DispatcherServlet初始化参数设置到DispatcherServlet中,并调用子类FrameworkServlet的initServletBean()方法。

  • FrameworkServlet的initServletBean()创建Spring MVC容器并初始化,并且和Spring父容器进行关联,使得Spring MVC容器能访问Spring容器中定义的bean,之后调用子类DispatcherServlet的onRefresh()方法。

  • DispatcherServlet的onRefresh(ApplicationContext context)对DispatcherServlet的策略组件进行初始化。

参考: https://www.cnblogs.com/yoci/p/10642493.html

https://www.cnblogs.com/fangjian0423/p/springMVC-directory-summary.html

https://segmentfault.com/a/1190000014674239

https://blog.csdn.net/win7system/article/details/90674757

https://www.cnblogs.com/liwangcai/p/10743943.html

https://www.cnblogs.com/myitnews/p/11565941.html

特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报
评论区(0)
按点赞数排序
用户头像
精选文章
thumb 中国研究员首次曝光美国国安局顶级后门—“方程式组织”
thumb 俄乌线上战争,网络攻击弥漫着数字硝烟
thumb 从网络安全角度了解俄罗斯入侵乌克兰的相关事件时间线
下一篇
Spring5 MVC——初始化入口分析 2023-01-10 20:52:21