Spring概述
什么是Spring?
Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
你们项目为什么使用Spring框架?
这个问题我们直接回答其优点即可。
- 轻量:Spring是轻量级框架,基本版本大约只有2MB。
- 控制反转(IoC):Spring通过控制反转实现了松散耦合,使其具备高可维护性和拓展性。
- 面向切面编程(AOP):Spring支持面向切面编程,能够很方便地进行日志记录,数据库访问等。
- 容器:Spring包含并负责管理应用中对象的生命周期和配置,无需手动干预。
- 事务管理:Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。
- 异常处理:Spring 提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked 异常。
Spring的组成部分

重要模块如下:
- Spring Core:核心类库,提供IoC服务
- Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
- Spring Aspects:提供AspectJ 集成支持
- Spring AOP:提供面向切面编程实现
- Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;
- Spring ORM:对现有的ORM框架的支持;
- Spring Web:提供了基本的面向Web的综合特性,如:Web基本功能,WebSockets等。
- Spring MVC:提供面向Web应用的Model-View-Controller实现。
- Spring Test:集成测试模块。(Spring团队提倡测试驱动开发,即TDD)
依赖注入(DI)
什么是依赖注入?
依赖注入,即Dependency Inversion,全称是“依赖注入到容器”,通俗地讲,容器是某个对象,如果把某个类放入这个容器中,通过“依赖注入”可以解析出这个类的实例。
依赖注入的方式有哪些?
依赖注入的方式:
-
构造器注入:将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。
-
优点: 对象初始化完成后便可获得可使用的对象。
-
缺点: 当需要注入的对象很多时,构造器参数列表会变得很长,不够灵活。若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,使得代码难以维护。
-
-
setter方法注入:
IoC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。-
优点: 灵活,可以有选择性地注入需要的对象。
-
缺点: 由于依赖对象初始化完成后尚未注入被依赖对象,故还不能使用。
-
-
接口注入:依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入,该函数的参数就是要注入的对象。
-
优点:接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可。
-
缺点: 侵入性太强,破坏系统的封装性,不建议使用。
-
还有一种注入方式是注解注入,即使用@Autowired进行注入。严格地说,它不是一种新的注入方式,注解注入本质上还是Setter注入或构造函数注入。
控制反转(IoC)
什么是IoC
IoC(Inversion of Control)控制反转,是Spring Core最核心的部分,它是一种设计思想,不是具体的技术实现。IoC的思想就是将原本需要手动创建的对象交由Spring框架进行管理,Spring会根据容器的配置文件去创建实例和管理实例之间的依赖关系,对象与对象之间松散耦合,有利于功能的复用。简单来说,IoC让对象创建不用去new了,可以由Spring自动生产,并配合Java的反射机制,根据 配置文件在运行的时候动态去创建对象及管理对象,并调用对象方法。
在没有IoC之前,项目中的一个类可能依赖于很多其他不同的类,如果我们需要实例化这个类,我们需要搞清楚所有依赖类的构造函数,并逐一实例化,在添加或者修改类结构时,项目几乎无法维护。有了IoC之后,只需要配置好,并在需要的地方进行引用,这大大增加了可维护性。
依赖倒置原则、IoC、DI、IoC容器的关系
依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
关系如图所示:

Spring Bean
什么是Bean?
Spring bean是指被Spring IoC容器管理的对象。Bean是由Spring IoC容器进行实例化、组装和管理的对象。它是在程序运行时,通过反射机制生成的。
在使用时,我们需要编写配置文件告诉IoC容器帮助我们管理哪些对象,可以通过XML文件、注解或Java配置类进行配置。
BeanFactory
BeanFactory是Spring框架最底层的接口,它提供了IoC的配置机制,包含Bean的各种定义,便于实例化Bean。在实例化时,BeanFactory可用于建立Bean之间的依赖关系。
ApplicationContext
ApplicationContext,即应用上下文,继承自BeanFactory接口,它是Spring的一个更高级容器,提供了更丰富的功能:
- 国际化(MessageSource)
- 访问资源,如URL和文件(ResourceLoader)
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
- 消息发送、响应机制(ApplicationEventPublisher)
- AOP
BeanFactory和ApplicationContext的区别
-
加载方式不同:BeanFactory采用的是延迟加载(懒加载)的方式来注入Bean的。在启动的时候不会实例化Bean,只有在使用到某个Bean,即调用
getBean()时才会对该Bean进行实例化。ApplicationContext在启动时就会将所有Bean进行实例化。不过当Bean配置了lazy-init=true时,ApplicationContext也会延迟对该Bean的加载。 -
BeanFactory是Spring框架的基础设施,面向Spring,而ApplicationContext面向使用Spring框架的开发者,相比BeanFactory提供了更多面向实际应用的功能,几乎所有场合都能够直接使用ApplicationContext,而不是使用底层的BeanFactory。
Bean的作用域
Bean的作用域:
- singleton:唯一的bean实例,与单例模式相同,Spring中的bean默认都是单例的。
- prototype:每次请求都会创建一个新的bean实例。
- request:为每个http请求都创建一个新的bean,该bean仅在当前http request内有效
- session:每次来自新session的请求都创建一个新的bean,该bean仅在当前session内有效。
- global-session : 全局 session 作用域,仅仅在基于 portlet 的 web 应用中才有意义,Spring5 已经没有了。
Bean的生命周期
Bean生命周期:
创建bean
- 实例化Bean:对于
BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。 - 设置对象属性(依赖注入):实例化后的对象被封装在
BeanWrapper对象中,Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。 - 处理Aware接口:在这一步,Spring会检测对象是否实现了
xxxAware接口,并将相关的xxxAware实例注入给Bean:- 如果这个Bean已经实现了
BeanNameAware接口,则调用它所实现的setBeanName(String beanId)方法,此处传递的是Spring配置文件中Bean的id值。 - 如果这个Bean已经实现了
BeanFactoryAware接口,会调用它所实现的setBeanFactory()方法,传递的是BeanFactory自身。 - 如果这个Bean已经实现了
ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传递的是ApplicationContext;
- 如果这个Bean已经实现了
- BeanPostProcessor:如果想对Bean进行一些自定义的处理,可以让Bean实现
BeanPostProcessor接口,这样做会调用postProcessBeforeInitialization(Object obj, String s)方法。 - InitializingBean与init-method:如果Bean在Spring配置文件中配置了init-method属性,则会自动调用其配置的初始化方法。
- 如果这个Bean实现了
BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法。另外,由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术。
以上几个步骤执行完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
销毁bean
- DisposableBean:若Bean实现了
DisposableBean这个接口,会调用其实现的destroy()方法。 - destroy-method:若配置了destroy-method属性,则会调用其配置的销毁方法。
最后我通过一张图来总结Bean的生命周期。

@Component和@Bean的区别
@Component作用于类,而@Bean作用于方法@Component会自动扫描路径来自动装配Bean到Spring IoC中,也可以通过@ComponentScan自定义扫描路径。@Bean表示方法产生一个由Spring管理的bean。@Bean比@Component的自定义性更强,许多地方我们只能通过@Bean来对bean进行注册。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过@Bean来实现。
Spring AOP
谈谈你对AOP的理解
AOP(Aspect-Oriented Programming)面向切面编程,它能够将那些与业务无关,却为业务模块所共同调用的通用功能(例如事务处理、日志管理、权限控制等)封装起来,减少系统中的重复代码,降低模块间的耦合度,提高项目的拓展性和可维护性。
它的基本思想如下:AOP注重关注点分离,即不同的问题交给不同的部分去解决,目的是为了将业务功能的关注点和通用化功能的关注点进行分离。这些通用化功能(例如:事务处理、日志管理...)的代码实现就是所谓的切面(Aspect)。业务功能代码和切面代码分开后,架构将变得高内聚,低耦合。最后,切面需要最终被合并到业务中,确保功能的完整性,这就是织入(Weave)。
AOP的三种织入方式
- 编译时织入:需要特殊的Java编译器,如AspectJ。
- 类加载时织入:需要特殊的Java编译器,如AspectJ何AspectWerkz。
- 运行时织入:Spring AOP采用的方式,通过动态代理的方式,实现简单。
AOP中的重要概念
AOP,面向切面编程,即Aspect Oriented Programming。
- Aspect:切面,类似于Java中的类声明,是对系统中横跨多个类的关注点进行模块化封装
- Joinpoint:连接点,程序执行过程中的一个“点”,例如:方法的执行或异常的处理。在Spring AOP中,一个连接点总是代表一个方法的执行。
- Pointcut:切入点,即一组连接点的集合;
- Advice:通知,定义了将会织入到 Joinpoint 的具体逻辑,通过@Before、@After、@Around等注解来区别在JointPoint之前、之后等位置执行代码
- Weaving:织入,织入指的是将Advice连接到Pointcut指定的Joinpoint处的过程
- Interceptor:拦截器,是一种实现Advice(通知)的方式;
- Target:目标对象,即符合切入点所指定的条件被织入Advice的对象。
Advice的种类
- 前置通知(Before)
- 后置通知(AfterReturning)
- 异常通知(AfterThrowing)
- 最终通知(After)
- 环绕通知(Around)
AOP的实现
前置知识:设计模式-代理模式,可查看我的这篇文章:https://www.yeliheng.com/articles/dc912a27
在Java中,AOP有两种基于动态代理的实现方式,分别是JDK Proxy和cglib。具体使用哪种方式生成,由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认策略是:如果目标类是接口,则采用JDK Proxy来实现,否则使用cglib来生成代理。
JDK Proxy的核心是InvocationHandler接口和Proxy类,通过Java内部的反射机制实现。
cglib是通过修改字节码来实现代理的,底层基于ASM框架(ASM是一种字节码操作框架)。它以继承的方式动态生成目标类的代理,因此,如果某个类被final关键字修饰,则无法使用cglib来做动态代理。
反射机制在生成类的过程中比较高效,ASM在生成类之后的执行过程中比较高效。
Spring中的代理模式的实现
- 真实实现类的逻辑包含在
getBean()方法中。 getBean方法返回的实际上是Proxy的实例。Proxy实例是Spring采用JDK Proxy或cglib动态生成的。
注:getBean()方法用于查找或实例化容器中的Bean,这样是为什么Spring AOP只能作用于Spring容器中的Bean的原因。对于不是使用IoC容器管理的对象,Spring AOP是无能为力的。
Spring AOP的应用
请查看我的这篇文章:Spring AOP的最佳实践:在你的系统中记录用户的操作日志
Spring 事务
Spring管理事务的方式
- 编程式事务管理:在代码中通过编码的方式实现事务,例如通过
TransactionTemplate或TransactionManager手动管理事务,这种方式不推荐在项目中使用,实现复杂,会使得代码难以维护。 - 声明式事务管理:此方式意味着你可以将事务管理和业务代码分离,只需要使用注解或在Xml配置文件中进行配置。在项目中经常基于AOP实现,使用
@Transactional进行事务管理。
事务三要素是什么?
数据源:表示具体的事务性资源,是事务的真正处理者,如MySQL等。
事务管理器:像一个大管家,从整体上管理事务的处理过程,如打开、提交、回滚等。
事务应用和属性配置:像一个标识符,表明哪些方法要参与事务,如何参与事务,以及一些相关属性如隔离级别、超时时间等。
Spring 事务中有哪几种的事务传播行为?
事务传播行为是为了解决业务层方法之间互相调用的事务问题。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
Spring事务定义了以下7种传播行为:
-
PROPAGATION_REQUIRED:默认的Spring事务传播级别,若当前存在事务,则加入该事务,若不存在事务,则新建一个事务。 -
PAOPAGATION_REQUIRE_NEW:若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交。 -
PROPAGATION_NESTED:如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于PAOPAGATION_REQUIRE_NEW。 -
PROPAGATION_SUPPORTS:支持当前事务,若当前不存在事务,以非事务的方式执行。 -
PROPAGATION_NOT_SUPPORTED:以非事务的方式执行,若当前存在事务,则把当前事务挂起。 -
PROPAGATION_MANDATORY:强制事务执行,若当前不存在事务,则抛出异常。 -
PROPAGATION_NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
说说Spring事务的隔离级别
-
ISOLATION_DEFAULT:使用数据库默认的隔离级别。MySQL 默认采用的REPEATABLE_READ隔离级别,Oracle默认采用的READ_COMMITTED隔离级别。 -
ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它允许读取该事务尚未提交的数据变更。此隔离级别可能会产生脏读、幻读或不可重复读。 -
ISOLATION_READ_COMMITTED:保证一个事务修改的数据提交后才能够被另外一个事务读取。此隔离级别可以阻止脏读,但是幻读或不可重复读仍有可能发生。 -
ISOLATION_REPEATABLE_READ:它除了保证一个事务不能读取另外一个事务未提交的数据外,还保证对同一字段多次读取结果都是一致的,除非数据是被自身事务所修改。此隔离级别可以阻止脏读和不可重复读,但幻读仍有可能发生。 -
ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能,开销巨大,使用场景较少。
事务注解的本质是什么?
@Transactional这个注解仅仅是一些(和事务相关的)元数据,在运行时被事务基础设施读取消费,并使用这些元数据来配置bean的事务行为。 大致来说具有两方面功能:
- 表明该方法要参与事务。
- 配置相关属性来定制事务的参与方式和运行行为。
声明式事务主要得益于Spring AOP。使用一个事务拦截器,在方法调用的前后/周围进行事务性通知(Advice),来驱动事务完成。@Transactional注解既可以标注在类上,也可以标注在方法上。当在类上时,默认应用到类里的所有方法。如果此时方法上也标注了,则方法上的优先级高,另外注意方法必须是public的。