什么是Spring框架?
Spring框架指Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。
比如:Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
Spring官网列出的Spring的6个特征:
核心技术 :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。
测试 :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。
数据访问 :事务,DAO支持,JDBC,ORM,编组XML。
Web支持 : Spring MVC和Spring WebFlux Web框架。
集成 :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
语言 :Kotlin,Groovy,动态语言。
Spring的两大核心概念
IOC(控制翻转)
控制翻转,也叫依赖注入,他就是不会直接创建对象,只是把对象声明出来,在代码中不直接与对象和服务进行连接,但是在配置文件中描述了哪一项组件需要哪一项服务,容器将他们组建起来。在一般的IOC场景中容器创建了所有的对象,并设置了必要的属性将他们联系在一起,等到需要使用的时候才把他们声明出来,使用注解就更方便了,容器会自动根据注解把对象组合起来。
AOP(面对切面编程)
面对切面编程,这是一种编程模式,他允许程序员通过自定义的横切点进行模块化,将那些影响多个类的行为封装到可重用的模块中。 例子:比如日志输出,不使用AOP的话就需要把日志的输出语句放在所有类方法中,但是有了AOP就可以把日志输出语句封装一个可重用模块,在以声明的方式将他们放在类中,每次使用类就自动完成了日志输出。
IOC
什么是Spring IOC容器?
- 控制反转即IOC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
- Spring IOC 负责创建对象,管理对象,通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
控制反转(IOC)有什么作用
- 管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的
- 解耦,由容器去维护具体的对象
- 托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的
IOC的优点是什么?
- IOC 或 依赖注入把应用的代码量降到最低。
- 它使应用容易测试,单元测试不再需要单例和JNDI查找机制。
- 最小的代价和最小的侵入性使松散耦合得以实现。
- IOC容器支持加载服务时的饿汉式初始化和懒加载。
AOP
AOP(Aspect-Oriented Programming),一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
spring的事务传播机制?
七种类型
REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED
支持当前事务:
1、REQUIRED:
如果当前没有事务,则新建一个事务;当前有事务,加入到当前事务。
2、SUPPORTS: 当前有事务则加入,没有事务就以非事务的方式执行。
3、MANDATORY: 强制事务,使用当前的事务,如果没有事务则抛出异常。
不支持当前事务:
4、REQUIRES_NEW:
新建一个事务,如果当前存在事务,则挂起。
5、NOT_SUPPORTED: 以非事务的方式执行,如果当前存在事务,则挂起。
6、NEVER: 以非事务的方式执行,如果存在事务则抛出异常。
嵌套事务:
7、NESTED:
如果存在事务,则在嵌套事务内执行。
Spring中的bean生命周期?
Spring的循环依赖是什么?如何解决?
1、出现循环依赖的Bean必须要是单例
2、依赖注入的方式不能全是构造器注入的方式
bean A依赖bean B的同时B也依赖A。
Spring是如何解决的循环依赖?
Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。
当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。
当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:
第一步,先获取到三级缓存中的工厂;
第二步,调用对象工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。
当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!
- 一级缓存:为“Spring 的单例属性”而生,就是个单例池,用来存放已经初始化完成的单例 Bean;
- 二级缓存:为“解决 AOP”而生,存放的是半成品的 AOP 的单例 Bean;
- 三级缓存:为“打破循环”而生,存放的是生成半成品单例 Bean 的工厂方法。
假如A 需要进行AOP,因为代理对象每次都是生成不同的对象,如果干掉第二级缓存,只有第一、三级缓存:
- B 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A1。
- C 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A2。
看到问题没?你通过 A 的工厂的代理对象,生成了两个不同的对象 A1 和 A2,所以为了避免这种问题的出现,我们搞个二级缓存,把 A1 存下来,下次再获取时,直接从二级缓存获取,无需再生成新的代理对象。
所以“二级缓存”的目的是为了避免因为 AOP 创建多个对象,其中存储的是半成品的 AOP 的单例 bean。
说说自己对于Spring MVC的了解?
MVC是一种设计模式,Spring MVC是一款优秀的MVC框架。Spring MVC下我们一般把后端项目分为Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)。
Spring MVC 的简单原理图如下:
Spring MVC工作原理了解吗?
上图的一个笔误的小问题:Spring MVC的入口函数也就是前端控制器 DispatcherServlet 的作用是接收请求,响应结果。
流程说明(重要):
1、客户端(浏览器)发送请求,直接请求到DispatcherServlet(前端控制器)。
2、DispatcherServlet根据请求信息调用HandlerMapping(处理器映射器),解析请求对应的Handler。
3、解析到对应的Handler(也就是我们平常说的 Controller控制器)后,开始由HandlerAdapter适配器处理。
4、HandlerAdapter会根据 Handler来调用真正的处理器处理请求,并处理相应的业务逻辑。
5、处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是个逻辑上的 View。
6、ViewResolver会根据逻辑View查找实际的View。
7、DispaterServlet把返回的Model传给 View(视图渲染)。
8、把View返回给请求者(浏览器)
Spring框架中用到了哪些设计模式?
工厂设计模式:Spring使用工厂模式通过BeanFactory、ApplicationContext创建bean对象。
代理设计模式:Spring AOP功能的实现。
单例设计模式:Spring中的Bean默认都是单例的。
模板方法模式:Spring中jdbc Template、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、spring MVC中也是用到了适配器模式适配Controller。
列举一些重要的Spring模块?
Spring Core: 基础,可以说Spring其他所有的功能都需要依赖于该类库。主要提供IOC依赖注入功能。
Spring Aspects:该模块为与AspectJ的集成提供支持。
Spring AOP:AOP(Aspect Oriented Programming)面向切面编程。
Spring JDBC: Java数据库连接。
Spring JMS:Java消息服务。
Spring ORM: 用于支持Hibernate等ORM工具。
Spring Web: 为创建Web应用程序提供支持。
Spring Test: 提供了对JUnit和TestNG测试的支持。
谈谈自己对于Spring IoC和AOP的理解
IoC
IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理。IoC在其他语言中也有应用,并非Spirng特有。IoC容器是Spring用来实现IoC的载体,IoC容器实际上就是个Map(key,value),Map中存放的是各种对象。
将对象之间的相互依赖关系交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个Service类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个Service,你可能要每次都要搞清这个Service所有底层类的构造函数,这可能会把人逼疯。如果利用IOC的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
Spring时代我们一般通过XML文件来配置Bean,后来开发人员觉得XML文件来配置不太好,于是SpringBoot注解配置就慢慢开始流行起来。
AOP
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用JDK Proxy去进行代理了,这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理,如下图所示:
当然你也可以使用 AspectJ ,Spring AOP已经集成了AspectJ,AspectJ应该算的上是Java生态系统中最完整的 AOP框架了。
使用AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了AOP 。
Spring AOP和AspectJ AOP有什么区别?
Spring AOP属于运行时增强,而AspectJ是编译时增强。Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。
Spring AOP已经集成了AspectJ,AspectJ应该算的上是Java生态系统中最完整的AOP框架了。AspectJ相比于 Spring AOP功能更加强大,但是Spring AOP相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比Spring AOP快很多。
Spring中的bean的作用域有哪些?
singleton: 唯一bean实例,Spring中的bean默认都是单例的。
prototype: 每次请求都会创建一个新的bean实例。
request: 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
session: 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
global-session:全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与servlet不同,每个portlet都有不同的会话。
Spring中的单例bean的线程安全问题了解吗?
大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
常见的有两种解决办法:
在Bean对象中尽量避免定义可变的成员变量(不太现实)。
在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。
@Component和@Bean的区别是什么?
1、作用对象不同: @Component注解作用于类,而@Bean注解作用于方法。
2、@Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 @ComponentScan注解定义要扫描的路径从中找出标识了需要装配的类自动装配到Spring的bean容器中)。@Bean注解通常是我们在标有该注解的方法中定义产生这个bean,@Bean告诉了Spring这是某个类的示例,当我需要用它的时候还给我。
3、@Bean注解比Component注解的自定义性更强,而且很多地方我们只能通过@Bean注解来注册bean。比如当我们引用第三方库中的类需要装配到Spring容器时,则只能通过@Bean来实现。
将一个类声明为Spring的bean的注解有哪些?
我们一般使用 @Autowired 注解自动装配bean,要想把类标识成可用于Autowired注解自动装配的bean的类,采用以下注解可实现:
@Component:通用的注解,可标注任意类为Spring组件。如果一个Bean不知道属于哪个层,可以使用@Component注解标注。
@Repository:对应持久层即Dao层,主要用于数据库相关操作。
@Service: 对应服务层,主要涉及一些复杂的逻辑,需要用到Dao层。
@Controller: 对应Spring MVC控制层,主要用户接受用户请求并调用Service层返回数据给前端页面。
Spring 管理事务的方式有几种?
编程式事务,在代码中硬编码。(不推荐使用)
声明式事务,在配置文件中配置(推荐使用)
声明式事务又分为两种:
基于XML的声明式事务
基于注解的声明式事务
Spring事务中哪几种事务隔离级别?
TransactionDefinition接口中定义了五个表示隔离级别的常量:
1、TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql默认采用 REPEATABLE_READ隔离级别,Oracle默认采用的READ_COMMITTED隔离级别。
2、TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
3、TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
4、TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
5、TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能,通常情况下也不会用到该级别。