Spring专题2: DI,IOC 控制反转和依赖注入

时间:2023-03-08 22:13:03
Spring专题2: DI,IOC 控制反转和依赖注入
  • 合集目录
    • Spring专题2: DI,IOC 控制反转和依赖注入

https://docs.spring.io/spring/docs/2.5.x/reference/aop.html

https://docs.spring.io/spring/docs/2.5.x/reference/aop.html#aop-understanding-aop-proxies

Spring 框架中的核心组件只有三个:Core、Context 和 Beans. 它们构建起了整个 Spring 的骨骼架构. 没有它们就不可能有 AOP、Web 等上层的特性功能

Bean的作用域

singleton This scopes the bean definition to a single instance per Spring IoC container (default).

prototype This scopes a single bean definition to have any number of object instances.

request This scopes a bean definition to an HTTP request. Only valid in the context of a web-aware Spring ApplicationContext.

session This scopes a bean definition to an HTTP session. Only valid in the context of a web-aware Spring ApplicationContext.

global-session This scopes a bean definition to a global HTTP session. Only valid in the context of a web-aware Spring ApplicationContext.

怎么理解面向切面编程的切面?

讲解OOP与AOP的简单对比?

AOP的好处: 把和主业务无关的事情, 放到代码外面去做

AOP像OOP一样, 只是一种编程范式, AOP并没有规定说, 实现AOP协议的代码, 要用什么方式去实现.

Spring AOP就是基于动态代理的, 如果要代理的对象, 实现了某个接口, 那么Spring AOP会使用JDK Proxy

但是不是所有AOP的实现都是在运行时进行织入的, 因为这样效率太低了, 而且只能针对方法进行AOP, 无法针对构造函数、字段进行AOP. 可以在编译成class时就织入啊, 比如AspectJ

讲解Spring 框架中如何基于 AOP 实现的事务管理?

pring 为事务管理提供了丰富的功能支持. Spring 事务管理分为编码式和声明式的两种方式. 编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦. 声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多. 声明式事务有两种方式, 一种是在配置文件(xml)中做相关的事务规则声明, 另一种是基于@Transactional 注解的方式. 注释配置是目前流行的使用方式

在应用系统调用声明@Transactional 的目标方法时, Spring Framework 默认使用 AOP 代理, 在代码运行时生成一个代理对象, 根据@Transactional 的属性配置信息, 这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截, 在 TransactionInterceptor 拦截时, 会在在目标方法开始执行之前创建并加入事务, 并执行目标方法的逻辑, 最后根据执行情况是否出现异常, 利用抽象事务管理器AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务

谈谈对控制反转的设计思想的理解?

怎么理解 Spring IOC 容器?

IoC是 Ioc—Inversion of Control, 即“控制反转”, 是一种设计思想. 在Java开发中, Ioc意味着将你设计好的对象交给容器控制, 而不是传统的在你的对象内部直接控制.

传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象, 也就是正转;而反转则是由容器来帮忙创建及注入依赖对象

它能指导我们如何设计出松耦合、更优良的程序. 传统应用程序都是由我们在类内部主动创建依赖对象, 从而导致类与类之间高耦合, 难于测试;有了IoC容器后, 把创建和查找依赖对象的控制权交给了容器, 由容器进行注入组合对象, 所以对象与对象之间是 松散耦合, 这样也方便测试, 利于功能复用, 更重要的是使得程序的整个体系结构变得非常灵活

IoC 是通过Dependency Injection, 依赖注入来实现的. 比如对象A需要操作数据库, 以前我们总是要在A中自己编写代码来获得一个Connection对象, 有了 spring我们就只需要告诉spring, A中需要一个Connection, 至于这个Connection怎么构造, 何时构造, A不需要知道. 在系统运行时, spring会在适当的时候制造一个Connection, 注入到A当中, 这样就完成了对各个对象之间关系的控制. A需要依赖 Connection才能正常运行, 而这个Connection是由spring注入到A中的, 依赖注入的名字就这么来的.

那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射 reflection, 它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性, spring就是通过反射来实现注入的

Spring IOC 怎么管理 Bean 之间的依赖关系, 怎么避免循环依赖?

对Spring IOC 容器的依赖注入的理解?

Spring的循环依赖的理论依据其实是基于Java的引用传递, 当我们获取到对象的引用时, 对象的field或则属性是可以延后设置的, 但是构造器必须是在获取引用之前.

Spring的单例对象的初始化主要分为三步:

  1. createBeanInstance:实例化, 其实也就是调用对象的构造方法实例化对象
  2. populateBean:填充属性, 这一步主要是多bean的依赖属性进行填充
  3. initializeBean:调用spring xml中的init 方法.

从上面讲述的单例bean初始化步骤我们可以知道, 循环依赖主要发生在第一、第二部. 也就是构造器循环依赖和field循环依赖.

那么我们要解决循环引用也应该从初始化过程着手, 对于单例来说, 在Spring容器整个生命周期内, 有且只有一个对象, 所以很容易想到这个对象应该存在Cache中, Spring为了解决单例的循环依赖问题, 使用了三级缓存, 这三级缓存分别指

  • singletonFactories : 单例对象工厂的cache
  • earlySingletonObjects :提前暴光的单例对象的Cache
  • singletonObjects:单例对象的cache

Spring首先从一级缓存singletonObjects中获取. 如果获取不到, 并且对象正在创建中, 就再从二级缓存earlySingletonObjects中获取. 如果还是获取不到且允许singletonFactories通过getObject()获取, 就从三级缓存singletonFactory.getObject()(三级缓存)获取

所以setter和field的依赖可以解决, 但是Spring不能解决A的构造方法中依赖了B的实例对象, 同时B的构造方法中依赖了A的实例对象这类问题, 因为加入singletonFactories三级缓存的前提是执行了构造器, 所以构造器的循环依赖没法解决.

说说对Spring IOC 的单例模式和高级特性?

lazy-init

如果你不想让一个singleton bean在ApplicationContext实现初始化时被提前实例化, 那么可以将bean设置为延时实例化. 延时加载, 设置为lazy 的bean将不会在ApplicationContext启动时提前被实例化, 而是第一次向容器通过getBean索取bean时实例化的. 如果一个设置了立即加载的bean1, 引用了一个延时加载的bean2, 那么bean1在容器启动时被实例化, 而bean2 由于被bean1引用, 所以也被实例化, 这种情况也符合延时加载的bean在第一次调用时才被实例化的规则.

FactoryBean的实现

概念:工厂bean, 本质也是bean.

作用:产生其他bean实例提供一个工厂方法, 该方法用来返回其他bean实例.

@Autowire注解实现

BeanFactory 和 FactoryBean 有什么区别?

BeanFactory 是bean工厂, Spring IoC最顶层的接口, 作用是管理bean, 即定位、配置对象、实例化和建立对象之间的依赖, getObject()方法由实现类调用, 来创建bean实例化

FactoryBean 工厂bean, 作用是产生其它bean实例, 提供工厂方法, 返回其它bean实例, 一般Spring容器担任工厂角色

BeanFactory 和 ApplicationContext 又有什么不同?

BeanFactory

BeanFactory是spring中较原始的Factory, BeanFactory不支持spring插件例如: AOP, Web应用等功能

ApplicationContext

ApplicationContext是BeanFactory的子类, ApplicationContext基本上代替了BeanFactory的工作, 以一种更面向框架的工作方式以及对上下文进行分层和实现继承, 并在这个基础上对功能进行扩展

  • MessageSource, 提供国际化的消息访问
  • 资源访问(如URL和文件)
  • 事件传递
  • Bean的自动装配
  • 各种不同应用层的Context实现
  1. 如果使用ApplicationContext, 如果配置的bean是singleton, 那么不管你有没有或想不想用它, 它都会被实例化. 好处是可以预先加载, 坏处是浪费内存.
  2. BeanFactory, 当使用BeanFactory实例化对象时, 配置的bean不会马上被实例化, 而是等到你使用该bean的时候(getBean)才会被实例化. 好处是节约内存, 坏处是速度比较慢. 多用于移动设备的开发.
  3. 没有特殊要求的情况下, 应该使用ApplicationContext完成. 因为BeanFactory能完成的事情, ApplicationContext都能完成, 并且提供了更多接近现在开发的功能

Spring 在 Bean 创建过程中是如何解决循环依赖的?

pring中一共有三种循环依赖: 构造器循环依赖, Setter循环依赖, Prototype作用域的循环依赖.

对于这三种循环依赖, Spring并不是都解决的:

构造器循环依赖, 不能解决

Setter循环依赖, 可以解决

Prototype作用域的循环依赖, 不能解决

获取到对象的引用时, 对象的field或则属性是可以延后设置的, 但是构造器必须是在获取引用之前. Spring的单例对象的初始化主要分为三步:

  1. createBeanInstance:实例化, 其实也就是调用对象的构造方法实例化对象
  2. populateBean:填充属性, 这一步主要是多bean的依赖属性进行填充
  3. initializeBean:调用spring xml中的init 方法.

所以setter和field的依赖可以解决, 但是Spring不能解决A的构造方法中依赖了B的实例对象, 同时B的构造方法中依赖了A的实例对象这类问题, 因为加入singletonFactories三级缓存的前提是执行了构造器, 所以构造器的循环依赖没法解决.

谈谈Spring Bean 创建过程中的设计模式?

工厂设计模式: Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象

单例设计模式: Spring 中的 Bean 默认都是单例的

代理设计模式: Spring AOP 功能的实现

模板方法: Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类, 它们就使用到了模板模式

观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用

适配器模式: Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller

装饰者模式: Spring 中用到的包装器模式在类名上含有 Wrapper或者 Decorator. 这些类基本上都是动态地给一个对象添加一些额外的职责