springmvc 系列

0 使用篇

因为我引用上面的文章中的项目时,tomcat总是启动不起来,所以换了一个简单版的:一个简单的SSM框架实例(使用IDEA)

1 预备知识篇:

1.1 使用tomcat+spring时ApplicationContext的创建

实际上,使用tomcat+spring时,一共会创建两个ApplicationContext,分别是:

  1. ContextLoaderListener创建根ROOT WebApplicationContext,父节点为空。
  2. Serlvet创建springmvc相关的WebApplicationContext,其父节点为根ROOT WebApplicationContext。

详见Tomcat+Spring中有几个ApplicationContext?它们的关系是什么样的?

这里再说明一点:Serlvet创建普通WebApplicationContext和ServletContext不是一个东西,前者是spring的applicationContext,后者是java web里用于多个Servlet共享资源的context。

1.1.1 ContextLoaderListener

在web.xml中,会设置以下参数:

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

可以看到,在web.xml中定义了监听器ContextLoaderListener,因此tomcat会在启动时运行ContextLoaderListener。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	...

    public void contextInitialized(ServletContextEvent event) {
    	//初始化WebApplicationContext
        this.initWebApplicationContext(event.getServletContext());
    }
	...
}

启动tomcat时会去调用contextInitialized()方法,接着往下看:

    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    	//已经存在,则抛错
        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!");
        } else {
        	//...log相关,略
            long startTime = System.currentTimeMillis();

            try {
            	//1. 如果为空,则会创建(这里的为根Root)WebApplicationContext
                if (this.context == null) {
                    this.context = this.createWebApplicationContext(servletContext);
                }

                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                    if (!cwac.isActive()) {
                    	//2. 设置根Root WebApplicationContext的父为null(这里的parent为null)
                        if (cwac.getParent() == null) {
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
						//3. 配置WebApplicationContext并调用最重要的refresh()方法初始化spring容器。
                       this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
				//4. 将Root WebApplicationContext配置到servletContext里
                servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
                ClassLoader ccl = Thread.currentThread().getContextClassLoader();
				//...忽略一些代码

                return this.context;
            } 
            //...忽略catch
    }

从上面的代码可以看到,初始化WebApplicationContext主要有以下两步:

  1. 创建WebApplicationContext:createWebApplicationContext()
  2. 配置WebApplicationContext并调用最重要的refresh()方法初始化spring容器:configureAndRefreshWebApplicationContext()
1.1.1.1 ContextLoader.createWebApplicationContext(ServletContext sc)
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    	//1. 根据ServletContext 生成WebApplicationContext的Class对象
        Class<?> contextClass = this.determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
        	//2. 使用反射方法生成实例
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }

再看一看determineContextClass方法:

    protected Class<?> determineContextClass(ServletContext servletContext) {
    	//尝试从servletContext中获取参数contextClass,但默认是没有的
        String contextClassName = servletContext.getInitParameter("contextClass");
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            } catch (ClassNotFoundException var4) {
				...
            }
        } else {
        	//会走这,获取WebApplicationContext.class
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } catch (ClassNotFoundException var5) {
                throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
            }
        }
    }
1.1.1.2 ContextLoader.configureAndRefreshWebApplicationContext()
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        String configLocationParam;
        //1. wac中设置id、ServletContext属性
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            configLocationParam = sc.getInitParameter("contextId");
            if (configLocationParam != null) {
                wac.setId(configLocationParam);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        wac.setServletContext(sc);
        //2. 获取web.xml中的<context-param>标签中的contextConfigLocation属性的值,
        // 本文为:“classpath:applicationContext.xml”,并存入wac中。
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

		//3. 初始化wac中的environment的PropertySources,不展开讲
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
        }

        this.customizeContext(sc, wac);
        //4.  调用refresh()方法!
        wac.refresh();
    }
1.1.1.3 ContextLoaderListener流程图

在这里插入图片描述

1.1.2 FrameworkServlet创建WebApplicationContext

创建WebApplicationContext在FrameworkServlet的initWebApplicationContext()方法中

1.1.2.1 initWebApplicationContext()
	protected WebApplicationContext initWebApplicationContext() {
		//1, 获得根Root WebApplicationContext
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
		
		//2. 初始化时不会走这部分,略
		if (this.webApplicationContext != null) {
			...
		}
		//3. 寻找WebApplicationContext
		if (wac == null) {
			wac = findWebApplicationContext();
		}
		//4. 找不到的话就创建一个WebApplicationContext
		if (wac == null) {
			wac = createWebApplicationContext(rootContext);
		}

		//5. onfresh让子类DispatcherServlet来实现
		if (!this.refreshEventReceived) {
			onRefresh(wac);
		}

		//6, 获取到WebApplicationContext后,设置到当前servlet内。
		if (this.publishContext) {
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

其中第四步createWebApplicationContext()与1.1.1.1小节的虽然方法名一样,但是参数不同,具体实现有一些差别,而第五步onRefresh()方法则会在第二章中进行介绍。

1.1.2.2 FrameworkServlet.createWebApplicationContext(ApplicationContext parent)
	protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
		//1. 获取contextClass
		Class<?> contextClass = getContextClass();
		//...进行一些判断,略
		//2. 根据contextClass生成上下文WebApplicationContext
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		//3. 为WebApplicationContext设置属性,parent为根Root WebApplicationContext
		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		wac.setConfigLocation(getContextConfigLocation());
		
		//4. 再配置一些属性后,会调用refresh()方法初始化spring容器
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

可以看到,与1.1.1.1小节相比,前两步都是一样的,但在FrameworkServlet中,configureAndRefreshWebApplicationContext(wac)方法是内置在创建方法中的。

1.1.2.3 FrameworkServlet.configureAndRefreshWebApplicationContext(wac)
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		//1. 配置id,servletContext,ServletConfig等属性
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) { information
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {

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

		//2. 初始化wac中的environment的PropertySources,不展开讲
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
		
		//3. refresh方法!
		wac.refresh();
	}
1.1.2.3 FrameworkServlet流程图

在这里插入图片描述

1.2 springmvc总体流程图:

在这里插入图片描述

2 分发器DispatcherServlet

2.1 初始化

2.1.1 HttpServletBean的初始化

初始化的部分个人觉得上面的作者还是有所遗漏的,首先是HttpServletBean类的init()方法:

@Override
	public final void init() throws ServletException {
		//...

		try {
			//1. 从解析web.xml中DispatcherServlet的参数,例如contextConfigLocation
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			//2. 建立一个this(DispatcherServlet本身)的BeanWrapper,后面一系列操作都是为了将pvs作为属性注入到DispatcherServlet中
			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;
		}

		//3. 这里为空实现,会交给子类FrameworkServlet来完成
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

可以看到,HttpServletBean的init()方法的主要任务是解析web.xml中DispatcherServlet的参数,并将解析结果pv注入到this中。接着会去调用子类FrameworkServlet的initServletBean()来进行初始化。

2.1.2 FrameworkServlet的初始化
@Override
	protected final void initServletBean() throws ServletException {
		...日志
		long startTime = System.currentTimeMillis();

		try {
			this.webApplicationContext = initWebApplicationContext();
			//默认实现为空
			initFrameworkServlet();
		}
		catch...
		
		...日志
	}

initWebApplicationContext()方法在1.1.2.1小节已经介绍。

2.1.3 DispatcherServlet的初始化

但里面的onRefresh()方法由DispatcherServlet介绍:

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

	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		//初始化HandlerMapping
		initHandlerMappings(context);
		//初始化HandlerAdapter
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}
2.1.4 初始化流程图

在这里插入图片描述

2.2 处理请求

详见SpringMVC源码阅读:核心分发器DispatcherServlet的第3节,附上自己的总结的流程图:
在这里插入图片描述

3 HandlerMapping

传送门:SpringMVC工作原理之处理映射[HandlerMapping],这篇讲得很好
实际应用:SpringMVC RESTful 性能优化,(springboot也可以用到,我自己测试的时候,大概写了180个接口,rest是74ms,非rest是76ms,差别不大,不知道是哪出了问题)

补充一下:

  1. 文章2.1.1小节中,AbstractUrlHandlerMapping等类之所以会在生成时自动调用initApplicationContext()方法,原因在于可以父类ApplicationObjectSupport的setApplicationContext()方法会调用:
public abstract class ApplicationObjectSupport implements ApplicationContextAware {
	...
    public final void setApplicationContext(ApplicationContext context) throws BeansException {
        if (context == null && !this.isContextRequired()) {
			...
        } else if (this.applicationContext == null) {
            ...
            //注意这里
            this.initApplicationContext(context);
        }
        ...
  }
  1. detectHandlers()方法中,获得容器内所有的beanName,这里的容器是springmvc相关的WebApplicationContext,不包括根Root WebApplicationContext。

一图概括:
在这里插入图片描述
(注:上图引用自https://www.jianshu.com/p/f04816ee2495)
整个的流程图:
在这里插入图片描述

4 拦截器Interceptor

传送门:SpringMVC源码阅读:拦截器
该章内容较为简单,主要阐述了HandlerChain中的另一个组成部分拦截器。
再补充一下filter和interceptor的区别:过滤器(Filter)与拦截器(Interceptor )区别
在这里插入图片描述

5 HandlerAdapter

传送门:SpringMVC工作原理之适配器[HandlerAdapter]
SpringMVC工作原理之参数解析
在这里插入图片描述
(注:上图引用自https://www.jianshu.com/p/2bfd65bc9ce4)

整个的流程图:
在这里插入图片描述

6 视图解析器

预备知识:

  1. 使用@RestController或@ResponseBody的话,HandlerAdapter返回的ModelAndView为null
  2. Model指的是返回时的数据,而view指的是具体的视图是哪一个。
    例如下面的代码中,Model就是(“hello”->“欢迎学习springmvc!”)
    view是路径为"/WEB-INF/jsps/index.jsp"的JstlView
    @RequestMapping(value = "/test",method = RequestMethod.GET)
    public ModelAndView test(@RequestParam  String hello){
        ModelAndView mv = new ModelAndView();
        mv.addObject("hello","欢迎学习springmvc!");
        mv.setViewName("index");
        return mv;
    }

在这里插入图片描述在这里插入图片描述

7 <mvc:annotation-driven/>

使用mvc:annotation-driven/标签时,refresh()中解析BeanDefinition的时候,AnnotationDrivenBeanDefinitionParser会专门负责解析mvc:annotation-driven/这个标签。
解析的过程实际上就是注册HandlerMappings,HandlerAdapters,
HandlerExceptionResolvers 等BeanDefinition。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值