Spring面试

Spring面试

面试官:你说下什么是Spring?

我:Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。我们一般说的Spring框架指的是Spring Framework,它是很多模块的集合,使用这些模块可以很方便的协助我们开发。这些模块是:核心容器、数据访问/集成、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container中的Core组件是Spring所有组件的核心,Beans组件和Context组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。

面试官:使用Spring框架有什么好处呢?

我:框架能更让我们高效的编程以及更方便的维护我们的系统。

  1. 轻量:Spring是轻量的,相对其他框架来说。
  2. 控制反转:Spring通过控制反转实现了松散耦合,对象给出他们的依赖,而不是创建或查找依赖的对象们。
  3. 面向切面编程(AOP):Spring支持面向切面编程,并且把业务逻辑和系统服务分开。
  4. 容器:Spring包含并管理应用中对象的生命周期和配置。
  5. MVC框架:Spring的WEB框架是个精心设计的框架,是WEB框架的一个很好的替代品。
  6. 事务管理:Spring提供一个持续的事务管理接口,提供声明式事务和编程式事务。
  7. 异常处理:Spring提供方便的API把具体技术相关的异常转化为一致的unchecked异常。

面试官:你第二点提到了Spring的控制反转,能解释下吗?

我:首先来解释下控制反转。控制反转(Inversion Of Control,缩写为IOC)是一个重要的面向对象编程的法则来削减程序的耦合问题,也是spring框架的核心。
应用控制反转,对象在被创建的时候,由一个调控系统内的所有对象的外界实体,将其所依赖的对象的引用,传递给它。
也可以说,依赖被注入到对象中。
所以,控制反转是关于一个对象如何获取他所依赖的对象的引用,这个责任的反转。
另外,控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。
依赖注入应用比较广泛。

面试官:那IOC与new对象有什么区别吗?

我:这就是正转与反转的区别。传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转。而反转则是容器来帮助我们创建并注入依赖对象。

面试官:好的,那IOC有什么优缺点吗?

我:优点:很明显,实现了组件之间的解耦,提高程序的灵活性和可维护性。缺点:对象生成因为是反射编程,在效率上有些损耗。但相对于IOC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高。

面试官:Spring管理这么多对象,肯定需要一个容器吧。你能说下对IOC容器的理解吗?

我:首先来解释下容器:在每个框架中都有个容器的概念,所谓的容器就是将常用的服务封装起来,然后用户只需要遵循一定的规则就可以达到统一、灵活、安全、方便和快速的目的。

我:然后IOC容器是具有依赖注入功能的容器,负责实例化、定位、配置应用程序中的对象以及建立这些对象间的依赖。

面试官:那你能说下IOC容器是怎么工作的吗?

我:两个概念。

  1. Bean的概念:
    Bean就是由Spring容器初始化、装配及管理的对象,除此之外,bean就与应用程序中的其他对象没什么区别了。

  2. 元数据BeanDefinition:

确定如何实例化Bean、管理bean之间的依赖关系以及管理bean,这就需要配置元数据,在spring中由BeanDefinition代表。

我:下面说下工作原理:
准备配置文件:配置文件中声明Bean定义也就是为Bean配置元数据。

  1. 由IOC容器进行解析元数据:
    IOC容器的Bean Reader读取并解析配置文件,根据定义生成BeanDefinition配置元数据对象,IOC容器根据BeanDefinition进行实例化、配置以及组装Bean。
  2. 实例化IOC容器:由客户端实例化容器,获取需要的Bean。
    下面举个例子:
@Test  
public void testHelloWorld() {
 //1、读取配置文件实例化一个IoC容器
 ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml");
 //2、从容器中获取Bean,注意此处完全“面向接口编程,而不是面向实现”
  HelloApi helloApi = context.getBean("hello", HelloApi.class);
  //3、执行业务逻辑
  helloApi.sayHello();
}

面试官:那你知道BeanFactory和ApplicationContext的区别吗?

我:
1.BeanFactory是Spring中最基础的接口。

它负责读取读取bean配置文档,管理bean的加载,实例化,维护bean之间的依赖关系,负责bean的生命周期。
2.ApplicationContext是BeanFactory的子接口,除了提供上述BeanFactory的所有功能外,还提供了更完整的框架功能:如国际化支持,资源访问,事件传递等。

常用的获取ApplicationContext的方法:

2.1 FileSystemXmlApplicationContext:

从文件系统或者url指定的xml配置文件创建,参数为配置文件名或者文件名数组。

2.2 ClassPathXmlApplicationContext:

从classpath的xml配置文件创建,可以从jar包中读取配置文件 2.3 WebApplicationContextUtils:

从web应用的根目录读取配置文件,需要先在web.xml中配置,可以配置监听器或者servlet来实现。

1.ApplicationContext的初始化和BeanFactory有一个重大区别:

BeanFactory在初始化容器时,并未实例化Bean,知道第一次访问某个Bean时才实例化Bean;

而ApplicationContext则在初始化应用上下文时就实例化所有的单例Bean,因此ApplicationContext的初始化时间会比BeanFactory稍长一些。更详细的可以看看这篇文章:面试题:说说 BeanFactory 和 FactoryBean 的区别

面试官:你说下你对Spring aop的了解。

我:
Aop(面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的扩展性和可维护性。
这里聚个日志处理的栗子:
在这里插入图片描述

面试官:那你知道Spring aop的原理吗?

我:Spring aop就是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring aop会使用jdk proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用jdk的动态代理,这时Spring aop会使用cglib动态代理,这时候Spring aop会使用cglib生成一个被代理对象的子类作为代理。

面试官:那你知道Spring Aop和AspecJ Aop有什么区别吗?

我:Spring AOP属于运行时增强,而AspectJ是编译时增强。Spring Aop基于代理,而AspectJ基于字节码操作。Spring Aop已经集成了AspectJ,AspectJ应该算得上Java生态系统中最完整的AOP框架了。AspectJ相对于Spring Aop功能更加强大,但是Spring AOP相对来说更简单。如果我们的切面比较少,那么两者性能差异不大。但是,当且切面太多的话,最好选择AspectJ,它比Spring Aop快很多。

面试官:你对Spring中的bean了解吗?都有哪些作用域?

我:Spring中的Bean有五种作用域:

  1. singleton:

唯一Bean实例,Spring中的Bean默认都是单例的。

  1. prototype:

每次请求都会创建一个新的bean实例。

  1. request:

每次HTTP请求都会产生一个新的Bean,该Bean仅在当前HTTP request内有效。

  1. session:

每次HTTP请产生一个新的Bean,该Bean仅在当前HTTP session内有效。

  1. global-session:

全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。

面试官:Spring中的单例Bean的线程安全问题了解吗?

我:大部分时候我们并没有在系统中使用多线程。

单例Bean存在线程安全问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
常见的有两种解决方法:

  1. 在Bean中尽量避免定义可变的成员变量(不太现实)。
  2. 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在Threadlocal中。

面试官:Spring中的Bean的生命周期你了解吗?

我心想,这个过程还挺复杂的,还好来之前小本本记了。
Spring中的Bean从创建到销毁大概会经过这些:

  1. Bean容器找到配置文件中Spring Bean的定义。
  2. Bean容器利用Java反射机制创建一个Bean的实例。
  3. 如果涉及一些属性值,利用set()方法设置一些属性值。
  4. 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名称。
  5. 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
  6. 如果Bean实现了BeanFactoryAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
  7. 与上面类似,如果实现了其他*.Aware接口,就调用相应的方法。
  8. 如果有和加载这个Bean的Spring容器相关的BeaPostProcessor对象,执行postProcessBeforeInitialization()方法
  9. 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法
  10. 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。
  11. 如果有和加载这个 Bean的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法
  12. 当要销毁Bean的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
  13. 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
    在这里插入图片描述

面试官:将一个类声明为Spring的Bean的注解有哪些你知道吗?

我:我们一般用@Autowried注解自动装配Bean,要想把类识别为可用于自动装配的Bean,采用以下注解可以实现:
@Component:通用的注解,可标注任意类为spring组件。
如果一个Bean不知道属于哪个层,可以使用@Component注解标注
@Repository:对应持久层即Dao层,主要用于数据库的操作。
@Service:对应服务层,主要涉及一些复杂的逻辑
@Controller:对应Spring MVC控制层,主要用于接收用户请求并调用Service层返回数据给前端页面。

面试官:那@Component和@Bean有什么区别呢?

我:那我来总结下:
作用对象不同:@Component作用于类,@Bean作用于方法。
@Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(使用@ComponentScan注解定义要扫描的路径从中找出识别了需要装配的类自动装配到spring的Bean容器中)。
@Bean注解通常是在标有该注解的方法中定义产生这个bean,@Bean告诉Spring这是某个类的实例,当我需要用它的时候还给我。
@Bean注解比@Component注解的自定义性更强,而且很多地方只能通过@Bean注解来注册Bean,比如第三方库中的类。

面试官:你能详细说下Spring MVC从接受请求到返回数据的整个流程吗?

(1)用户发送请求至前端控制器DispatcherServlet;
(2)DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handler;
(3)处理器映射器根据请求url找到具体的处理器Handler,生成处理器对象及处理器拦截器(如果有则生成),一并返回给DispatcherServlet;
(4)DispatcherServlet 调用 HandlerAdapter处理器适配器,请求执行Handler;
(5)HandlerAdapter 经过适配调用 具体处理器进行处理业务逻辑;
(6)Handler执行完成返回ModelAndView;
(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
(9)ViewResolver解析后返回具体View;
(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet响应用户。
在这里插入图片描述

面试官:你知道Spring框架中用到了哪些设计模式吗?

我:那我来总结一下。

  1. 工厂设计模式:Spring使用工厂模式通过BeanFactory、ApplicationContext创建Bean对象。
  2. 代理设计模式:Spring AOP功能的实现。
  3. 单例设计模式:Spring中的Bean默认都是单例的。
  4. 模板方法模式:Spring中jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,就是用到了模板模式。
  5. 包装器设计模式:我们的项目需要链接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。
    这种模式让我们可以根据客户需求都太切换不同的数据源。
  6. 观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
  7. 适配器模式:Spring AOP的增强或通知使用到了适配器模式。
    SpringMVC中也是用到了适配器模式适配Controller。

面试官:你使用过Spring的事务吗?是怎么用的?

我:当然用过。Spring管理事务有两种方式:

  1. 编程式事务:在代码中硬编码(不推荐使用)
  2. 声明式事务:在配置文件中配置,声明式事务又分为两种:
    基于XML的方式和基于注解的方式(推荐使用) 在项目中使用Spring的事务只需要在你需要事务的方法上加上@Transaction注解,那么这个方法就加上了事务,如果遇到异常,整个方法中的数据修改的逻辑都会被回滚掉,避免造成数据的不一致性。

面试官:那Spring的事务有哪几种隔离级别?

我:TransactionDefinition接口中定义了五个隔离级别的常量:

1.ISOLATION_DEFAULT:

使用后端数据库默认的隔离级别(一般用这个就好了),MySQL默认采用的是REPEATABLE_READ隔离级别,Oracle默认采用的是READ_COMMITTED隔离级别。
2. ISOLATION_READ_UNCOMMITTED:

最低的隔离级别,允许读取尚未提交的数据,可能导致脏读、幻读或不可重复读。
3. ISOLATION_READ_COMMITTED:

允许读取并发事务以及提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
4. ISOLATION_REPEATABLE_READ:

对同一字段的多次读取结果都是一致的,除非数据是被事务自己修改的,可以阻止脏读和不可重复读,但幻读仍有可能发生。
5. ISOLATION_SERIALIZABLE:

最高的隔离级别,所有事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读和幻读。

但是这将严重影响程序性能,通常也不会用到。

面试官:那你知道Spring事务有哪几种事务传播行为吗?

(面试官露出神秘的一笑)
(我心想:Spring事务中这里的坑踩的最多,怎么会不清楚呢)
在TransactionDefinition中定义了7种事务传播行为:

支持当前事务的情况

  1. PROPAGATION_REQUIRED:
    如果当前存在事务,则加入该事务;
    如果当前没有事务,则创建一个新的事务。

  2. PROPAGATION_SUPPORTS:

如果当前存在事务,则加入该事务;
如果当前没有事务,则以非事务的方式继续运行。

  1. PROPAGATION_MANDATORY:

如果当前存在事务,则加入该事务;
如果当前没有事务,则抛出异常(mandatory:强制) 不支持当前事务的情况

  1. PROPAGATION_REQUIRES_NEW:

创建一个新的事务,如果当前存在事务,则把当前事务挂起。

  1. PROPAGATION_NOT_SUPPORTED:

以非事务的方式运行,如果当前存在事务,则把当前事务挂起。

  1. PROPAGATION_NEVER:

以非事务的方式运行,如果当前存在事务,则抛出异常。

其他情况
7. PROPAGATION_NESTED:

如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;

如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。
我:
一篇有价值的博客,列举了Spring事务不生效的几大原因
https://blog.youkuaiyun.com/f641385712/article/details/80445933。
这里我列举一下:

原因1:是否是数据库引擎设置不对造成的。
比如我们常用的mysql,引擎MyISAM,是不支持事务操作的,需要改成InnoDB才能支持。
原因2:入口的方法必须是public,否则事务不起作用(这一点由Spring的AOP特性决定)private方法,final方法和static方法不能添加事务,加了也不生效。

原因3:Spring事务管理默认只对出现运行时异常(kava.lang.RuntimeException及其子类)进行回滚(至于Spring为什么这么设计:

因为Spring认为Checked异常属于业务,程序员应该给出解决方案而不应该直接扔给框架)。

原因4:@EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />。

没有使用该注解开启事务。

原因5:请确认你的类是否被代理了。
(因为Spring的事务实现原理是AOP,只有通过代理对象调用方法才能被拦截,事务才能生效)。

原因6:请确保你的业务和事务入口在同一个线程里,否则事务也是不生效的,比如下面的代码:

@Transactional
@Override
public void save(User user1, User user2) {
    new Thread(() -> {
          saveError(user1, user2);
          System.out.println(1 / 0);
    }).start();
}

原因7:同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的。
例如下面的代码:
在这里插入图片描述
转载:https://mp.weixin.qq.com/s/qayHa5pkL0Fmhza5uG6sWQ

### 常见Spring面试问题及解答 #### 什么是Spring Boot?其主要特点是什么? Spring Boot是Spring开源组织下的子项目,作为Spring组件的一站式解决方案,旨在简化使用Spring的难度。它通过减少繁重的手动配置需求并提供多种启动器使开发者能够更迅速地上手新项目[^1]。 ```java @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 此代码片段展示了如何创建一个简单的Spring Boot应用程序入口点。仅需少量代码即可启动整个应用框架,体现了Spring Boot降低复杂度的设计理念。 #### Spring容器中的Bean是否具备线程安全性? Spring容器本身并未为Bean内置任何特定的线程安全机制;因此,默认情况下这些对象并不保证线程安全。不过,实际线程行为会受到所选作用域的影响——例如单例模式下共享实例可能需要额外同步措施而原型模式每次请求都会生成新的实例从而自然规避了一些并发访问带来的风险[^2]。 #### 自动装配的方式有哪些? 一种常见的自动装配方式是由名称匹配完成注入过程(`byName`)。当某个属性的名字正好对应另一个定义好的bean名字时,则可以实现自动化关联操作而不必显式指定依赖关系[^3]。 ```xml <bean id="exampleService" class="com.example.ExampleServiceImpl"/> <!-- 如果存在名为 exampleService 的property 将会被自动装配 --> <bean name="clientComponent"> <!-- ... --> </bean> ``` 上述XML配置文件片断演示了基于名称(`byName`)的自动装配方法,在这里假设有一个叫做 `exampleService` 的服务类以及客户端组件将会尝试寻找相同命名的服务来进行绑定工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值