springmvc工作流程(一)(源码解读)

在了解springmvc的工作流程之前,我们需要了解servlet以及servlet容器。

1. 为什么有servlet

互联网技术的发展,使得基于HTTP和HTML的web应用急速增长。用户页不满足于仅浏览静态页面,需要一些交互操作,获取一些动态结果。如果基于HTTP协议实现服务器端软件增强功能太过 复杂,所以需要一些扩展机制来实现用户想要的功能。早期使用的Web服务器扩展机制是CGI(Common Gateway Interface,公共网关接口)。使用这种方法,用户单击某个链接或输入网址来访问CGI程序,web服务器收到请求后,运行该CGI程序,对用户请 求进行处理,紧接着将处理结果并产生一个响应,该响应被返回给web服务器,web服务器对响应进行包装,以HTTP响应的方式返回给浏览器。

CGI程序在一定程度上解决了用户需求。不过还存在一些不足之处,如CGI程序编写困难,响应时间较长,以进程方式运行导致性能受限。于是1997年,sun公司推出了Servlet技术,作为java阵营的CGI解决方案。

2. 什么是servlet

Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态Web内容。

servlet接口定义的是一套处理网络请求的规范,所有实现servlet类的服务都需要实现规范定义的方法。定义如下:

public interface Servlet {
    // 初始化方法
    public void init(ServletConfig config) throws ServletException;
    // 获取配置信息
    public ServletConfig getServletConfig();
    // 服务方法,处理逻辑
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;
    // 返回servlet创建者信息,比如author等
    public String getServletInfo();
    // 销毁
    public void destroy();
}
public interface ServletConfig {
    // servlet名字
    public String getServletName();
    // servlet的上下文信息
    public ServletContext getServletContext();
    // 初始化参数
    public String getInitParameter(String name);
    /**
     * @return an <code>Enumeration</code> of <code>String</code> objects
     *         containing the names of the servlet's initialization parameters
     */
    public Enumeration<String> getInitParameterNames();
}

3. 怎么使用servlet

servlet作为一种规范,没有main方法,不能独立运行,它必须被部署到Servlet容器中,由容器来实例化和调用 Servlet的方法。

Servlet容器也叫做Servlet引擎,是Web服务器或应用程序服务器的一部分,用于在发送的请求和响应之上提供网络服务,解码基于 MIME的请求,格式化基于MIME的响应。最常见的servlet容器是tomcat。在tomcat中servlet的生命周期如图:

关于web容器,可参见:

4. springmvc和servlet有什么关系

springmvc是对servlet的封装,核心的DispatcherServlet最终继承自HttpServlet。同时,springmvc做为spring家族一员,将和web相关的处理器托付给springmvc容器,使得对象的管理和spring保持一致。

5. SpringMVC的启动过程

在介绍springmvc的启动过程之前,先了解一下几个基本概念。

Java规范定义ServletContextServlet规范中定义的,是所有Servlet的上下文    
tomcat定义StandardContextTomcat中定义的容器,一个Web应用对应一个StandardContext
ServletContextListener同属于Tomcatjar包,实现该接口的类可以监听到 StandardContext初始化完成事实和销毁完成事件
springApplicationContextSpring中定义的容器接口,MVC使用的实现类是WebApplicationContext
springmvcDispatcherServletSpring定义的Servlet,负责处理所有请求,并分配到达对Controller

通过web.xml配置启动springMVC的web项目

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext.xml</param-value>
    </context-param>

    <!-- 字符集 过滤器 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 容器启动监听 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Spring view分发器 -->
    <servlet>
        <servlet-name>servletDispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>servletDispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    

web容器启动的时候,会创建ServletContext,这个过程中,如果实现了ServletContextListener接口,会被调用contextInitialized方法。

public interface ServletContextListener extends EventListener {
    // context创建的时候执行
    public default void contextInitialized(ServletContextEvent sce) {
    }
    // context销毁的时候执行
    public default void contextDestroyed(ServletContextEvent sce) {
    }
}

在配置web.xml的时候,会配置监听器ContextLoaderListener, 它实现了 ServletContextListener 接口, 主要用来加载根容器。ContextLoaderListener类图结构有:

查看ContextLoaderListener的实现, 

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	public ContextLoaderListener() {
	}
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}

    // 调用父类的初始化方法
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}

}

执行初始化方法,会调用initWebApplicationContext方法,

    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!");
		}

		long startTime = System.currentTimeMillis();

		try {
			// 这个时候是没有容器的,所以创建一个容器。如果是java config,可以传入一个context
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
            // 将容器设置到servletcontext中,
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
                    // 这里会做refresh操作
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			return this.context;
		}
		catch (RuntimeException | Error ex) {
			throw ex;
		}
	}

因为web.xml配置,还没有WebApplicationContext,所以会创建

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
	Class<?> contextClass = determineContextClass(sc);
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
			"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
	}
	return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

 创建时,需要判断根Context的类别,web.xml是通过initparam方式传递进来

protected Class<?> determineContextClass(ServletContext servletContext) {
	// 会从web.xml中获取初始化参数contextClass
	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 {
		// 如果没有配置contextClass,从DefaultStrategies属性文件中获取
		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);
		}
	}
}

如果没有配置,则会采用默认的方式获取根容器

public class ContextLoader {
    private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
    private static final Properties defaultStrategies;
    
    // 加载默认的属性数据
    static {
		try {
			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());
		}
	}
}

 回到initWebApplicationContext方法中,this.context 并没有处于active状态,设置根的父容器为null,然后做容器的初始化过程configureAndRefreshWebApplicationContext。具体的方法实现有:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// 设置容器的Id
		String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
		if (idParam != null) {
			wac.setId(idParam);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(sc.getContextPath()));
		}
	}
	// 设置容器中的上下文信息
	wac.setServletContext(sc);
	// 这里获取了contextConfigLocation
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	if (configLocationParam != null) {
		wac.setConfigLocation(configLocationParam);
	}
	// 及早的获取到环境变量中的初始化参数
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}
	
	customizeContext(sc, wac);
	wac.refresh();
}

这里有一个小插曲,customizeContext方法中,会做ApplicationContextInitializer的加载,这一块内容在后面会有讲到,这里web.xml并没有涉及到,所以此方法会执行为空。接着做容器的刷新操作。

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			finishRefresh();
		}catch (BeansException ex) {
			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

			// Reset 'active' flag.
			cancelRefresh(ex);

			// Propagate exception to caller.
			throw ex;
		}finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}

到这里,contextLoaderListener的工作完成了,跟容器WebApplicationContext也创建完毕。

6. 通过ServletContainerInitializer方式启动springmvc

web容器启动的时候,为第三方组件提供了做一些初始化操作的功能,servlet规范中通过ServletContainerInitializer实现此功能。每个框架要使用ServletContainerInitializer就必须在对应的jar包的META-INF/services 目录创建一个名为javax.servlet.ServletContainerInitializer的文件,文件内容指定具体的ServletContainerInitializer实现类,当web容器启动时就会运行这个初始化器做一些组件内的初始化工作。

Tomcat容器的ServletContainerInitializer,主要由Context容器和ContextConfig监听器共同实现,ContextConfig监听器负责在容器启动时读取每个web应用的WEB-INF/lib目录下包含的jar包的META-INF/services/javax.servlet.ServletContainerInitializer,以及web根目录下的META-INF/services/javax.servlet.ServletContainerInitializer,通过反射完成这些ServletContainerInitializer的实例化,然后再设置到Context容器中,最后Context容器启动时就会分别调用每个ServletContainerInitializer的onStartup方法。

ServletContainerInitializer是一个接口,在javax.servlet-api中有定义

public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}

在spring-web 的jar包中,META-INF/services/javax.servlet.ServletContainerInitializer=SpringServletContainerInitializer。

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	@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);
		}
	}

}

Springweb中,WebApplicationInitializer的实现有:

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
	protected final Log logger = LogFactory.getLog(getClass());

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		registerContextLoaderListener(servletContext);
	}
	
	protected void registerContextLoaderListener(ServletContext servletContext) {
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			listener.setContextInitializers(getRootApplicationContextInitializers());
			servletContext.addListener(listener);
		}
		else {
		}
	}

	@Nullable
	protected abstract WebApplicationContext createRootApplicationContext();

	@Nullable
	protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
		return null;
	}
}
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
	public static final String DEFAULT_SERVLET_NAME = "dispatcher";
	
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		super.onStartup(servletContext);
		registerDispatcherServlet(servletContext);
	}
	// 这里会注册dispatcherServlet
	protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");

		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
		// 这里很重要,已经传入了跟容器信息
		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);
	}
	...
}

接着,再看一个Initializer类

public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer {
	// 定义了根容器类
	@Override
	@Nullable
	protected WebApplicationContext createRootApplicationContext() {
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
			context.register(configClasses);
			return context;
		}
		else {
			return null;
		}
	}

	// 定义了dispatcherServlet容器
	@Override
	protected WebApplicationContext createServletApplicationContext() {
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			context.register(configClasses);
		}
		return context;
	}

	@Nullable
	protected abstract Class<?>[] getRootConfigClasses();

	@Nullable
	protected abstract Class<?>[] getServletConfigClasses();
}

所以,可以自定义实现WebApplicationContextInitializer ,作为springmvc启动入口:

public class MyWebAppInitializer implements WebApplicationInitializer {
 
    @Override
    public void onStartup(ServletContext container) {
        // 创建根容器
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(RootConfig.class);

        // 创建 ContextLoaderListener
        // 用来管理 root WebApplicationContext 的生命周期:加载、初始化、销毁
        container.addListener(new ContextLoaderListener(rootContext));

        // 创建 dispatcher 持有的上下文容器
        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(WebConfig.class);

        // 注册、配置 dispatcher servlet
        ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    }
}

也可以继承AbstractAnnotationConfigDispatcherServletInitializer,

public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer  {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] {WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }
}
@Configuration
@EnableWebMvc
@ComponentScan("com.xm.web")
public class WebConfig extends WebMvcConfigurerAdapter {
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){
        configurer.enable();
    }
}
@Configuration
@ComponentScan(basePackages={"xm.web"},
excludeFilters={
    @Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)
})

public class RootConfig {}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值