Spring源码学习(5)- springmvc解析

介绍

SpringMvc是基于servlet规范来完成的一个请求详情模块,也是spring中比较大的一个模块。springmvc使用有两种方式,一种是配置文件的形式;另一个就是注解的形式,这种方法采用的是约定大于配置的方式。完成这个过程,springmvc要解决两个问题。

1.取代web.xml配置

springmvc借助servlet中的一个规范,来完成这个事情

当servlet容器启动的时候,会根据spi规范,在家META-INF/services文件夹里面的javax.servlet.ServletContainerInitializer文件,这个文件会实现javax.servlet.ServletContainerInitializer接口。

这个类在启动时,被servlet容器实例化,然后调用 onStartup 方法,并且servlet容器会收集实现了类上 @HandlesTypes 注解里面的接口的类,并作为入参(Set)传入到onStartup方法中,就可以拿到对应的class进行操作了,接下来看到方法中的 onStartup 方法

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

}

onStartup ():AbstractDispatcherServletInitializer这个类中的
代码分为两部分,一部分是调用父类的onStartup方法,另一部分就是registerDispatcherServlet

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

super.onStartup()方法

这个方法作用是创建spring容器的上下文对象,存入到ContextLoaderListener对象中,最后存入servletContext中

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
   
	registerContextLoaderListener(servletContext);
}

/**
 * Register a {@link ContextLoaderListener} against the given servlet context. The
 * {@code ContextLoaderListener} is initialized with the application context returned
 * from the {@link #createRootApplicationContext()} template method.
 * @param servletContext the servlet context to register the listener against
 */
protected void registerContextLoaderListener(ServletContext servletContext) {
   

	// 创建spring上下文,注册了SpringContainer
	WebApplicationContext rootAppContext = createRootApplicationContext();
	if (rootAppContext != null) {
   
		// 创建监听器
		/*
			形如这种配置
		* <listener>
			  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
			<!--<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>-->
		  </listener>
		*
		* */
		ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
		listener.setContextInitializers(getRootApplicationContextInitializers());
		servletContext.addListener(listener);
	}
	else {
   
		logger.debug("No ContextLoaderListener registered, as " +
				"createRootApplicationContext() did not return an application context");
	}
}

createRootApplicationContext():
创建spring上下文,上下文就是收集各种bean的信息的容器,有两种收集方式
一种是配置文件收集
另一种则是注解方式收集,注解方式需要需要配置扫描器(@ComponentScan)去扫描项目中需要扫描入容器的bean。

在这个方法中,有个getRoowConfigClasses(),这就需要我们自己写一个类,继承AbstractAnnotationConfigDispatcherServletInitializer类,重写这个方法,将扫描器提供给上下文对象

@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
   
	Class<?>[] configClasses = getRootConfigClasses();
	if (!ObjectUtils.isEmpty(configClasses)) {
   
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		context.register(configClasses);
		return context;
	}
	else {
   
		return null;
	}
}

然后将上下文对象设置到ContextLoaderListener监听器对象中,最后把监听器对象设置到servletContext中

if (rootAppContext != null) {
   
	// 创建监听器
	/*
		形如这种配置
	* <listener>
		  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
		<!--<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>-->
	  </listener>
	*
	* */
	ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
	listener.setContextInitializers(getRootApplicationContextInitializers());
	servletContext.addListener(listener);
}
else {
   
	logger.debug("No ContextLoaderListener registered, as " +
			"createRootApplicationContext() did not return an application context");
}

registerDispatcherServlet()方法

这个方法,作用是实例化DispatcherServlet对象
主要就是两个createXXX方法来创建springmvc上下文跟servlet对象
创建springmvc上下文方法,跟创建spring上下文基本一致,都需要自己定义一个扫描器来扫描生成上下文。
然后根据上下文创建servlet对象

protected void registerDispatcherServlet(ServletContext servletContext) {
   
	String servletName = getServletName();
	Assert.hasLength(servletName, "getServletName() must not return null or empty");

	// 创建springmvc的上下文,注册了MvcContainer类
	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.");
	}

	/*
	* 	如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
		如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,
		值越小,servlet的优先级越高,就越先被加载
	* */
	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);
}

到此为止一共有spring跟springmvc上下文,分别存放在ContextLoaderListener对象跟DispatcherServlet中

启动spring容器

监听器 ContextLoaderListener 的启动
ContextLoaderListener类的 contextInitialized 方法

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

在这里插入图片描述
DispatcherServlet的启动
servlet要完成spring容器的启动,只能在init方法里面完成,所以需要找到此方法
DispatcherServlet 的父类 HttpServletBean 中找到init()方法,主要看方法里面的 initServletBean 方法

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

initServletBean()

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 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值