热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

解析Java的Spring框架的基本结构

这篇文章主要介绍了Java的Spring框架的基本结构,作者从Spring的设计角度触发解析其基础的架构内容,需要的朋友可以参考下

   在java届,有位名叫Rod Johnson的牛人,发现最初的java企业级开发处于混沌状态。

   于是,它决心编写一个能够解决问题的通用的基础架构。

   因为它深信面向接口编程能够将变化控制到最小,同时也利于扩展和变化。于是,它编写了如下的接口。 

201632184450914.png (1045×387)

   在混沌状态最先要创造的是一切对象的母亲BeanFactory,有了它,就能够得到一切它孕育的对象和属性,也就是说首先要造盖亚--大地之母。

   有了最初的母亲BeanFactory,johnson想,如果我要得到一组Bean对象而不单单是某个或某几个呢?另外,如果母亲的孩子也要孕育对象呢?于是,johnson创造了ListableBeanFactory以操作一组bean对象,比如getBeansOfType就能够根据得到同类型的一组Bean;创造了HierarchicalBeanFactory来解决多个BeanFactory的层次问题,比如getParentBeanFactory就能够得到BeanFactory的父Factory。

   这个BeanFactory最终是要在某个应用上使用的,那么,需要给予BeanFactory在一个应用中活动的能力。在BeanFactory中,只需要考虑跟bean相关的行为,比如怎么得到bean,bean的类型等;而如果要赋予其在应用中的能力,则就需要考虑更多,比如应用的名字,启动时间,id等等跟应用本身相关的行为和属性,于是johnson想到了创造ApplicationContext。johnson想,这个ApplicationContext一定要能做许多事,要能够处理参数化和国际化的文本信息,于是增加了MessageSource接口;要能发布事件以便解耦组件,于是有了ApplicationEventPublisher接口;要能得到资源文件,于是有了ResourcePatternResolver接口;要能有在不同环境有不同处理对象的能力,于是有了EnvironmentCapable接口。

   ApplicationContext继承了所有这些接口。

   但是最重要的是,无论是BeanFactory还是ApplicationContext,它们都需要有可配置的能力,于是有了子接口ConfigurableBeanFactory和ConfigurableApplicationContext;另外,web在当时是非常重要的趋势,而且相比其他应用有些独特,需要得到ServletContext,于是有了WebApplicationContext。

   到目前为止,johnson都是面向接口进行行为的抽象思考,并未具体实现他们。

   看着创造出来的BeanFactory和ApplicationContext,johnson意识到这一天的工作远远没有结束,因为并没有真正解决怎么能够让整套体系运转起来,于是,johnson开始思索如何实现他们。

   johoson首先想到的还是这个实现应该具备什么能力?当然要把之前提到的AutowireCapableBeanFactory,ListableBeanFactory和ConfigurableBeanFactory都包括进去。因此创造了ConfigurableListableBeanFactory。其次,需要考虑对于bean对象的几种能力,一是起别名的能力;二是保存单例对象的能力;三是缓存的能力;他们分别在SimpleAliasRegistry类,DefaultSingletonBeanRegistry类和FactoryBeanRegistrySupport类中实现。

201632184523588.png (673×591)

   最终,创造出了DefaultListableBeanFactory,它是spring中一切ioc工厂的原型,是BeanFactory第一个真正的孩子,这个孩子非常重要,已经成为独立的创建ioc容器的基础,如果有扩展和使用,大多是继承它或者组合使用它。

  如果要初始化一个DefaultListableBeanFactory,可以用如下代码

ClassPathResource res = new ClassPathResource("applicationContext.xml"); 
    DefaultListableBeanFactory f = new DefaultListableBeanFactory(); 
    XmlBeanDefinitionReader r = new XmlBeanDefinitionReader(f); 
    r.loadBeanDefinitions(res); 

  接下来johnson想,BeanFactory有了一个功能比较全面的默认实现,那么ApplicationContext呢?于是johnson孜孜不倦的创造了3个重要的ApplicationContext实现:FileSystemXmlApplicationContext, ClassPathXmlApplicationContext, AnnotationConfigWebApplicationContext(其实还有很多,比如处理portlet的, 处理web的)
201632184550126.png (791×504)

    johnson最先考虑的是如何去做spring的启动流程,它应该放到一个比较抽象的层次以便下层的所有类能够复用。于是他用一个AbstractApplicationContext实现了ConfigurableApplicationContext。还有一个很重要的功能,即将一个文件以资源的形式加载进来,这需要将资源抽象为Resource类,将定位资源的具体实现抽象到ResourceLoader,AbstractApplicationContext同样需要继承DefaultResourceLoader以提供这个功能。AbstractApplicationContext完成了整个启动流程(上帝将它安排在第二天完成),唯独没有做对BeanFactory的管理。于是,它的子类AbstractRefreshableApplicationContext专门做了这件事,实现了refreshBeanFactory, closeBeanFactory, getBeanFactory专门对BeanFactory的生命周期做了一些管理,但是AbstractRefreshableApplicationContext仍然没有加载所有配置好的Bean。到哪里加载配置好的资源,实际上到了下层的子类去做,比如FileSystemXmlApplicationContext,就是到文件系统去读一个xml形式的applicationContext;ClassPathXmlApplicationContext则是到类加载路径下去读这个applicationContext。而AnnotationConfigWebApplicationContext则从类文件的annotation中加载bean,spring的扫描也从此开始。

  看着主要的框架已经建立起来,johnson满意的笑着睡着了。
 
   头一日,johnson完成了一个spring的整体框架。

  第二日,johnson准备实际去处理前面遗留的问题。比如spring的容器初始化过程。如图,johnson将这个过程分为很多子过程,这些子过程都在围绕着如何将bean载入这一宏伟的目标而努力。

201632184620113.png (148×651)

    这个过程放在AbstractApplicationContext中的refresh方法中。代码如下,johnson将refresh的过程分为很多子过程,并且这些子过程在同一个抽象层级上,这种写法是为了给后人一个榜样。

public void refresh() throws BeansException, IllegalStateException { 
  synchronized (this.startupShutdownMonitor) { 
    // Prepare this context for refreshing. 
    prepareRefresh(); 
 
    // Tell the subclass to refresh the internal bean factory. 
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 
 
    // Prepare the bean factory for use in this context. 
    prepareBeanFactory(beanFactory); 
 
    try { 
      // Allows post-processing of the bean factory in context subclasses. 
      postProcessBeanFactory(beanFactory); 
 
      // Invoke factory processors registered as beans in the context. 
      invokeBeanFactoryPostProcessors(beanFactory); 
 
      // Register bean processors that intercept bean creation. 
      registerBeanPostProcessors(beanFactory); 
 
      // Initialize message source for this context. 
      initMessageSource(); 
 
      // Initialize event multicaster for this context. 
      initApplicationEventMulticaster(); 
 
      // Initialize other special beans in specific context subclasses. 
      onRefresh(); 
 
      // Check for listener beans and register them. 
      registerListeners(); 
 
      // Instantiate all remaining (non-lazy-init) singletons. 
      finishBeanFactoryInitialization(beanFactory); 
 
      // Last step: publish corresponding event. 
      finishRefresh(); 
    } 
 
    catch (BeansException ex) { 
      // Destroy already created singletons to avoid dangling resources. 
      destroyBeans(); 
 
      // Reset 'active' flag. 
      cancelRefresh(ex); 
 
      // Propagate exception to caller. 
      throw ex; 
    } 
  } 
} 

  如果更高层次一些看,实际上这些过程围绕着几个方面来做:1. 刷新的生命周期;2. 对beanFactory的初始化及准备;3. 生成并注册beanDefinition;4. beanFactory后处理器;5.设置消息,事件及监听器。
  1. 刷新的生命周期

201632184644326.png (473×197)

    prepareRefresh,这个过程主要记录日志表示spring启动了,初始化property资源(比如serlvet中一些资源初始化),以及property资源的验证(比如只写了key没有value)。

  onRefresh,目的是提供给一些特殊的ApplicationContext,使他们有能够在刷新过程中能够扩展的能力。目前使用到的大多是为servlet application context设置theme

  finishRefresh,做一些收尾的工作,如初始化LifecycleProcessor,发布refresh结束的事件等。

  cancelRefresh,主要是在异常产生时将当前的状态改变为非active。

  2. 对beanFactory的初始化及准备

  johnson想,我们应该让beanFactory在初始化时就把bean透明的加载并注册好,这样对外界而言,我的封装就非常成功,因此这步实际上做了很多事。下图省略了许多步骤,只列出关键点。

  AbstractApplicationContext会调用refreshBeanFactory,它首先会检查并关闭已有的beanFactory,其次新建一个beanFactory,然后利用该factory装载所有BeanDefinition

  其中,loadBeanDefinitions会交给子类做不同的实现,比如AbstractXmlApplicationContext主要是通过xml读取;AnnotationConfigWebApplicationContext的实现则会调用扫描器扫描类中的bean

201632184713998.png (992×248)

   3. 生成并注册beanDefinition
  当解析完xml配置以后,DefaultBeanDefinitionDocumentReader的parseDefaultElement方法会根据xml中的元素做对应的处理。其中,遇到bean元素时会最终调用BeanDefinitionReaderUtils中的registerBeanDefinition方法,该方法传入的参数为BeanDefinitionRegistry,实际上是回调了DefaultListableBeanFactory的registerBeanDefinition方法来注册beanDefinition(DefaultListableBeanFactory实现了BeanDefinitionRegistry)。

   4. beanFactory后处理器

   beanFactory后处理器是spring提供出来的让其子类灵活扩展的方式。spring中分为2个步骤:postProcessBeanFactory,invokeBeanFactoryPostProcessors。registerBeanPostProcessors则是实例化并调用所有的BeanPostProcessor,用来在bean初始化前和初始化后对bean做扩展。

   5. 设置消息,事件及监听器

  设置默认消息源为DelegatingMessageSource,如工厂里已经有messageSource则使用该messageSource,事件多播器为SimpleApplicationEventMulticaster,如工厂里已经有applicationEventMulticaster,则使用该applicationEventMulticaster,并注册所有的应用程序Listener以便能够接收事件

  消息源:MessageSource是国际化资源文件的重要方法,spring在applicationContext就支持消息源。

  spring中提供了MessageSource的默认实现,使用java.util.ResourceBundle来提取消息。spring通过配置一个特殊id为messageSource的bean并制定i18n的文件名,就能够从ApplicationContext.getMessage()直接访问message。如果在jsp中,还能通过spring:message这个tag访问到message。

  事件:事件是比较重要的解耦机制,spring在核心ApplicationContext就引入了它,其原理比较简单,一方面是事件产生方,能够发送事件;一方面似乎事件监听方,能够响应事件。而具体实现基本上都是在产生方hold住一个事件监听者集合,并将所有监听方“注册”(即加入)到这个事件监听者集合。

  spring中将ApplicationContext当做事件产生方,使用applicationListeners作为监听者集合,applicationEventMulticaster用来做事件发布。

  事件发布的几个步骤:

  订阅:最初addApplicationListener将applicationListener加入监听者集合。

  发布:ApplicationContext继承了ApplicationEventPublisher,因而实现了publishEvent,该方法首先会遍历本applicationContext的applicationListeners集合,对每个listener调用onApplicationEvent,因此每个listener都会被通知到;这步完成后会对applicationContext的parent的所有applicationListeners做同样的事件发布。

  事件发布非常常用,不仅我们自己的应用可以使用这个事件发布,spring框架自身也在使用事件发布。下面是一些spring中事件的应用:

  在finishRefresh中会发布ContextRefreshedEvent表明refresh结束借此通知listener。在ApplicationContext中start和stop方法会发布事件表示context开始或结束。
  johnson将spring的主框架与运行流程创造完毕之后,发觉spring中提供了许多灵活扩展的地方。于是johnson准备在第三日将这些灵活扩展的用法公布出来。

  1. BeanPostProcessor。BeanPostProcessor提供了bean创建完成后的扩展接口,当你需要在bean创建完后对其做一定处理,则BeanPostProcessor是首选的方式。

  2. Aware。注入的bean需要了解其容器的某些部分,spring通过Aware完成回调,如BeanNameAware,可以让bean得知自己的名字, BeanFactoryAware可以让bean了解到BeanFactory, ApplicationContextAware,可以让bean操作ApplicationContext。通过这种方式,注入spring的bean能够做更加广泛的事情。

  对于BeanPostProcessor的扩展,spring自身有一个例子,即如何识别Aware bean的例子。Aware bean是比较特殊的bean,需要spring对其额外注入一些属性,那么注入的过程spring会怎么做呢?实际上spring并没有将他写在核心的处理过程里面,而是放到了ApplicationContextAwareProcessor这个BeanPostProcessor,通过BeanPostProcessor的postProcessBeforeInitialization最终invokeAwareInterfaces以判断该bean的类型并注入相应的属性。这种做法利用了BeanPostProcessor完成了另一个扩展用法,实在是高超。

private void invokeAwareInterfaces(Object bean) { 
    if (bean instanceof Aware) { 
      if (bean instanceof EnvironmentAware) { 
        ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); 
      } 
      if (bean instanceof EmbeddedValueResolverAware) { 
        ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver( 
            new EmbeddedValueResolver(this.applicationContext.getBeanFactory())); 
      } 
      if (bean instanceof ResourceLoaderAware) { 
        ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); 
      } 
      if (bean instanceof ApplicationEventPublisherAware) { 
        ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); 
      } 
      if (bean instanceof MessageSourceAware) { 
        ((MessageSourceAware) bean).setMessageSource(this.applicationContext); 
      } 
      if (bean instanceof ApplicationContextAware) { 
        ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); 
      } 
    } 
  } 

  关于Aware的用法则更多了,比如如下代码能够感知ApplicationContext,spring在创建完这个bean之后便会注入ApplicationContext,于是我们就可以使用该context完成事件发布。

public class HelloBean implements ApplicationContextAware {  
  
  private ApplicationContext applicationContext;  
  private String helloWord = "Hello!World!";  
  
  public void setApplicationContext(ApplicationContext context) {  
    this.applicatiOnContext= context;  
  }  
  
  public void setHelloWord(String helloWord) {  
    this.helloWord = helloWord;  
  }  
  
  public String getHelloWord() {  
    applicationContext.publishEvent(  
        new PropertyGettedEvent("[" + helloWord + "] is getted"));  
    return helloWord;  
  }  
}  

  3. BeanFactoryPostProcessor,这个PostProcessor通常是用来处理在BeanFactory创建后的扩展接口。一个例子如下,当注入这个bean以后,它便会在BeanFactory创建完毕自动打印注入的bean数量:

public class BeanCounter implements BeanFactoryPostProcessor{ 
 
  @Override 
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
      throws BeansException { 
    System.out.println(beanFactory.getBeanDefinitionCount()); 
  } 
   
} 

   4. FactoryBean。FactoryBean是一种特殊的bean,这种bean允许注入到spring容器并用其生成真正的bean,所以可以这样定义,FactoryBean本身是一种bean,这种bean又有能够提供bean的能力。下面从FactoryBean的调用开始,讲到spring是如何使用这个bean的。
   要想区分普通bean和FactoryBean,spring也必须有判断他们并特殊处理的过程,这个过程就在AbstractBeanFactory的getObjectForBeanInstance中

protected Object getObjectForBeanInstance( 
      Object beanInstance, String name, String beanName, RootBeanDefinition mbd) { 
 
    // Don't let calling code try to dereference the factory if the bean isn't a factory. 
    if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) { 
      throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass()); 
    } 
 
    // Now we have the bean instance, which may be a normal bean or a FactoryBean. 
    // If it's a FactoryBean, we use it to create a bean instance, unless the 
    // caller actually wants a reference to the factory. 
    if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) { 
      return beanInstance; 
    } 
 
    Object object = null; 
    if (mbd == null) { 
      object = getCachedObjectForFactoryBean(beanName); 
    } 
    if (object == null) { 
      // Return bean instance from factory. 
      FactoryBean<&#63;> factory = (FactoryBean<&#63;>) beanInstance; 
      // Caches object obtained from FactoryBean if it is a singleton. 
      if (mbd == null && containsBeanDefinition(beanName)) { 
        mbd = getMergedLocalBeanDefinition(beanName); 
      } 
      boolean synthetic = (mbd != null && mbd.isSynthetic()); 
      object = getObjectFromFactoryBean(factory, beanName, !synthetic); 
    } 
    return object; 
  } 

  可以看出来,如果是普通bean,就直接返回了,而如果是FactoryBean,最终调用会调用factory.getObject从而返回具体对象。如果将整个spring看做一个抽象工厂,生产抽象的bean时,则FactoryBean就是具体工厂,生产你需要的对象。
  spring中FactoryBean用法很多,举个比较常见的例子,集成hibernate的sessionFactory时一般会注入LocalSessionFactoryBean,但是这个sessionFactory实际上不是普通的bean,可以简单在配置文件中注入就能生产,它有很多定制的部分,于是spring让这个bean成为一个FactoryBean并控制其生产的对象。


 


推荐阅读
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • 用友深耕烟草行业25年,提出数字化转型建议
    本文介绍了用友在烟草行业深耕25年的经验,提出了数字化转型的建议,包括总体要求、主要任务、发展阶段和六位一体推进举措。通过数字化转型,烟草行业将注入新动能,实现高质量发展。 ... [详细]
  • Principle for Mac(交互式屏幕设计软件)免激活版
    Mac上好用的交互式屏幕设计软件,PrincipleforMac是一款交互式屏幕设计软件,principle mac让您的设计将以原则出现,随时为您注入新的活力。如果您进行更改,再 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 本文介绍了使用cacti监控mssql 2005运行资源情况的操作步骤,包括安装必要的工具和驱动,测试mssql的连接,配置监控脚本等。通过php连接mssql来获取SQL 2005性能计算器的值,实现对mssql的监控。详细的操作步骤和代码请参考附件。 ... [详细]
  • 本文讨论了如何在codeigniter中识别来自angularjs的请求,并提供了两种方法的代码示例。作者尝试了$this->input->is_ajax_request()和自定义函数is_ajax(),但都没有成功。最后,作者展示了一个ajax请求的示例代码。 ... [详细]
  • 突破MIUI14限制,自定义胶囊图标、大图标样式,支持任意APP
    本文介绍了如何突破MIUI14的限制,实现自定义胶囊图标和大图标样式,并支持任意APP。需要一定的动手能力和主题设计师账号权限或者会主题pojie。详细步骤包括应用包名获取、素材制作和封包获取等。 ... [详细]
  • 网络请求模块选择——axios框架的基本使用和封装
    本文介绍了选择网络请求模块axios的原因,以及axios框架的基本使用和封装方法。包括发送并发请求的演示,全局配置的设置,创建axios实例的方法,拦截器的使用,以及如何封装和请求响应劫持等内容。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • 本文介绍了在Mac上安装Xamarin并使用Windows上的VS开发iOS app的方法,包括所需的安装环境和软件,以及使用Xamarin.iOS进行开发的步骤。通过这种方法,即使没有Mac或者安装苹果系统,程序员们也能轻松开发iOS app。 ... [详细]
  • 本文讨论了如何使用Web.Config进行自定义配置节的配置转换。作者提到,他将msbuild设置为详细模式,但转换却忽略了带有替换转换的自定义部分的存在。 ... [详细]
  • 本文介绍了如何使用JSONObiect和Gson相关方法实现json数据与kotlin对象的相互转换。首先解释了JSON的概念和数据格式,然后详细介绍了相关API,包括JSONObject和Gson的使用方法。接着讲解了如何将json格式的字符串转换为kotlin对象或List,以及如何将kotlin对象转换为json字符串。最后提到了使用Map封装json对象的特殊情况。文章还对JSON和XML进行了比较,指出了JSON的优势和缺点。 ... [详细]
author-avatar
luhd88112010_254
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有