目录
IOC
核心思想:将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理,IOC最常见以及最合理的实现方式叫做依赖注入(DI)
DI(依赖注入):把底层类作为参数传入上层类,实现上层类对下层类的“控制”
实现流程:
- 定义 Bean:在 Spring IOC 中,所有的对象都被看作是 Bean,需要在配置文件或者使用注解的方式中进行定义和配置。
- 创建 Bean 工厂:在 Spring 中,Bean 工厂负责管理 Bean 的创建、组装和销毁等任务。Spring IOC 容器就是 Bean 工厂的一种实现。
- 读取配置文件:Spring IOC 容器会读取配置文件或者使用注解的方式来获取 Bean 的定义和配置信息。
- 创建 Bean 实例:Spring IOC 容器根据配置文件中的信息,使用反射技术来创建 Bean 实例,并将其保存在容器中。
- 组装 Bean:Spring IOC 容器根据配置文件中的信息,将不同的 Bean 实例组装起来,形成一个完整的应用程序。
- 注入依赖:Spring IOC 容器根据配置文件中的信息,自动为 Bean 注入依赖的对象或者值。
- 提供 Bean 实例:应用程序通过 Spring IOC 容器获取需要的 Bean 实例,从而使用其中的方法和属性等。
IOC优点:
- 只需要维护一个Configuration
- 创建实例时不需要了解其中的细节,它先从最上层开始往下找依赖关系,到达最底层之后再往上一步一步new
IOC实现方式
Spring 提供了两种容器类型来提供支持 IOC方式:
- BeanFactory: 基础类型的IOC容器,提供完整的IOC服务支持
- ApplicationContext: ApplicationContext是在 BeanFactory的基础之上构建的,是相对高级的容器实现
ApplicationContext除了拥有BeanFactory的所有支持,ApplicationContext提供了其他高级特性:
• MessageSource, 提供国际化的消息访问
• 资源访问,如URL和文件
• 事件传播
• 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的 web层
ApplicationContext 间接继承自 BeanFactory,ApplicationContext 和 BeanFactory的继承关系如下:
BeanFactory
BeanFactory 是基础类型IOC容器,提供完整的IOC服务支持。如果没有特殊指定,默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及依赖注入工作。因此,相对来说,容器启动初期的时候速度是比较快的。所需要的资源有限。所以,对资源有限,并且功能要求不是很严格的场景,BeanFactory是比较合适的IOC容器
BeanFactory实现方式
实际上,BeanFactory只是一个接口,它负责定义如何访问容器内管理的Bean的方法,各个BeanFactory的具体实现类负责具体Bean的注册以及管理工作。
BeanFactory有三个直接子类:
- ListableBeanFactory: 通过继承该接口可以列出所有的Bean,也可以只列出与预期类型相对应的bean
- HierarchicalBeanFactory: 支持分层bean的管理,使BeanFactory支持双亲IOC容器的管理功能
- AutowireCapableBeanFactory: 可以填充不受Spring 控制的 Bean
DefaultListableBeanFactory这个类其实就是 BeanFactory的默认实现类,一个比较通用的BeanFactory实现类,它除了间接实现 BeanFactory接口外,还实现了 BeanDefinitionRegistry接口,该接口才是BeanFactory实现中担任 Bean注册管理的角色,它抽象的定义了Bean注册的逻辑
ApplicationContext
ApplicationContext是在BeanFactory的基础上构建的,是相对比较高级的容器实现,ApplicationContext 容器启动之后,默认全部初始化并绑定完成,所以,对于BeanFactory来说,ApplicationContext 往往要求更多的系统资源,因此,ApplicationContext更适用于系统资源充足,并且要求更多功能的场景中。
ApplicationContext实现方式
Spring 为基本的 BeanFactory 类型容器提供了 XmlBeanFactory 实现(继承自DefaultListableBeanFactory),相应的,它也为 ApplicationContext 类型容器提供了以下几个常用的实现:
基于 XML的Spring项目:
- org.springframework.context.support.FileSystemXmlApplicationContext: 在默认情况下,从文件系统加载 bean 定义以及相关资源的 ApplicationContext 实现
- org.springframework.context.support.ClassPathXmlApplicationContext: 在默认情况下,从Classpath 加载bean 定义以及相关资源的 ApplicationContext 实现
- org.springframework.web.context.support.XmlWebApplicationContext: Spring提供的用于 Web 应用程序的 ApplicationContext 实现。
基于SpringBoot:
在官方文档中给出对于一个 SpringBoot 应用它对应的Context的情况:
- 对于 web应用,context 是 AnnotationConfigServletWebServerApplicationContext
- 对于 响应式应用,context 是 AnnotationConfigReactiveWebServerApplicationContext
- 对于普通非 web应用,context 是 AnnotationConfigApplicationContext
初始化过程
当创建一个ClassPathXmlApplicationContext时,构造方法做了两件事情:
- 1调用父容器的构造方法为容器设置好Bean资源加载器
- 2调用父类的setConfigLacations方法设置Bean配置信息的定位路径。
ClassPathXmlApplicationContext通过调用父类AbstractApplicationContext的refresh方法启动整个IOC容器对Bean定义的载入过程,refresh是一个模板方法,规定了IOC容器的启动流程。在创建IOC容器前如果已有容器存在,需要把已有的容器销毁,保证在refresh方法后使用新创建的IOC容器。
容器创建后通过LoadBeanDefinitions方法加载Bean配置资源,该方法做两件事:
- 1调用资源加载器的方法获取要加载的资源,
- 2真正执行加载的功能,由子类XmlBeanDefinitionReader实现,加载资源时首先解析配置文件路径,读取配置文件的内容,然后通过XML解析器将Bean配置信息转换成文档对象,之后按照Spring Bean的定义规则对文档进行解析。
Spring IOC容器中注册解析的Bean信息存放在一个HashMap集合中,key时字符集,只是BeanDefinition,注册过程中需要使用synchronized保证线程安全。当配置信息中配置的Bean被解析且被注册到IOC容器中后,初始化就算真正完成了,Bean定义信息已经可以使用且可被检索。
AOP
Spring AOP全称为Spring Aspect-Oriented Programming,即面向切面编程,实现方式:代理
- 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
- 动态代理:在程序运行时,运用反射机制动态创建而成。
Spring AOP使用动态代理技术在运行期间织入增强的代码,主要有两种代理机制:
- 基于JDK的动态代理
- 基于CGLIB的动态代理
JDK动态代理
- 通过实现InvocationHandler接口创建自己的调用处理器;
- 通过Java.lang.reflect.Proxy类指定ClassLoader对象和一组interface来创建动态代理;
- 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
- JDK动态代理是面向接口的代理模式,如果被代理目标没有接口,那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。
为什么JDK动态代理只能代理接口?
通过Proxy.newProxyInstance()方法实现,其需要传入被动态代理的一个接口类,
其底层在于,JDK动态代理会在程序运行期间动态生成一个代理类$Proxy0,其会继承java.lang.reflect.Proxy类,同时去实现被代理类的接口,每个动态代理类都会继承一个Proxy,Java只能单继承所以不能代理实现类,如果要去代理实现类的话,那么代理类不仅要继承Proxy,还要继承当前类,违反了多继承的原则
JDK动态代理可以代理实现类吗?
可以,Proxy这个类只是保存了动态代理的一个名为InvocationHandler处理器,可以不抽取出来,直接设置在$Proxy0中,这样就可以代理实现类
CGLIB动态代理
CGLIB是一个强大的高性能代码生成包,通过修改其字节码在运行时期(非编译时期)生成被代理对象的子类,并重写了被代理对象的所有方法,从而作为代理对象
局限性:代理类被final关键字所修饰,无法生成子类,也就无法代理
JDK动态代理与CGLIB动态代理的区别
- 实现方式:JDK 动态代理是通过反射实现的,而CGLIB动态代理是通过继承目标类来实现的。
- 目标类限制:JDK 动态代理要求目标类必须要实现接口,而CGLIB动态代理则没有这个限制。
- 性能:JDK 动态代理相对于 CGLIB 动态代理生成的代理类的效率会低一些。
- 对象类型:JDK 动态代理只能代理实现了接口的类,CGLIB 通过继承实现,不能代理 final 类。
- 依赖库:JDK 动态代理是 Java 自带的库,不需要额外的依赖,而 CGLIB 动态代理需要依赖 CGLIB 库。
- 如果要被代理的对象不是个实现类,那么Spring会强制使用CGLib来实现动态代理。
实现方式
相关注解
- @PointCut即在哪个地方进行切入,它可以指定某一个点,也可以指定多个点。
- @Advice 在切入点干什么,指定在PointCut地方做什么事情(增强),打日志、执行缓存、处理异常等等。
- @Aspect:声明被注解的类是一个切面的Bean;PointCut + Advice 形成了切面Aspect,这个概念本身即代表切面的所有元素。但到这一步并不是完整的,因为还不知道如何将切面植入到代码中,解决此问题的技术就是PROXY
- @Before:前置通知,指在某个连接点之前执行的通知;
- @Afrer:后置通知,指某个连接点退出时执行的通知(不论程序是正常返回还是异常退出)
- @AfterReturning:返回后通知,指某连接点正常完成之后执行的通知,返回值使用returning属性接收。
- @AfterThrowing:异常通知,指方法抛出异常导致退出时执行的通知,和@AfterReturning只会有一个执行,异常使用throwing属性接收。
- @Around:当外部调用代理对象的方法时候就会触发方法拦截器来做方法执行前后的增强逻辑。
- @Proxy 即代理,其不能算做AOP的家庭成员,更相当于一个管理部门,它管理 了AOP的如何融入OOP。之所以将其放在这里,是因为Aspect虽然是面向切面核心思想的重要组成部分,但其思想的践行者却是Proxy,也是实现AOP的难点与核心据在。
核心方法
AbstractAdvisorAutoProxyCreator类
AbstractAdvisorAutoProxyCreator实现了BeanPostProcessor,所以它本质上来说也是一个Spring的后置处理器,在Spring Bean实例化后,在初始化前后会经过这个后置处理器的加工,从而来实现AOP的代理生成。
大致过程是:AbstractAdvisorAutoProxyCreator通过BeanPostProcessor机制来加工初始化后的Bean,找到容器中所有的Advisor(切点+切面逻辑),然后判断这个Bean是否存在Advisor所匹配的切点,如果匹配就表示当前这个初始化的Bean需要代理,此时就会通过ProxyFactory选择具体的动态代理手段来构建代理对象。
执行优先级
如果开启了@EnableAspectJAutoProxy注解,并且proxyTargetClass属性为true,那么无条件使用CGLIB。如果需要代理的对象基于接口,且参数没有指定强制使用CGLIB则使用JDK的动态代理
@EnableAspectJAutoProxy
这个注解的核心原理就是往IOC容器中Import了一个AnnotationAwareAspectJAutoProxyCreator类的Bean实例。这个类继承自AbstractAdvisorAutoProxyCreator,所以它本质上也是一个BeanPostProcessor后置处理器。 AnnotationAwareAspectJAutoProxyCreator除了可以找到所有的Advisor类型的Bean,还可以找到@Aspect注解的Bean,并将其解析为Advisor对象,然后走AOP的逻辑生成代理对象。