云计算是当前最火的技术,Hadoop/MapReduce/NoSQL都是为了适应海量数据和云计算而生的技术,而绝大多数企业应用软件似乎还在徘徊,听到的顶多是集成现有云存储到企业软件中,如BI开源产品Pentaho提供到Big Data的连接器。不过类似的消息也越来越多,传统的企业应用开始一个个都加入了云的阵营。Spring大家都熟悉,企业应用里最流行的框架,不久前被VMWare收购,如今随着VMWare的崛起,其Cloud Foundry将Spring,Rails,Node.js和scala正式带入云端。企业应用飞入云里已指日可待!

于是花了点时间,将最新的Spring3.1(支持CloudFoundry平台)的源代码过了一遍,看看它如何在虚拟化的平台发挥作用的。

1. IoC如何工作?

在Spring中,最基本的IOC容器接口是BeanFactory – 这个接口为具体的IOC容器的实现作了最基本的功能规定 – 不管怎么着,作为IOC容 器,这些接口你必须要满足应用程序的最基本要求。XmlBeanFactory是基于XML配置的基本实现,但是在3.1中已经被deprecate,不过替换方法是比较简单的,使用DefaultListableBeanFactory和XmlBeanDefinitionReader来组合IoC容器的创建。

假设beans.xml中定义了“student”bean,然后编写程序来使用它。



ClassPathResource res = new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);reader.loadBeanDefinitions(res);factory.getBean("student");
这些代码演示了以下几个步骤:
  • 1. 创建IOC配置文件的抽象资源
  • 2. 创建一个BeanFactory
  • 3. 把读取配置信息的BeanDefinitionReader,这里是XmlBeanDefinitionReader配置给BeanFactory
  • 4. 从定义好的资源位置读入配置信息,具体的解析过程由XmlBeanDefinitionReader来完成,这样完成整个载入bean定义的过程。我们的IoC容器就建立起来了。
  • 5. 从创建好的factory中获取bean

最终xml文件的解析是在类DefaultBeanDefinitionDocumentReader.registerBeanDefinitions()中实现的。

  • 1. 处理默认的节点,包括bean, alias, import, beans。使用BeanDefinitionParserDelegate.parseBeanDefinitionElement()来创建BeanDefinition,
    • BeanDefinitionReaderUtils.createBeanDefinition(parentName, className, classloader)返回BeanDefinition;
    • 创建一个GenericBeanDefinition实例,设置parentName和beanClass(使用classLoader来获取bean对应的class);(问题:如果class不在当前类路径下,而是OSGi或者cloud上的其它地方,如何让Spring找到它?)
      • 这里的classloader来自readerContext.getBeanClassLoader(),其中readContext来自XmlBeanDefinitionReader,其上有setter和getter来设置别的classloader!
    • bean的name和BeanDefinition的映射注册到XmlBeanDefinitionReader 的registry里, 而这个registry就是DefaultListableBeanFactory,factory里有一个私有的ConcurrentHashMap变量beanDefinitionMap,存储所有的bean定义。
  • 2. 处理其它客户自定义的节点

从创建好的factory中获取bean是由DefaultListableBeanFactory的父类实现的。大致逻辑是先从当前缓存中找,如果没有在到父工厂中找。

BeanFactory 是一个接口,在实际应用中我们一般使用ApplicationContext来使用IOC容器,它们也是IOC容器展现给应用开发者的使用 接口。对应用程序开发者来说,可以认为BeanFactory和ApplicationFactory在不同的使用层面上代表了SPRING提供的IOC容器服务。 下面我们具体看看通过FileSystemXmlApplicationContext是怎样建立起IOC容器的。

ApplicationContext context = new FileSystemXmlApplicationContext("beans.xml");

context.getBean("student");

context接收configLocation参数后,直接调用refresh()方法,其初始化过程的大致步骤由AbstractApplicationContext来定义。

  • prepareRefresh();
  • beanFactory = obtainFreshBeanFactory(); // 让子类(这里是FileSystemXMLApplicationContext)来刷新初始的Bean工厂
    • 内部创建一个DefaultListableBeanFactory作为bean工厂
    • AbstractXmlApplicationContextloadBeanDefinitions(fac)方法将真正负责bean定义的加载和创建,它类似上面的客户端代码,由XmlBeanDefinitionReader负责加载beans.xml并实例化bean定义,所有的定义将存入内部bean工厂中。
  • prepareBeanFactory(beanFactory); // 设置bean工厂的属性
    • 设置类加载器,Bean的表达式分析器(ExpressionResolver),增加属性编辑器注册器;
    • 设置上下文回调信息,如BeanPostProcessor,忽略依赖接口ResourceLoaderAware, ApplicationEventPublisherAware, MessageSourceAware, ApplicationContextAware, EnvironmentAware;
    • registerResolvableDependency() on BeanFactory, ResourceLoader, ApplicationEventPublisher, ApplicationContext
    • 看Bean工厂中是否包含“LoadTimeWeaver”Bean,如果有就给它增加一个beanPostProcessor到bean工厂;
    • 增加下列bean到bean工厂
      • “environment”
      • systemProperties”
      • systemEnvironment”
  • postProcessBeanFactory(beanFactory); // 子类覆盖方法做额外的处理
  • invokeBeanFactoryPostProcessors(beanFactory); // 调用所有的BeanPostProcessor
  • registerBeanPostProcessors(beanFactory); // 注册拦截Bean创建的Bean处理器
  • initMessageSource();  // 为上下文初始化Message源,即不同语言的消息体
  • initApplicationEventMulticaster(); // 初始化应用消息广播器,并放入“applicationEventMulticaster”bean中
  • onRefresh();  // 留给子类来初始化其它的Bean
  • registerListeners();  // 在所有注册的bean中查找Listener bean,注册到消息广播器中
  • finishBeanFactoryInitialization(beanFactory); // 初始化剩下的单实例(非惰性的)
  • finishRefresh();  // 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
  • 如有异常,将调用destroyBeans()和cancelRefresh()方法;

仔细分析Spring BeanFactory的结构,我们来看看在BeanFactory基础上扩展出的ApplicationContext – 我们最常使用的上下文。除了具备 BeanFactory的全部能力,上下文为应用程序又增添了许多便利:

可以支持不同的信息源,我们看到ApplicationContext扩展了MessageSource访问资源 , 体现在对ResourceLoader和Resource的支持上面,这样我们可以从不同地方得到bean定义资源支持应用事件,继承了接口ApplicationEventPublisher,这样在上下文中引入了事件机制而BeanFactory是没有的。

ApplicationContext允许上下文嵌套 – 通过保持父上下文可以维持一个上下文体系 - 这个体系我们在下面对Web容器中的上下文环境的分析中可以清楚地看到。对于bean的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为 不同的Spring应用提供了一个共享的bean定义环境。这个我们在分析Web容器中的上下文环境时也能看到。



2. WebApplicationContext如何初始化?

对于一个Spring激活的web应用程序,可以通过使用Spring代码声明式的指定在web应用程序启动时载入应用程序上下文(WebApplicationContext),Spring的ContextLoader是提供这样性能的类,我们可以使用 ContextLoaderServlet或者ContextLoaderListener的启动时载入的Servlet来实例化Spring IOC容器 – 为什么会有两个不同的类来装载它呢,这是因为它们的使用需要区别不同的Servlet容器支持的Serlvet版本。但不管是 ContextLoaderSevlet还是 ContextLoaderListener都使用ContextLoader来完成实际的WebApplicationContext的初始化工作。这个ContextLoder就像是Spring Web应用程序在Web容器中的加载器booter。当然这些Servlet的具体使用我们都要借助web容器中的部署描述符来进行相关的定义。

在web.xml中定义:

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/applicationContext.xml</param-value>

</context-param>

<listener>

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

</listener>

<!– OR USE THE CONTEXTLOADERSERVLET INSTEAD OF THE LISTENER

<servlet>

<servlet-name>context</servlet-name>

<servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

–>

下面我们使用ContextLoaderListener作为载入器作一个详细的分析,这个Servlet的监听器是根上下文被载入的地方,也是整个 Spring web应用加载上下文的第一个地方;从加载过程我们可以看到,首先从Servlet事件中得到ServletContext,然后可以读到配置好的在web.xml的中的各个属性值,然后ContextLoder实例化WebApplicationContext并完成其载入和初始化作为根上下文。当这个根上下文被载入后,它被绑定到web应用程序的ServletContext上。任何需要访问该ApplicationContext的应用程序代码都可以从WebApplicationContextUtils类的静态方法来得到:

WebApplicationContext WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)

以Tomcat作为Servlet容器为例,下面是具体的步骤:

a) Tomcat 启动时需要从web.xml中读取启动参数,在web.xml中我们需要对ContextLoaderListener进行配置,对于在web应用启动入口是在ContextLoaderListener中的初始化部分(contextInitialized());从Spring MVC上看,实际上在web容器中维护了一系列的IOC容器,其中在ContextLoader中载入的IOC容器作为根上下文而存在于 ServletContext中。通过ContextLoader建立起根上下文的过程,我们可以在ContextLoader中的initWebApplicationContext()看到。

b) 查找ServletContext的初始化参数“contextClass”,没有的话就看“ContextLoader.properties”文件中定义的值,默认为org.springframework.web.context.support.XmlWebApplicationContext。弦外之意是Spring提供默认的ApplicationContext实现,如果要客户化,请在ServeltContext中指定;找到类名后在classLoader中构造一个实例(这儿的类加载器首先在当前线程中的类加载器中寻找,然后在当前类的类加载器寻找。因此你可以修改当前的线程加载器让Spring到别处去寻找,比如OSGi框架中)

c) 如果context是ConfigurableWebApplicationContext的实现,则调用configureAndRefreshWebApplicationContext()初始化。包括:

  • 查找ServletContext里定义的初始化参数contextId,赋给上下文作为ID,如果没有则系统生成一个;
  • 然后查找父上下文,连接它们的关系;查找ServletContext的初始化参数“parentContextKey”和“locatorFactorySelector”,当parentContextKey不为空时,以locatorFactorySelector为键在ContextSingletonBeanFactoryLocator中获得BeanFactoryLocator,再从中获得一个应用上下文,作为父上下文;

BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);

this.parentContextRef = locator.useBeanFactory(parentContextKey);

parentContext = (ApplicationContext) this.parentContextRef.getFactory();

  • 将ServletContext作为变量存入当前上下文(所有所有的BeanFactory都可以拿到这个Servlet上下文);
  • 将ServletContext中的初始化参数“contextConfigLocation”赋给当前上下文configLocation;其值默认为“/WEB-INF/applicationContext.xml”;
  • 找到所有的上下文初始器(从参数“contextInitializerClasses”中得到,是由逗号“,”分隔的类名,每个类必须实现ApplicationContextInitializer<ConfigurableApplicationContext>接口),然后使用当前应用上下文作为参数初始化它们;最后调用应用上下文的refresh()方法。ApplicationContext的刷新过程在上节中已经说过,只是bean定义的XML文件来源于web.xml中的“contextConfigLocation”。

d) 当应用上下文创建并初始化成功后,将其存入ServletContext的属性“WebApplicationContext.ROOT”中,以便其它组件获取应用上下文。

e) 如果当前线程的类加载器是当前类ContextLoader的类加载器,则将应用上下文保存为ContextLoader的当前上下文,否则将<线程的类加载器,应用上下文>键值对保存到ContextLoader的currentContextPerThread中。返回…

3. Spring MVC如何工作的?

当你使用Spring MVC框架时, 你会如下配置web.xml。

请注意这个时候我们的WebApplicationContext已经建立起来了,也意味着DispatcherServlet有自己的定义资源,可以需要从web.xml中读取bean的配置信息,通常我们会使用单独的xml文件来配置MVC中各个要素定义,这里和web容器相关的加载过程实际上已经完成了,下面的处理和普通的Spring应用程序的编写没有什么太大的差别,我们先看看MVC的初始化过程:

  1. <web-app> 
  2. <servlet> 
  3.     <servlet-name>Dispatcher</servlet-name> 
  4. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
  5.     <init-param> 
  6.         <param-name>contextConfigLocation</param-name> 
  7.         <param-value>/WEB-INF/beans.xml</param-value> 
  8.     </init-param> 
  9.     <load-on-startup>1</load-on-startup> 
  10. </servlet> 
  11. <servlet-mapping> 
  12.     <servlet-name>Dispatcher</servlet-name> 
  13.     <url-pattern>*</url-pattern> 
  14. </servlet-mapping> 
  15. </web-app>

则对应的applicationContext的attribute key值为org.springframework.web.servlet.FrameworkServlet.CONTEXT.mvcServlet。在每次request请求时,DispatcherServlet会将此applicationContext存放在request中attribute值为org.springframework.web.servlet.DispatcherServlet.CONTEXT中(request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE,getWebApplicationContext());)。可以通过RequestContextUtils.getWebApplicationContext 或 WebApplicationContextUtils.getWebApplicationContext(servletContext,attrname)方法 来获取对应的applicationContext。

DispatchServlet.properties定义了servlet内部需要使用的类,DispatchServlet初始化时会使用这些定义。

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\

    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\

    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\

    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\

    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\

    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.DefaultFlashMapManager

DispatchServlet初始化过程:servelt的init()方法调用initServletBean()方法,该方法在中FrameworkServlet重写。

  • initWebApplicationContext
  • initFrameworkServlet

DispatchServlet.onRefresh(ApplicationContext)方法会触发initStrategies()来初始化这些bean。

protected void initStrategies(ApplicationContext context) {

        initMultipartResolver(context);

        initLocaleResolver(context);

        initThemeResolver(context);

        initHandlerMappings(context);

        initHandlerAdapters(context);

        initHandlerExceptionResolvers(context);

        initRequestToViewNameTranslator(context);

        initViewResolvers(context);

        initFlashMapManager(context);

    }

4. AOP如何工作?