SpringMvc请求原理流程

springmvc是用户和服务沟通的桥梁,官网提供了springmvc的全面使用和解释:DispatcherServlet :: Spring Framework

流程

1.Tomcat启动

2.解析web.xml文件,根据servlet-class找到DispatcherServlet,根据init-param来获取spring的配置文件,spring的配置文件配置的主要内容就是参数的扫描路径(扫描Bean)和自定义Bean

XML 复制代码
   <servlet>
        <servlet-name>mvc_shine_aa</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:mvc.xml</param-value>
        </init-param>
        <!-- Servlet默认懒加载,改成饿汉式加载(可选) -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc_shine_aa</servlet-name>
        <!--<url-pattern>*.action</url-pattern>-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

mvc.xml文件

XML 复制代码
<!-- 注解扫描 -->
    <context:component-scan base-package="com.qf.web"/>

<bean class="com.yuyu.AService">
        <property name="id" value="2"></property>
    </bean>

3.创建DispatcherServlet实例,创建实例后会执行父类(FrameworkServlet)的父类(HttpServletBean)的init方法,HttpServletBean 在执行init()方法最后会执行initServletBean()方法,该方法在HttpServletBean是一个空方法,目的是让继承他的子类自己去完成接下来的初始化操作。

java 复制代码
	@Override
	public final void init() throws ServletException {

		// Set bean properties 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 properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
        // 核心代码,将剩余的初始化操作交给子类自己去实现
		initServletBean();
	}

接下来程序就走到了FrameworkServlet的initServletBean()方法,

java 复制代码
	@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 {
            // 核心代码
			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");
		}
	}

该方法的核心就是

this.webApplicationContext = initWebApplicationContext();

这一步就是创建一个spring容器,该方法首先会尝试获取父容器,然后判断之前是否有创建过webApplicationContext容器。

java 复制代码
	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
			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);
					}
					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.
			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;
	}

如果之前没有创建容器,程序会走到

if (wac == null) {

// 核心代码,创建本地容器实例

// No context instance is defined for this servlet -> create a local one

wac = createWebApplicationContext(rootContext);

}

通过createWebApplicationContext(rootContext),之后就会成功创建并初始化一个spring容器了

java 复制代码
	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);
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
        // 对容器进行初始化等操作 填充容器
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

之后spring容器创建完成,就可以给外部提供访问了。

springmvc的父子容器

在前文中发现,DispatcherServlet在生成容器之前会去找一个rootContext父容器,那么这个父容器是什么呢?为什么要找父容器呢?

现在如果在配置文件中声明了两个servlet,并且对应的spring配置文件配置了不同的bean,但是扫描的bean路径都相同的话,就会出现两个DispatcherServlet容器里会有一部分重复的bean

XML 复制代码
<!-- servlet1 -->
<servlet>
    <servlet-name>app1</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation1</param-name>
        <param-value>spring1.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>app1</servlet-name>
    <url-pattern>/app1/*</url-pattern>
</servlet-mapping>

<!-- servlet2 -->
<servlet>
    <servlet-name>app2</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation2</param-name>
        <param-value>spring2.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>app2</servlet-name>
    <url-pattern>/app2/*</url-pattern>
</servlet-mapping>

springmvc为了解决这个问题就创造了一个父容器的概念,在springmvc官方提供的配置文件中就有一个<listener/>属性 该属性就是定义的父容器,tomcat在读取web.xml文件时,首先读取的就是<listener/>和<context-param>来创建父容器。之后再创建子容器,创建完子容器后就会将父容器放入子容器中,这样就可以避免Bean的重复创建。

XML 复制代码
<web-app>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/app-context.xml</param-value>
	</context-param>

	<servlet>
		<servlet-name>app</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value></param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>app</servlet-name>
		<url-pattern>/app/*</url-pattern>
	</servlet-mapping>

</web-app>

springMvc的零配置

spring官方不止提供了通过配置文件来配置springMvc,还提供了配置类的形式来配置,这样就可以省略掉xml配置文件了。

java 复制代码
public class MyWebApplicationInitializer implements WebApplicationInitializer {

	@Override
	public void onStartup(ServletContext servletContext) {

		// Load Spring web application configuration
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        // AppConfig对应之前的spring.xml配置文件配置包扫描路径
		context.register(AppConfig.class);

		// Create and register the DispatcherServlet
		DispatcherServlet servlet = new DispatcherServlet(context);
		ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
		registration.setLoadOnStartup(1);
		registration.addMapping("/app/*");
	}
}
java 复制代码
@ComponentScan("com.yuyu")
public class AppConfig {
}

使用代码的方式来实现父子容器也非常简单

java 复制代码
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);
        

        // 创建子容器,将context 父容器注入子容器
        AnnotationConfigWebApplicationContext context2 = new AnnotationConfigWebApplicationContext();
        context2.register(AppConfig.class);
        context2.setParent(context);

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}
相关推荐
sz66cm23 分钟前
Python基础 -- 使用Python实现ssh终端并实现数据处理与统计功能
开发语言·python·ssh
码农小野1 小时前
基于SpringBoot的自习室预订系统
java·spring boot·后端
liangbm31 小时前
MATLAB系列02:MATLAB基础
开发语言·数据结构·笔记·matlab·教程·工程基础·高级绘图
lizi888882 小时前
单组件的编写
java
java_heartLake2 小时前
设计模式之代理模式
java·设计模式·代理模式
藓类少女3 小时前
正则表达式
数据库·python·mysql·正则表达式
change95133 小时前
PHP纯离线搭建(php 8.1.7)
开发语言·php
福鸦3 小时前
详解c++:new和delete
开发语言·c++
qq_172805593 小时前
Go Testify学习与使用
开发语言·golang·go
魏 无羡3 小时前
pgsql 分组查询方法
java·服务器·数据库