Spring

Spring中用到了哪些设计模式

  1. 工厂模式:比如通过BeanFactory和ApplictionContext来生产Bean对象。
  2. 代理模式:AOP的实现方式就是通过代理来实现的,Spring主要使用JDK动态代理和CGLIB代理。
  3. 单例模式:Spring中的Bean默认都是单例的。
  4. 模板模式:Spring中的jdbcTemplate等以Templage结尾的对数据库操作的类,都会使用到模板模式,一些通用的功能。
  5. 包装器模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  6. 观察者模式:Spring事件驱动模型是观察者模式的。
  7. 适配器模式:Spring AOP的增强或者通知Advice使用到了适配器模式。

Spring中有哪些核心模块

图片

  • Spring Core:Spring核心,它是框架最基础的部分,提供IoC和依赖注入DI特性。
  • Spring Context:Spring上下文容器。它是BeanFactory功能增强的一个子接口。
  • Spring Web:提供Web应用开发支持。
  • Spring MVC:针对Web应用中的MVC思想的实现。
  • Spring DAO:提供对JDBC的抽象层,简化了JDBC编码,同时,编码更具有健壮性。
  • Spring ORM:它支持用于流行的ORM框架的整合,比如Hibernate,iBatis,JDO等。
  • Spring AOP:面向切面编程,它提供了与AOP联盟兼容的编程实现。

IOC如何理解

首先IOC是一个容器,是用来装对象的,它的核心思想就是控制反转。所谓控制反转,就是将对象的控制权交给Spring容器来进行管理,我们不进行任何操作,需要使用对象的时候,只需要去Spring取就行了。

Spring中的IOC容器有哪些和它们的区别

  • BeanFactory
  • ApplicationContext

它们的区别在于,BeanFactory只提供了最基本的实例化对象和拿对象的功能,而ApplicationContext是继承了BeanFactory所派生出来的产物,是其子类,它的作用更加的强大,比如支持注解注入、国际化等功能。

BeanFactory和FactoryBean有什么区别

BeanFactory是一个IOC容器,是用来装载对象的。

FactoryBean是一个接口,为Bean提供了更加灵活的方式,通过代理一个Bean对象,对方法前后做一些操作。

@Repository,@Service,@Component,@Controller

这四个注解本质上都是一样,都是将该注解标识的对象放入Spring容器中,只是为了在使用上区分不同的应用层。

  • @Repository:DAO层
  • @Servie:Service层
  • Controller:Controller层
  • @Component:其他不属于以上三层的统一使用该注解

DI是什么

DI意为依赖注入,和IOC大致相同,只不过是同一个概念使用了不同的角度去阐述。

DI所描述和重点在于依赖。IOC的核心功能就是在于在程序运行时动态的向某个对象提供其他的依赖对象,而这个功能就是依赖DI去完成的。比如我们需要注入一个对象A,而A依赖于对象B,我们就需要将B注入到A中,这就是依赖注入。

Spring有三种注入方式:

  • 接口注入
  • 构造器注入
  • Set方法注入

AOP是什么

AOP意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP是OOP的延续,是Spring框架中一个重要的功能,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提供程序的可重用性,同时提高了开发的效率。

AOP主要分类动态AOP和静态AOP:

  • 静态AOP:AOP框架在编译阶段对程序源代码进行修改,生成静态的AOP代理类,比如AspectJ。
  • 动态AOP:AOP框架在运行时阶段对动态生成代理对象,比如SpringAOP,方式是在内存中使用JDK动态代理和CGLIB动态生成AOP代理类。

Spring中的AOP实现是通过动态代理实现的,如果是实现了接口就会使用JDK动态代理,否则就会使用CGLIB代理。Spring支持5种通知类型。

  • @Before:在目标方法调用前去通知
  • @AfterReturning:在目标方法返回或者异常后调用
  • @AfterThrowing:在目标方法返回后调用
  • @After:在目标方法异常后调用
  • @Around:将目标方法封装起来,自己确定调用的实时机

动态代理和静态代理的区别

静态代理:

  • 由程序员创建或者由特定工具自动生成原代理,再对其进行编译。在程序运行前,代理类的字节码文件就已经存在了。
  • 静态代理通常只代理一个类。
  • 静态代理事先知道要代理的是什么。

动态代理:

  • 在程序运行时,运用反射机制动态创建而成。
  • 动态代理是代理一个接口下的多个实现类。
  • 动态代理不知道要代理什么东西,只有在运行时才知道。

JDK动态代理和CGLIB代理区别

JDK动态代理业务类必须要实现某个接口,它时基于反射机制来实现的,生成一个实现同样接口的一个代理类,然后通过重写方法的方式,实现对代码的增强。

CGLIB动态代理是使用字节码处理框架ASM,其原理是通过字节码技术为一个类创建子类,然后重写父类的方法,实现对代码的增强。

Spring AOP和AspectJ AOP区别

Spring AOP是运行时增强,是通过动态代理实现的。

AspectJ AOP是编译时增强,需要特殊的编译器才可以完成,是通过修改代码来实现的,支持三种织入方式。

  • 编译时织入:就是在编译字节码的时候织入相关代理类。
  • 编译后织入:编译完初始类后发现需要AOP增强,然后织入相关代码。
  • 类加载时织入:指在类加载器加载类的时候织入。
主要区别 Spring AOP AspectJ AOP
增强方式 运行时增强 编译时增强
实现方式 动态代理 修改代码
编译器 javac 特殊的编译器ajc
效率 较低,反射损耗性能 较高
织入方式 运行时 编译时,编译后,类加载时

Spring中Bean的生命周期

大致分为四个阶段:

  • 实例化:实例化该Bean对象。
  • 填充属性:给该Bean赋值。
  • 初始化:
    • 如果实现了Aware接口,会通过其接口获取容器资源。
    • 如果实现了BeanPostProcessor接口,则会调用该接口的前置和后置处理增强。
    • 如果配置了init-method方法,会执行该方法。
  • 销毁
    • 如果实现了DisposableBean接口,则会回调该接口的destroy方法。
    • 如果配置了destroy-method方法,则会执行destroy-method配置的方法。

Spring是如何解决循环依赖的

循环依赖就是说两个对象相互依赖,形成一个环路。

Spring使用三级缓存来解决循环依赖,其核心逻辑就是把实例化和初始化的步骤分开,然后放入缓存中,供另一个对象调用。

  • 一级缓存:用来保存实例化,初始化都完成的对象。
  • 二级缓存:用来保存实例化,但是未完成初始化的对象。
  • 三级缓存:用来保存一个对象工厂,提供一个匿名的内部类,用于创建二级缓中的对象。

举个例子,如果A和B两个类发生循环引用,大致流程如下:

  1. A完成实例化后,去创建一个对象工厂,并放入三级缓存中。
    1. 如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象。
    2. 如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。
  2. A进行属性注入,去创建B
  3. B进行属性注入,需要A。则从三级缓存中去取A工厂代理对象并注入,然后删除三级缓存中的A工厂,将A对象放入二级缓存。
  4. B完成后续的属性注入,直到初始化完成,将B放入一级缓存。
  5. A从一级缓存中取到B并且注入B,直到完成后续的操作,将A从二级缓存删除,,并放入一级缓存,循环依赖结束。

Spring解决循环依赖有两个前提:

  • 不全是构造器注入方式,否则无法分离初始化和实例化操作。
  • 必须是单例,否则不能保证是同一个对象。

为什么要使用三级缓存,二级缓存不能解决吗

二级缓存也可以解决,三级缓存的光能是只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会通过这个工厂去真正创建对象。

如果使用了二级缓存,意味着所有Bean在实例化之后就要完成AOP代理,这样就违背了Spring设计的原则,Spring在设计之初就是在Bean的生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

@Autowired和@Resource

  • @Resource是Java自己的注解,@Resource有两个属性是比较重要的,分别是name和type,Spring将@Resource的name属性解析为bean的名字,而type属性解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,如果type属性,则使用byType自动注入策略,如果不指定,将通过反射机制使用byName自动注入策略。
  • @Autowired是Spring的注解,是Spring2.5之后的版本引入的,AutoWired只根据type进行注入,不会去匹配name,如果涉及到type无法辨别注入的对象时,需要依赖@Qulifier或者@Primary注解一起修饰。

Spring事务隔离级别

  • DEFAULT:采用DB默认的事务隔离级别
  • READ_UNCOMMITTED:读未提交
  • READ_COMMITTED:读已提交
  • REPREATABLE_READ:可重复读
  • SERIALIZABLE:串行化

Spring事务的传播机制

  • propagation_required:当前方法必须在一个具有事务的上下文中运行,如果客户端有事务在进行,那么被调用端在该事务中运行,否则的话重新开启一个事务。如果被调用端发生异常,那么调用端和被调用端的事务都将回滚。
  • propagation_supports:当前方法不必需要在一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行。
  • propagation_mandatory:表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常。
  • propagation_nested:如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,总之,内层事务并不影响外层事件。如果封装事务不存在,则同propagation_required的一样。
  • propagation_never:当前方法不应该在一个事务中运行,如果存在一个事务,则抛出异常。
  • propagation_requires_new:当前方法必须运行在自己的事务中,一个新的事务将被启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。
  • propagation_not_supported:方法不应该在一个事务中运行,如果有一个事务正在运行,它将在运行期被挂起,直到这个事务提交或者回滚才恢复执行。

SpringBoot自动装配原理

图片

  1. 容器在启动的时候会调用EnableAutoConfigurationImportSelector.class的selectImports方法获取一个全面的长的BeanConfiguration列表。
  2. 之后会读取spring-boot-autoconfigure.jar下面的spring.factories,获取到所有的Spring相关的Bean的全限定ClassName。
  3. 之后继续调用filter来进行筛选,过滤掉一些我们不需要和不符合条件的Bean。
  4. 最后把符合条件的BeanConfiguration注入默认的EnableConfigurationProperties类里面的属性值,并且注入到IOC环境当中。