Spring启动

mac2022-06-30  31

在web项目中使用spring的时候,我们会在web.xml中加入如下配置:

<listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>

      Listener也叫监听器,是Servlet的监听器,它可以监听客户端的请求、服务端的操作等。

      Spring的ContextLoaderListener 实现了实现了javax.servlet.ServletContextListener接口 ,需要实现接口contextInitialized()方法。当Servelt容器在启动的时候,会调用contextInitialized()方法。该监听器会自动扫描WEB-INF/ 下的applicationContext.xrnl 文件。spring中ContextLoaderListener也把这个作为起始点来初始化,

contextInitialized()方法的实现如下:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener { private ContextLoader contextLoader; public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader();//此方法直接return null了 if (this.contextLoader == null) { this.contextLoader = this; } //主要看这里 this.contextLoader.initWebApplicationContext(event.getServletContext()); } @Deprecated protected ContextLoader createContextLoader() { return null; } }

根据上面的代码,主要是调用了initWebApplicationContext方法。这个方法就是我们窥探的入口,它是在ContextLoader类中的:可以先不要读这个代码,大概扫一眼,然后继续根据后面的文字描述跟踪逻辑:

在web.xml中

<context-param>   <param-name>contextConfigLocation</param-name>   <param-value>/WEB-INF/application*.xml</param-value> </context-param>

在看看:ContextLoader类

//为了方便初步的阅读,我删除了一些占用篇幅的地方 public class ContextLoader { .... public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";//web.xml里有,熟悉吧 ... static { //这个静态代码块读取了ContextLoader.properties这个配置文件。在spring源码中可以找到这个配置文件 //内容只有一行 指定WebApplicationContext的实现类为XmlWebApplicationContext try { ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { ... } } private static volatile WebApplicationContext currentContext; private WebApplicationContext context; private BeanFactoryReference parentContextRef; public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { .... try { if (this.context == null) { // 这个create方法创建了一个ConfigurableWebApplicationContext实例 this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; if (!cwac.isActive()) {//isActive默认为false,所以会进入if,执行下面的代码 if (cwac.getParent() == null) { ApplicationContext parent = loadParentContext(servletContext); cwac.setParent(parent); } configureAndRefreshWebApplicationContext(cwac, servletContext); } } servletContext.setAttribute("一个变量名", 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 ex) { ... } catch (Error err) { ... } } }

在上面的贴出的的代码中,我们可以看到这个ContextLoader类有一个静态代码块,静态代码块会在类加载的时候就执行了,这个代码块执行的内容很简单,就是找到一个名为“ContextLoader.properties”的配置文件,并将这个resources赋给defaultStrategies变量。

ContextLoader.properties的内容如下:

org.springframework.web.context.WebApplicationContext= org.springframework.web.context.support.XmlWebApplicationContext

我们继续看initWebApplicationContext方法。这个方法很长,但是实际上它主要做了两件事情:

1.创建一个ConfigurableWebApplicationContext实例。 2.根据创建的ConfigurableWebApplicationContext实例,来配置并刷新WebApplicationContext。

先看第一步,创建ConfigurableWebApplicationContext,方法是createWebApplicationContext()。代码如下:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) { Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("日志描述"); } return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); }

这个方法先调用determineContextClass来找到一个类,然后return语句中通过BeanUtils反射来创建这个类的实例并返回。

determineContextClass类中,其实就是利用刚才读到的配置文件“ContextLoader.properties”,从这个文件中得到配置的类名根据类名并利用反射来创建这个类的实例并返回。

在看第二步,刷新WebApplicationContext。即调用了configureAndRefreshWebApplicationContext(...)方法。这个方法里边做的事情很重要。先看看代码:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // 这里我删除了一段无关紧要的代码,起逻辑和下面的两句一样。 String xxx= ...; wac.setId(xxx) } wac.setServletContext(sc); // 这里得到了我们在web.xml中配置的一个值,即:contextConfigLocation的值,也就是我们的spring文件 String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (initParameter != null) { // 设置spring的配置文件 wac.setConfigLocation(initParameter); } customizeContext(sc, wac);//后面分析 wac.refresh();//后面分析 }

方法中,最后两行的方法调用。

     先看customizeContext(sc,wac)方法,方法的里边通过serveltContext 的getInitParameter方法得到contextInitializerClasses。如果没配置,就什么也不做,进入之后,可以看到头两行的内容如下:

List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(servletContext); if (initializerClasses.size() == 0) { // no ApplicationContextInitializers have been declared -> nothing to do return; }

     通过determineContextInitializerClasser方法来获取配置的contextInitializerClasses变量值,这个值是一个class类名,多个的话用逗号隔开。如果没有配置的话,代码就会执行if size=0这一句,然后return了。

在看看wac.refresh()方法调用,非常重要。这个方法实际上完成了spring 容器的初始化

public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { .... }

      refresh()方法主要为IoC容器Bean的生命周期管理提供条件,Spring IoC容器载入Bean定义资源文件从其子类容器的refreshBeanFactory()方法启动,所以整个refresh()中“ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();”这句以后代码的都是注册容器的信息源和生命周期事件,载入过程就是从这句代码启动。

      AbstractApplicationContext的obtainFreshBeanFactory()方法调用子类容器的refreshBeanFactory()方法,启动容器载入Bean定义资源文件的过程。

     refresh()方法的作用是:在创建IoC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IoC容器。refresh的作用类似于对IoC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入。

和refreshBeanFactory方法类似,载入Bean定义的方法loadBeanDefinitions也使用了委派模式,在AbstractRefreshableApplicationContext类中只定义了抽象方法,具体的实现调用子类容器中的方法实现。

至此spring容器初始化完成了,这个wac.refresh()代码暂时不做深究,wac.refresh()详解

 

 

 

 

最新回复(0)