SpringMVC 面试题

12/28/2021 面试题JavaSpring

心灵鸡汤

有些路注定是要孤身一人走的,想要到达繁华,必经一段荒凉

# 一:循环依赖

Spring 如何解决循环依赖

# 二:静动态代理

静动态代理

# 三:Spring 中都用到了哪些设计模式?

设计模式

  1. 「工厂设计模式」:比如通过 BeanFactory 和 ApplicationContext 来生产 Bean 对象
  2. 「代理设计模式」:AOP 的实现方式就是通过代理来实现,Spring 主要是使用 JDK 动态代理 和 CGLIB 代理
  3. 「单例设计模式」:Spring依赖注入Bean实例默认是单例的。Spring的依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创建
  4. 「模板方法模式」:Spring 中 jdbcTemplate 等以 Template 结尾的对数据库操作的类,都会使用到模板方法设计模式,一些通用的功能
  5. 「装饰器模式」:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator
  6. 「观察者模式」:Spring 事件驱动模型观察者模式的。Spring中Observer模式常用的地方是listener的实现。如:ApplicationContextEvent、ApplicationListener
  7. 「适配器模式」:Spring AOP 的增强或通知(Advice)使用到了适配器模式
  8. 「策略模式」:Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源
      • UrlResource:访问网络资源的实现类
      • ClassPathResource:访问类加载路径里资源的实现类
      • FileSystemResource:访问文件系统里资源的实现类
      • ServletContextResource:访问相对于 ServletContext 路径里的资源的实现类
      • InputStreamResource:访问输入流资源的实现类
      • ByteArrayResource:访问字节数组资源的实现类

# 四:Spring 中有哪些核心模块?

核心模块

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

# 五:IOC

# 5.1 IOC 理解

IOC

首先 IOC 是一个 「容器」,用来装载对象的,它的核心思想就是 「控制反转」

那么究竟 ** 「什么是控制反转」?**

控制反转就是说,「把对象的控制权交给了 spring,由 spring 容器进行管理」,我们不进行任何操作

那么为 「什么需要控制反转」?

我们想象一下,没有控制反转的时候,我们需要 「自己去创建对象,配置对象」,还要 「人工去处理对象与对象之间的各种复杂的依赖关系」,当一个工程的量起来之后,这种关系的维护是非常令人头痛的,所以就有了控制反转这个概念,将对象的创建、配置等一系列操作交给 spring 去管理,我们在使用的时候只要去取就好了.

# 5.2 IOC 区别

spring 中的 IOC 容器有哪些?有什么区别?

spring 主要提供了 「两种 IOC 容器」,一种是 「BeanFactory」,还有一种是 「ApplicationContext」

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

# 5.3 BeanFactory 和 FactoryBean 区别

这两个是「不同的产物」

「BeanFactory 是 IOC 容器」,是用来承载对象的

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

# 六:DI

DI 就是依赖注入,其实和 IOC 大致相同,只不过是 「同一个概念使用了不同的角度去阐述」

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

spring 中有三种注入方式

  • 接口注入
  • 构造器注入
  • set注入

# 七:注解

# 7.1 分层

@Repository、@Service、@Compent、@Controller它们有什么区别?

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

  • @Repository:dao层

  • @Service:service层

  • @Controller:controller层

  • @Compent:其他不属于以上三层的统一使用该注解

# 7.2 注入

@Autowired 和 @Resource 有什么区别?

  • 「@Resource 是 Java 的注解」

@Resource 有两个属性是比较重要的,分别是 nametype;Spring 将 @Resource 注解的 name 属性为 bean 的名字,而 type 属性则解析为 bean 的类型。所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。如果既不指定 name 也不指定 type 属性,这时将通过反射机制使用 byName 自动注入策略。

  • 「@Autowired 是 Spring 的注解」

Spring 2.5 版本引入的,Autowired 只根据 type 进行注入,不会去匹配 name。如果涉及到 type 无法辨别注入对象时,那需要依赖 @Qualifier 或 @Primary 注解一起来修饰。

# 八:AOP

# 8.1 AOP 理解

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

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

# 8.2 AOP 分类

「AOP 实现主要分为两类:」

AOP分类

  • 「静态 AOP 实现」, AOP 框架**「在编译阶段」**对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ

  • 「动态 AOP 实现」, AOP 框架**「在运行阶段」**对动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP

spring 中 AOP 的实现是**「通过动态代理实现的」**,如果是实现了接口就会使用 JDK 动态代理,否则就使用 CGLIB 代理。

# 8.3 AOP 注解

注解

「有 5 种通知类型:」

  • 「@Before」:在目标方法调用前去通知

  • 「@AfterReturning」:在目标方法返回或异常后调用

  • 「@AfterThrowing」:在目标方法返回后调用

  • 「@After」:在目标方法异常后调用

  • 「@Around」:将目标方法封装起来,自己确定调用时机

# 8.4 AOP 区别

Spring AOP 和 AspectJ AOP 有什么区别?

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

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

  • 「编译时织入」:就是在编译字节码的时候织入相关代理类

  • 「编译后织入」:编译完初始类后发现需要 AOP 增强,然后织入相关代码

  • 「类加载时织入」:指在加载器加载类的时候织入

概述

主要区别 Spring AOP AspecjtJ AOP
增强方式 运行时增强 编译时增强
实现方式 动态代理 修改代码
编译器 javac 特殊的编译器 ajc
效率 较低(运行时反射损耗性能) 较高
织入方式 运行时 编译时、编译后、类加载时

# 九:Bean生命周期

Spring 中 Bean 的生命周期是怎样的?

概述

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

# 十:事务

# 10.1 事务实现方式

Spring 事务实现方式有哪些?

  1. 编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用 beginTransaction()、commit()、rollback() 等事务管理相关的方法,这就是编程式事务管理
  2. 基于 TransactionProxyFactoryBean 的声明式事务管理
  3. 基于 @Transactional 的声明式事务管理
  4. 基于 Aspectj AOP 配置事务

# 10.2 事务隔离理解

说一下 Spring 的事务隔离?

事务隔离级别指的是一个事务对数据的修改与另一个并行的事务的隔离程度,当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题:

  • 脏读:一个事务读到另一个事务未提交的更新数据。
  • 幻读:例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的 "全部数据行"。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入"一行新数据"。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样。
  • 不可重复读:比方说在同一个事务中先后执行两条一模一样的select语句,期间在此次事务中没有执行过任何DDL语句,但先后得到的结果不一致,这就是不可重复读。

# 10.3 事务隔离级别

Spring 事务隔离级别有哪些?

事务隔离级别

  1. DEFAULT:采用 DB 默认的事务隔离级别
  2. READ_UNCOMMITTED:读未提交
  3. READ_COMMITTED:读已提交
  4. REPEATABLE_READ:可重复读
  5. SERIALIZABLE:串行化

# 10.4 事务传播机制

Spring 事务的传播机制有哪些?

事务的传播机制

  1. 「propagation_required」

    • 当前方法**「必须在一个具有事务的上下文中运行」**,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚)
  2. 「propagation_supports」

    • 当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行
  3. 「propagation_mandatory」

    • 表示当前方法 「必须在一个事务中运行」,如果没有事务,将抛出异常
  4. 「propagation_nested」

    • 如果当前方法正有一个事务在运行中,则该方法应该**「运行在一个嵌套事务」**中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同propagation_required的一样
  5. 「propagation_never」

    • 当方法务不应该在一个事务中运行,如果**「存在一个事务,则抛出异常」**
  6. 「propagation_requires_new」

    • 当前方法**「必须运行在它自己的事务中」**。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。
  7. 「propagation_not_supported」

    • 方法不应该在一个事务中运行。「如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行」

# 十一:Spring MVC 和 Struts 区别

  • 拦截机制的不同

Struts2是类级别的拦截,每次请求就会创建一个Action,和Spring整合时Struts2的ActionBean注入作用域是原型模式prototype,然后通过setter,getter把request数据注入到属性。Struts2中,一个Action对应一个request,response上下文,在接收参数时,可以通过属性接收,这说明属性参数是让多个方法共享的。Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了,只能设计为多例

SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,所以方法直接基本上是独立的,独享request,response数据。而每个方法同时又何一个url对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果通过ModeMap返回给框架。在Spring整合时,SpringMVC的Controller Bean默认单例模式Singleton,所以默认对所有的请求,只会创建一个Controller,有应为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,需要添加@Scope注解修改

Struts2有自己的拦截Interceptor机制,SpringMVC这是用的是独立的Aop方式,这样导致Struts2的配置文件量还是比SpringMVC大。

  • 底层框架的不同

Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用Servlet实现。Filter在容器启动之后即初始化;服务停止以后坠毁,晚于Servlet。Servlet在是在调用时初始化,先于Filter调用,服务停止后销毁。

  • 性能方面

Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,SpringMVC实现了零配置,由于SpringMVC基于方法的拦截,有加载一次单例模式bean注入。所以,SpringMVC开发效率和性能高于Struts2。

  • 配置方面

spring MVC和Spring是无缝的。从项目的管理和安全上也比Struts2高。

# 十二:为什么要使用 Spring?

# 12.1 简介

  • 目的:解决企业应用开发的复杂性
  • 功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能
  • 范围:任何Java应用

简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

# 12.2 轻量

从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。

# 12.3 控制反转

Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。

# 12.4 面向切面

Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

# 12.5 容器

Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。

# 12.6 框架

Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。

所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。

# 十三:Spring 中的 bean 是线程安全的吗?

容器本身并没有提供Bean的线程安全策略,因此可以说spring容器中的Bean本身不具备线程安全的特性,但是具体还是要结合具体scope的Bean去研究。

# 十四:Spring bean 的作用域

当通过spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下5种作用域:

    • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例
    • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
    • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
    • session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
    • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效

其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。在这种情况下,Spring容器仅仅使用new关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。

如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

# 十五:Spring 自动装配 bean 有哪些方式?

Spring容器负责创建应用程序中的bean同时通过ID来协调这些对象之间的关系。作为开发人员,我们需要告诉Spring要创建哪些bean并且如何将其装配到一起。

spring中bean装配有两种方式:

    • 隐式的bean发现机制和自动装配
    • 在java代码或者XML中进行显示配置

当然这些方式也可以配合使用。

# 十六:Spring MVC 运行流程

具体详情查看 Spring MVC 执行流程

# 十七:@RequestMapping 的作用

RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

RequestMapping注解有六个属性,下面分成三类进行说明。

value, method:

    • value:指定请求的实际地址,指定的地址可以是URI Template 模式
    • method:指定请求的method类型, GET、POST、PUT、DELETE等

consumes,produces

    • consumes:指定处理请求的提交内容类型(Content-Type),例如application/json, text/html
    • produces:指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回

params,headers

    • params: 指定request中必须包含某些参数值是,才让该方法处理
    • headers:指定request中必须包含某些指定的header值,才能让该方法处理请求

# 十八:文章来源

最后更新: 2/9/2023, 7:29:11 PM