Spring体系结构
1. IoC
Spring核心模块实现了IoC的功能,它将类与类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描述,由IoC容器负责依赖类之间的创建、拼接、管理、获取等工作。BeanFactoiy接口是Spring框架的核心接口,它实现了容器许多核心的功能。
Context模块构建于核心模块之上,扩展了BeanFactory的功能,添加了il8n国际化、Bean生命周期控制、框架事件体系、资源加载透明化等多项功能。
表达式语言模块是统一表达式语言(Unified EL)的一个扩展,该表达式语言用于查询和管理运行期的对象,支持设置/获取对象属性,调用对象方法,操作数组、集合等。 此外,该模块还提供了逻辑表达式运算、变量定义等功能,可以方便地通过表达式串和Spring IoC容器进行交互。
2.AOP
AOP是进行横切逻辑编程的思想,它开拓了考虑问题的思路。在AOP模块里,Spring提供了满足AOP Alliance 规范的实现,还整合了AspecJ这种AOP语言级的框架。
Spring Boot 概览
Spring Boot目的是用来简化Spring应用开发过程。该框架使用了特定的方式来进行配置,从而使得开发人员不再需要定义一系列样板化的配置文件,而专注于核心业务开发,项目涉及的一些基础设施则交由Spring Boot来解决。
Spring Boot像一个‘‘管家”,它会在后台“智能地”整合项目所需的第三方依赖类库或框架, 因此大部分基于Spring Boot的应用仅需要很少的配置就可以运行起来。
Spring Boot是由一系列启动器组成的,这些启动器构成一个强大的、灵活的开发助手。开发人员根据项目需要,选择并组合相应的启动器,就可以快速搭建一个适合项目需要的基础运行框架。
loC的概念
控制反转:即某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定,即由Spring容器借由Bean配置来进行控制。Spring支持构造函数注入和属性注入。
资源抽象接口
JDK所提供的访问资源的类(如java.net.URL、File等)并不能很好地满足各种底层资源的访问需求,比如缺少从类路径或者Web容器的上下文中获取资源的操作类。鉴于此,Spring设计了一个Resource接口,它为应用提供了更强的底层资源访问能力。该接口拥有对应不同资源类型的实现类。
父子容器
通过HierarchicalBeanFactory接口(ApplicationContext实现了该接口),Spring的IOC容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的Bean,但父容器不能访问子容器中的Bean。在容器内,Bean的id必须是唯一的,但子容器可以拥有一个和父容器id相同的Bean。父子容器层级体系增强了Spring容器架构的扩展性和灵活性,因为第三方可以通过编程的方式为一个己经存在的容器添加一个或多个特殊用途的子容器,以提供一些额外的功能。
Spring使用父子容器实现了很多功能,比如在Spring MVC中,展现层Bean位于一个子容器中,而业务层和持久层Bean位于父容器中。这样,展现层Bean就可以引用业务层和持久层Bean,而业务层和持久层Bean则看不到展现层Bean。
Bean的配置
Spring容器成功启动的三大要件分别是Bean定义信息、Bean实现类及Spring本身。Spring启动时读取应用程序提供的Bean配置信息,Bean配置信息是Bean的元数据信息,在Spring容器中的内部对应物是由一个个BeanDefinition形成的Bean注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,最后将这些准备就绪的Bean放到Bean缓存池中,为上层应用提供准备就绪的运行环境。
Spring支持多种形式的Bean配置方式。如果采用基于XML的配置,则Bean定义信息和Bean实现类本身是分离的;而如果采用基于注解的配置文件,则Bean定义信息通过在Bean实现类上标注注解实现。于Java类的配置方式和基于XML或基于注解的配置方式相比,前者通过代码编程的方式可以更加灵活地实现Bean的实例化及Bean之间的装配;后两者都是通过配置声明的方式,在灵活性上要稍逊一些,但在配置上要更简单一些。
循环依赖问题
Spring容器能对构造函数配置的Bean进行实例化有一个前提,即Bean构造函数入参引用的对象必须已经准备就绪。由于这个机制的限制,如果两个Bean都采用构造函数注入,而且都通过构造函数入参引用对方,就会发生类似于线程死锁的循环依赖问题,Spring容器将无法成功启动。将构造函数注入方式调整为属性注入方式就可以避免循环依赖。
自动装配
Spring IoC容器知道所有Bean的配置信息,此外,通过Java反射机制还可以获知实现类的结构信息。掌握所有Bean的这些信息后,Spring IoC容器就可以按照某种规则对容器中的Bean进行自动装配,而无须通过显式的方式进行依赖配置。自动装配可以根据名称或类型进行匹配。
<bean>之间的关系
注:在spring容器中等同于beanDefinition之间的关系。
继承
如果多个<bean>存在相同的配置信息,则Spring允许定义一个父<bean>,子<bean>将自动继承父<bean>的配置信息。
依赖
使用<ref>元素标签建立对其他Bean的依赖关系,Spring负责管理这些Bean的关系。当实例化一个Bean时,Spring保证该Bean所依赖的其他Bean己经初始化。
引用
假设一个<bean>要引用另一个<bean>的属性值,可以通过<idref>标签实现。
Bean作用域
除了以上5种预定义的Bean作用域外,Spring还允许用户自定义Bean的作用域。(通过BeanFactoryPost Processor注册自定义的Bean作用域)
singleton 作用域
一般情况下,无状态或者状态不可变的类适合使用单例模式,不过Spring对此实现了超越。在传统开发中,由于DAO类持有Connection这个非线程安全的变量,因此往往未采用单例模式。而在Spring环境下,对于所有的DAO类都可以采用单例模式,因为Spring利用AOP和LocalThread功能,实现了线程安全。因为Spring的这一超越,所以在实际应用中,大部分Bean都能以单实例的方式运行。
在默认情况下,Spring的ApplicationContext容器在启动时,自动实例化所有singleton 的Bean并缓存于容器中。虽然启动时会花费一些时间,但它带来两个好处:首先,对Bean提前进行实例化操作会及早发现一些潜在的配置问题;其次,Bean以缓存的方式保存,当运行时用到该Bean时就无须再实例化了,提高了运行的效率。如果用户不希望在容器启动时提前实例化singleton的Bean,则可以通过lazy-init属性进行控制。
prototype 作用域
在默认情况下,Spring容器在启动时不实例化prototype的Bean。此外,Spring容器将prototype的Bean交给调用者后,就不再管理它的生命周期。
作用域依赖问题
假设将Web相关作用域的Bean注入singleton或prototype的Bean中,需要使用Spring A0P的语法为注入的bean配置一个代理类,这样才能正确工作。
FactoryBean
在某些情况下,实例化Bean的过程比较复杂,如果按照传统的方式,则需要在<bean> 中提供大量的配置信息。Spring 为此提供了一个FactoryBean 工厂类接口,用户可以通过实现该工厂类接口定制实例化Bean的逻辑。
在该接口中共定义了3个接口方法:
T getObject():返回由FactoryBean创建的Bean 实例。
boolean isSingleton():确定由FactoryBean 创建的Bean 的作用域是singleton 还是prototype。
Class<?> getObjectType():返回FactoryBean 创建Bean 的类型。
当配置文件中<bean>class属性配置的实现类是FactoryBean时,通过getBean() 方法返回的不是FactoryBean本身,而是FactoryBean#getObject()方法所返回的对象,相当于FactoryBean#getObject()代理了getBean()方法。
如果用户希望获取FactoryBean的实例,则需要在使用getBean(beanName)方法时显式地在beanName 前加上“&” 前缀。
基于Java类的配置
JavaConfig是Spring的一个子项目,它旨在通过Java类的方式提供Bean的定义信息。普通的POJO只要标注@Configuration注解,就可以为Spring容器提供Bean定义的信息,每个标注了@Bean的类方法都相当于提供了一个Bean的定义信息,Bean的类型由方法返回值的类型决定,名称默认和方法名相同。由于@Configuration注解类本身己经标注了@Component注解,所以任何标注了©Configuration的类,本身也相当于标注了@Component,即它们可以像普通的Bean — 样被注入其他Bean中。
国际化信息
对于有国际化要求的应用系统,我们不能简单地采用硬编码的方式编写用户界面信息、报错信息等内容,而必须为这些需要国际化的信息进行特殊处理。简单来说,就是为每种语言提供一套相应的资源文件,并以规范化命名的方式保存在特定的目录中,由系统自动根据客户端语言选择适合的资源文件。“国际化信息”也称为“本地化信息”,一般需要两个条件才可以确定一个特定类型 的本地化信息,分别是“语言类型”和“国家/地区类型”。
MessageSource
Spring定义了访问国际化信息的MessageSource接口,并提供了若干个易用的实现类。ApplicationContext实现了MessageSource的接口。也就是说,ApplicationContext的实现类本身也是一个MessageSource 对象。Spring认为,在一般情况下,国际化信息资源应该是容器级的。一般不会将MessageSource作为一个Bean注入其他的Bean中,相反,MessageSource作为容器的基础设施向容器中所有的Bean开放。
容器启动过程时,initMessageSource()方法所执行的工作就是初始化容器中的国际化信息资源,它根据反射机制从BeanDefmitionRegistry中找出名为messageSource且类型为org.springframework.context.MessageSource 的Bean,将这个Bean 定义的信息资源加载为容器级的国际化信息资源。
容器事件
Spring的ApplicationContext能够发布事件并且允许注册相应的事件监听器,因此,它拥有一套完善的事件发布和监听机制。事件体系其实是观察者模式的一种具体实现方式。在事件体系中,除了事件和监听器外,还有另外3 个重要的概念。
事件源:事件的产生者,任何一个EventObject都必须拥有一个事件源。
事件监听器注册表:组件或框架的事件监听器不可能飘浮在空中,而必须有所依存。也就是说组件或框架必须提供一个地方保存事件监听器,这便是事件监听器注册表。一个事件监听器注册到组件或框架中,其实就是保存在事件监听器注册表中。当组件和框架中的事件源产生事件时,就会通知这些位于事件监听器注册表中的监听器。
事件广播器:它是事件和事件监听器沟通的桥梁,负责把事件通知给事件监听器。
Spring 在ApplicationContext 接口的抽象实现类AbstractApplicationContext 中完成了事件体系的搭建。AbstractApplicationContext 拥有一个applicationEventMulticaster 成员变量,applicationEventMulticaster 提供了容器监听器的注册表。AbstractApplicationContext 在refresh()这个容器启动方法中通过以下3个步骤搭建了事件的基础设施。
//⑤初始化应用上下文事件广播器 initApplicationEventMulticaster();
//⑦注册事件监听器 registerListeners();
//⑨完成刷新并发布容器刷新事件 finishRefresh();
AOP
一些概念
增强(Advice)
增强是织入目标类连接点上的一段程序代码。由于Spring只支持方法连接点,增强还包括在方法的哪一点加入横切代码的方位信息,所以增强既包含横切逻辑,又包含部分连接点的信息。
.引介(Introduction)
引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,也可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
Spring AOP通过Pointcut (切点)指定在哪些类的哪些方法上织入横切 逻辑,通过Advice (增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法 的两端等)。此外,Spring通过Advisor (切面)将Pointcut和Advice组装起来。有了Advisor的信息,Spring就可以利用JDK或CGLib动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了。
动态代理
Spring AOP使用动态代理技术在运行期织入增强的代码,Spring AOP使用了两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理。 JDK动态代理允许开发者在运行期创建接口的代理实例,而CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
在JDK动态代理中通过接口来实现方法拦截,所以必须确保要拦截的目标方法在接口中有定义,否则将无法实现拦截。
在CGLib动态代理中通过动态生成代理子类来实现方法拦截,所以必须确保要拦截的目标方法可被子类访问,也就是目标方法必须定义为非final,则非私有实例方法。
CGLib所创建的动态代理对象的性能比JDK所创建的动态代理对象的性能高不少(大概10倍)。但CGLib 在创建代理对象时所花费的时间却比JDK动态代理多(大概8倍)。对于singleton的代理对象或者具有实例池的代理,因为无须频繁地创建代理对象,所以比较适合采用CGLib动态代理技术;反之则适合采用JDK动态代理技术。
spring的声明式事务,拦截器的实现都是使用aop的动态代理来实现的。
@AspectJ
在Spring中定义一个切面是比较烦琐的,需要实现专门的接口,并进行一些较为复杂的配置。用户可以使用@AspectI注解或aop命名空间非常容易地定义一个切面,而不需要实现任何接口。
SpEL表达式
Spring动态语言(简称SpEL)是一个支持运行时查询和操作对象图的强大的动态语言。在Bean配置定义中,可以直接通过“#{}”编写SpEL表达式。
Spring Dao
统一的异常体系
Spring提供了一套和实现技术无关的、面向DAO层语义的异常体系,并通过转换器将不同持久化技术的异常转换成Spring的异常。
JDBC的异常转换器
传统的JDBC API在发生几乎所有的数据操作问题时都会抛出相同的SQLException, 它将异常的细节性信息封装在异常属性中。Spring根据错误码和SQL状态码信息将SQLException译成Spring DAO的异常体系所对应的异常。
统一数据访问模板
Spring为支持的持久化技术分别提供了模板访问的方式,降低了使用各种持久化技术的难度,因此可以大幅度地提高开发效率。Spring将这个相同的数据访问流程固化到模板类中,并将数据访问中固定和变化的部分分开,同时保证模板类是线程安全的,以便多个数据访问线程共享同一个模板实例。
数据连接泄露
使用Spring DAO的模板(如JdbcTemplate、HibernateTemplate等) 进行数据操作,则无须关注数据连接(Connection)及其衍生品(如Hibernate的Session等)的获取和释放操作,模板类己经通过其内部流程替我们完成了,且对开发者是透明的,一定不会存在数据连接泄露的问题。
参考:精通 Spring 4.x 企业应用开发实战