Spring作为Java的中最广泛使用的框架,在各大业务系统中是举足轻重的框架。可以说Spring框架成就了Java的半壁江山。Java一直处于排行榜的龙头,Spring功不可没。
Spring的核心
Spring最核心的两个概念,就是IOC和AOP。
IOC,控制反转,也叫依赖注入。Spring并不会直接创建对象,而是把对象声明出来,并通过配置文件描述相关的服务属性,通过容器来实现对象组件的装配和管理。其原理是利用工厂模式和反射机制(BeanFactory)。
依赖注入的方式有构造器注入和setter注入。
- 构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其他类的依赖。
- Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入。
AOP,面向切面编程。允许我们通过自定义的横切点,将那些影响多个类的行为封装到可以重用的模块中,减少系统中的重复代码,降低了模块间的耦合度。比如日志输出,使用AOP就可以把日志输出语句封装一个可重用模块,在以声明的方式将他们放在类中,每次使用类就自动完成了日志输出;类似的应用场景有权限认证、事务处理。AOP的主要实现原理是动态代理,Spring支持两种动态代理方式:JDK动态代理和CGLIB动态代理。
- JDK动态代理只提供接口的代理,不并支持类的代理。核心是InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
- 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
类加载期织入:JavaAgent又叫做 Java 探针,可以在类加载前,修改.class字节码,将增强的代码织入到类中。有两种方式:Premain,Agentmain(JDK6)。Premain只能在类加载之前修改字节码,类加载之后无能为力。而Agentmain恰恰可以弥补这缺点。Agentmain 可以在类加载之后再次加载这个类。AspectJ是一个面向切面的框架(AOP框架)。利用 -javaagent 实现的类加载期切面织入(LTW: Load-Time Weaving)
编译期织入:AspectJ-Maven-Plugin是一个Maven编译插件,用于在编译时直接修改源代码或字节码,以集成切面逻辑。
SpringBean生命周期
- Spring对Bean进行实例化
- Spring将值和bean的引用注入到Bean对应的属性中
- 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBeanName()方法;
- 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
- 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
- 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()方法;
- 如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法。类似地,如果bean使用init-method声明了初始化方法,该方法也会被调用;
- 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessAfterInitialization()方法;
- 此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
- 如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。
Spring如何解决循环依赖问题?
Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存(singletonFactories)中进行存储。
singletonFactories 的工作机制是这样的(假设 AService 最终是一个代理对象):
当我们创建一个 AService 的时候,通过反射刚把原始的 AService 创建出来之后,先去判断当前一级缓存中是否存在当前 Bean,如果不存在,则:
- 首先向三级缓存中添加一条记录,记录的 key 就是当前 Bean 的 beanName,value 则是一个 Lambda 表达式 ObjectFactory,通过执行这个 Lambda 可以给当前 AService 生成代理对象。
- 然后如果二级缓存中存在当前 AService Bean,则移除掉。
现在继续去给 AService 各个属性赋值,结果发现 AService 需要 BService,然后就去创建 BService,创建 BService 的时候,发现 BService 又需要用到 AService,于是就先去一级缓存中查找是否有 AService,如果有,就使用,如果没有,则去二级缓存中查找是否有 AService,如果有,就使用,如果没有,则去三级缓存中找出来那个 ObjectFactory,然后执行这里的 getObject 方法,这个方法在执行的过程中,会去判断是否需要生成一个代理对象,如果需要就生成代理对象返回,如果不需要生成代理对象,则将原始对象返回即可。最后,把对象存入到二级缓存中以备下次使用,同时删除掉三级缓存中对应的数据。这样 AService 所依赖的 BService 就创建好了。
接下来继续去完善 AService,去执行各种后置的处理器,此时,有的后置处理器想给 AService 生成代理对象,发现 AService 已经是代理对象了,就不用生成了,直接用已有的代理对象去代替 AService 即可。
Spring事务管理
Spring 使用
PlatformTransactionManager 类来管理事务,根据不同的数据访问框架提供不同的实现。
类(Class) | 说明 |
PlatformTransactionManager | 事务管理器,负责管理事务的开始,提交和回滚。 |
TransactionDefinition | 事务的定义信息,包括隔离级别,传播,超时时间设置等。 |
TransactionStatus | 事务的运行时状态。 |
Spring 事务的隔离级别:默认(使用数据库的隔离级别),读未提交,读已提交,可重复读,串行化。这些概念跟mysql的事务差不多,这里就不详细说明了。
Spring 事务的传播行为:Spring事务传播机制规定了事务方法和事务方法发生嵌套调用时事务如何进行传递。
REQUIRED | 支持当前事务,如果没有事务会创建一个新的事务 |
SUPPORTS | 支持当前事务,如果没有事务的话以非事务方式执行 |
MANDATORY | 支持当前事务,如果没有事务抛出异常 |
REQUIRES_NEW | 创建一个新的事务并挂起当前事务 |
NOT_SUPPORTED | 以非事务方式执行,如果当前存在事务则将当前事务挂起 |
NEVER | 以非事务方式进行,如果存在事务则抛出异常 |
NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。 |
通常我们使用
PlatformTransactionManager的实现(
DataSourceTransactionManager)来支持JDBC 和 mybatis的使用。当我们遇到多数据源或者分布式时,我们可以使用JtaTransactionManager来支持分布式事务 JTA 的使用。其核心依赖实现了XADataSource、XAConnection等接口。也就是说其底层是利用了XA协议。