SpringMVC源码探究——SpringMVC的启动及容器初始化(注解方式)

前置准备

0.Servlet规范

servlet规范规定,在servlet启动时,要通过ServletContainerInitializer处理所有@HandlesTypes中标注的类,而ServletContainerInitializer是一个接口。
而ServletContainerInitializer的实现类是Tomcat通过SPI机制加载的。

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

1.SPI机制

JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。

当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。

根据以上的知识,我们可以找到SpirngMVC实现了ServletContainerInitializer的类:SpringServletContainerInitializer
在这里插入图片描述

//可以看到SpringServletContainerInitializer 对WebApplicationInitializer感兴趣,它只处理WebApplicationInitializer相关的类
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
       //暂时省略
        }
    }
}

再来看看WebApplicationInitializer长什么样。

public interface WebApplicationInitializer {
    void onStartup(ServletContext var1) throws ServletException;
}

可以看到它和ServletContainerInitializer 类似,都是一个接口,且都有一个onStartup方法,区别在于WebApplicationInitializer的方法参数有两个:一个class集合,一个servlet上下文。而WebApplicationInitializer的参数只有一个即servlet上下文。

这里不难猜到SpringServletContainerInitializer的实现类会遍历class集合中的所有类,并调用它们的onStartup方法。

3.环境准备

实验采用Spring及SpringMVC版本均为5.3.1
1.首先写一个配置类,用来配置包扫描路径

@ComponentScan("com.example")
@Configuration
public class WebConfig {
}

2.根据spring官方文档的写法,写一个WebApplicationInitializer的实现类

public class MyApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //新建一个IOC容器
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        //注册配置类
        context.register(WebConfig.class);
        //新建DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/*");
    }
}

正文

通过前文我们知道Tomcat在启动时会通过SPI机制加载实现了ServletContainerInitializer的类,所以我们的探究就从SpringServletContainerInitializer开始。
在这里插入图片描述
打上断点,我们可以发现传入的类的集合里有了我们自己写的实现类。它可能是通过与@HandlesTypes 的某种机制实现的,这里先不探究,接着来看SpringServletContainerInitializer的源码

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        //准备一个空集合用来存放WebApplicationInitializer的实现类
        List<WebApplicationInitializer> initializers = Collections.emptyList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
            //实例化
            initializers = new ArrayList(webAppInitializerClasses.size());
            //迭代
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
            	//得到webAppInitializerClasses中的一个class对象
                Class<?> waiClass = (Class)var4.next();
                //若该对象若满足 1.不是接口 2. 不是抽象类 3.继承/实现自waiClass
                //这三个条件,则将它放入提前准备的List中。
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        //通过反射将class实例化并放入list中
                        ((List)initializers).add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }
			//所有的WebApplicationInitializer实现类均已被放入list中
        if (((List)initializers).isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(((List)initializers).size() + " Spring WebApplicationInitializers detected on classpath");
            //按照AnnotationAwareOrderComparator来进行排序,这里不细看
            AnnotationAwareOrderComparator.sort((List)initializers);
            var4 = ((List)initializers).iterator();
			//遍历所有的WebApplicationInitializer实现类,调用它们的onStartUp方法
            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}

紧接着来到我们自己写的实现类

public class MyApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //新建一个IOC容器
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        //注册配置类
        context.register(WebConfig.class);
        //新建DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/*");
    }
}

按照官方的写法,我们首先自己准备了一个AnnotationConfigWebApplicationContext 从名字和继承树我们可以看到它是一个IOC容器。
在这里插入图片描述
之前的文章中我们知道了在容器实例化的时候会进行容器刷新的12大步,但这里似乎什么都没有做?
这里先留个坑,我们接着往下看。紧接着是进行DispatcherServlet 的配置,就像我们之前在web.xml文件中配置类似,只不过现在将其用编码的方式实现了。

我们注意到在实例化DispatcherServlet时把IoC容器传过去了,我们可以推测容器刷新的十二大步一定与DispatcherServlet有关。

换个角度来思考,DispatcherServlet归根结底是个Servlet,既然是servlet就必然要遵守servlet的init->service->destroy的生命周期。显然,容器的初始化最有可能发生在初始化阶段。我们可以从这个角度切入,看看DispatcherServlet做了什么事情。首先来看DispatcherServlet的继承树
在这里插入图片描述
从树结构一个个向上找,我们在HttpServletBean中找到了init()方法。

public final void init() throws ServletException {
		//首先会读取配置,当我们通过web.xml形式来配置SpringMVC时,就是在这里读取到了我们配置的
		//SpringMVC的配置文件相关的信息。
        PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        if (!pvs.isEmpty()) {
            try {
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
                this.initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            } catch (BeansException var4) {
                if (this.logger.isErrorEnabled()) {
                    this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
                }

                throw var4;
            }
        }
		//模板方法留给子类实现
        this.initServletBean();
    }

来到HttpSevletBean的子类FrameworkServlet
我们可以看到,该方法的前后都是打印了一些相关日志,真正重要的是中间两行
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();

protected final void initServletBean() throws ServletException {
        this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("Initializing Servlet '" + this.getServletName() + "'");
        }

        long startTime = System.currentTimeMillis();

        try {
            this.webApplicationContext = this.initWebApplicationContext();
            this.initFrameworkServlet();
        } catch (RuntimeException | ServletException var4) {
            this.logger.error("Context initialization failed", var4);
            throw var4;
        }

        if (this.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";
            this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value);
        }

        if (this.logger.isInfoEnabled()) {
            this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
        }

    }

来到initWebApplicationContext()方法,这里涉及一个父子容器的问题,这里先不讨论。
按照我们的写法这里应该是只有一个IOC容器的。

    protected WebApplicationContext initWebApplicationContext() {
        //拿到根容器,我们这里只有一个容器,根容器为空
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;
        //这个webApplicationContext 及我们手动实例化的容器
        if (this.webApplicationContext != null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
                if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }
					//重点 配置并刷新容器
                    this.configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
		//省略部分代码
        return wac;
    }

来到configureAndRefreshWebApplicationContext()方法

 protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            if (this.contextId != null) {
                wac.setId(this.contextId);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName());
            }
        }
		//,可以看到先向容器中配置了一些东西(servle上下文,servlet配置等)
        wac.setServletContext(this.getServletContext());
        wac.setServletConfig(this.getServletConfig());
        wac.setNamespace(this.getNamespace());
        wac.addApplicationListener(new SourceFilteringListener(wac, new FrameworkServlet.ContextRefreshListener()));
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig());
        }
		//模板方法,我们可以实现该方法来在容器刷新前对容器做一些操作。
        this.postProcessWebApplicationContext(wac);
        //我们可以实现ApplicationContextInitializer类在容器刷新之前对容器做一些操作
        this.applyInitializers(wac);
        //熟悉的refresh方法
        wac.refresh();
    }

web容器的刷新与之前提到过的容器刷新略有不同,下一篇文章再来详解。对容器刷新没有概念的读者可以看看这篇文章

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值