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