《今天面试了吗》-Spring

前言

今每天气不错,我怀着自信的笑容来到某个大厂的研发中心,开启面试的一天。首先我不是毫无准备的,什么java并发,多线程,jvm,分布式,数据库都准备的妥妥的,没想到今天的面试的主题是spring。不过还好,我也准备了...门开了,走来一位拿着mac本,戴眼镜的年轻的小伙子,跟我差很少大吧。而后他示意我坐下,礼貌的说:“欢迎来咱们公司面试,今天咱们就聊聊spring吧”...前端

面试环节

  • 面试官:你说下什么是spring?java

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

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

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

  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)。依赖注入应用比较普遍。
    还有几个常见的问题:
  1. 谁依赖谁-固然是应用程序依赖于IOC容器。
  2. 为何须要依赖-应用程序须要IOC容器来提供对象须要的外部资源。
  3. 谁注入谁-很明显是IOC容器注入应用程序某个对象,应用程序依赖的对象
  4. 注入了什么-就是注入某个对象所须要的外部资源(包括对象、资源、常量数据)
  • 面试官:那IOC与new对象有什么区别吗面试

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

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

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

  • 面试官:spring管理这么多对象,确定须要一个容器吧。你能说下对IOC容器的理解吗?编程

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

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

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

  • 我:首先说下两个概念。

  1. Bean的概念:Bean就是由Spring容器初始化、装配及管理的对象,除此以外,bean就与应用程序中的其余对象没什么区别了。
  2. 元数据BeanDefinition:肯定如何实例化Bean、管理bean之间的依赖关系以及管理bean,这就须要配置元数据,在spring中由BeanDefinition表明。
  • 我:下面说下工做原理:
  1. 准备配置文件:配置文件中声明Bean定义也就是为Bean配置元数据。
  2. 由IOC容器进行解析元数据:IOC容器的Bean Reader读取并解析配置文件,根据定义生成BeanDefinition配置元数据对象,IOC容器根据BeanDefinition进行实例化、配置以及组装Bean。
  3. 实例化IOC容器:由客户端实例化容器,获取须要的Bean。
    下面举个例子:
@Test  
       public void testHelloWorld() {  
             //一、读取配置文件实例化一个IoC容器  
             ApplicationContext context = new ClassPathXmlApplicationContext("helloworld.xml");  
             //二、从容器中获取Bean,注意此处彻底“面向接口编程,而不是面向实现”  
              HelloApi helloApi = context.getBean("hello", HelloApi.class);  
              //三、执行业务逻辑  
              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来实现。
  3. ApplicationContext的初始化和BeanFactory有一个重大区别:BeanFactory在初始化容器时,并未实例化Bean,知道第一次访问某个Bean时才实例化Bean;而ApplicationContext则在初始化应用上下文时就实例化全部的单例Bean,所以ApplicationContext的初始化时间会比BeanFactory稍长一些。
  • 面试官:很好,看来对spring的IOC容器掌握的不错。那咱们来聊聊spring的aop,你说下你对spring aop的了解。
  • 我:Aop(面向切面编程)可以将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减小系统的重复代码,下降模块间的耦合度,并有利于将来的扩展性和可维护性。这里聚个日志处理的栗子:
日志处理方式 实现方式 优缺点
硬代码编写
处理代码相同,代码强耦合
抽离方法,代码复用
手动插入代码,代码强耦合
aop
横向的功能抽离出来造成一个独立的模块,低耦合
  • 面试官:那你知道spring aop的原理吗?

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

  • 我:关于动态代理的原理能够参考个人这篇文章:juejin.im/post/5cea01…

  • 面试官:那你知道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默认都是单例的。
  2. prototype:每次请求都会建立一个新的bean实例。
  3. request:每次HTTP请求都会产生一个新的Bean,该Bean仅在当前HTTP request内有效。
  4. session:每次HTTP请产生一个新的Bean,该Bean仅在当前HTTP session内有效。
  5. 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,采用如下注解能够实现:
  1. @Component:通用的注解,可标注任意类为spring组件。若是一个Bean不知道属于哪一个层,可使用@Component注解标注
  2. @Repository:对应持久层即Dao层,主要用于数据库的操做。
  3. @Service:对应服务层,主要涉及一些复杂的逻辑
  4. @Controller:对应Spring MVC控制层,主要用于接收用户请求并调用Service层返回数据给前端页面。
  • 面试官:那@Component和@Bean有什么区别呢?
  • 我:那我来总结下:
  1. 做用对象不一样:@Component做用于类,@Bean做用于方法。
  2. @Component一般是经过类路径扫描来自动侦测以及自动装配到Spring容器中(使用@ComponentScan注解定义要扫描的路径从中找出识别了须要装配的类自动装配到spring的Bean容器中)。@Bean注解一般是在标有该注解的方法中定义产生这个bean,@Bean告诉Spring这是某个类的实例,当我须要用它的时候还给我。
  3. @Bean注解比@Component注解的自定义性更强,并且不少地方只能经过@Bean注解来注册Bean,好比第三方库中的类。
  • 面试官:看来你对Spring的bean掌握的不错,那你能说下本身对于spring MVC的了解吗?
  • 我:谈到这个问题,不得不说下Model1和Model2这两个没有spring MVC的时代。
  1. Model1时代:整个Web应用几乎都是JSP页面组成,只用少许的JavaBean来处理数据库链接,访问等操做。这个模式下JSP既是控制层又是表现层。显而易见这种模式存在不少问题:好比讲控制层和表现层逻辑混杂在一块儿,致使代码重用率极低;先后端相互依赖,难以进行测试而且开发效率极低。
  2. Model2时代:学过Servlet的朋友应该了解“Java Bean(Model)+JSP(VIEW)+Servlet(Controller)”这种开发模式就是早期的JavaWeb开发模式。Model2模式下还存在不少问题,抽象和封装程度还远远不够,使用Model2进行开发时不可避免的会重复造轮子。
  • 我想了想,接着说:MVC是一种设计模式,Spring MVC是一款很优秀的MVC框架。Spring MVC能够帮助咱们进行更简洁的Web层的开发,而且它天生与Spring框架集成。Spring MVC下咱们通常把后端项目分为Service层(处理业务)、Dao层(数据库操做)、Entity层(实体类)、Controller层(控制层、返回数据给前端)。我画个Spring MVC的简单原理图:

  • 面试官:你能详细说下Spring MVC从接受请求到返回数据的整个流程吗?
  • (我心想:幸亏我还没忘)我:能够。这个流程虽然复杂,可是理解起来也不是很难。
  1. 客户端(浏览器)发送请求,直接请求到DispatcherServlet。
  2. DispatcherServlet根据请求细腻调用HandlerMapping,解析请求对应的Handler。
  3. 解析到对应的Handler(也就是Controller)后,开始由HandlerAdapter适配器处理。
  4. HandlerAdapter会根据Handler来调用真正的处理器来处理请求,并处理相应的业务逻辑。
  5. 处理器处理完业务后,会返回一个ModelAndView对象,Model是返回的数据对象,View是个逻辑上的View。
  6. ViewResolver会根据View查找实际的View。
  7. DispatcherServlet把返回的Model传给View(视图渲染)。
  8. 把View返回给请求者(浏览器)。
  • 面试官:你知道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:若是当前存在事务,则加入该事务;若是当前没有事务,则以非事务的方式继续运行。
  3. PROPAGATION_MANDATORY:若是当前存在事务,则加入该事务;若是当前没有事务,则抛出异常(mandatory:强制) 不支持当前事务的状况
  4. PROPAGATION_REQUIRES_NEW:建立一个新的事务,若是当前存在事务,则把当前事务挂起。
  5. PROPAGATION_NOT_SUPPORTED:以非事务的方式运行,若是当前存在事务,则把当前事务挂起。
  6. PROPAGATION_NEVER:以非事务的方式运行,若是当前存在事务,则抛出异常。 其余状况
  7. PROPAGATION_NESTED:若是当前存在事务,则建立一个事务做为当前事务的嵌套事务来运行;若是当前没有事务,则该取值等价于PROPAGATION_REQUIRED。
  • 我:一篇有价值的博客,列举了Spring事务不生效的几大缘由https://blog.csdn.net/f641385712/article/details/80445933。这里我列举一下:
  1. 缘由1:是不是数据库引擎设置不对形成的。好比咱们经常使用的mysql,引擎MyISAM,是不支持事务操做的,须要改为InnoDB才能支持。
  2. 缘由2:入口的方法必须是public,不然事务不起做用(这一点由Spring的AOP特性决定)private方法,final方法和static方法不能添加事务,加了也不生效。
  3. 缘由3:Spring事务管理默认只对出现运行时异常(kava.lang.RuntimeException及其子类)进行回滚(至于Spring为何这么设计:由于Spring认为Checked异常属于业务,程序员应该给出解决方案而不该该直接扔给框架)。
  4. 缘由4:@EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />。没有使用该注解开启事务。
  5. 缘由5:请确认你的类是否被代理了。(由于Spring的事务实现原理是AOP,只有经过代理对象调用方法才能被拦截,事务才能生效)。
  6. 缘由6:请确保你的业务和事务入口在同一个线程里,不然事务也是不生效的,好比下面的代码:
@Transactional
@Override
public void save(User user1, User user2) {
    new Thread(() -> {
          saveError(user1, user2);
          System.out.println(1 / 0);
    }).start();
}

复制代码
  1. 缘由7:同一个类中一个无事务的方法调用另外一个有事务的方法,事务是不会起做用的。例以下面的代码:

  • 面试官:看来你对spring框架的重点知识掌握的还不错,基础很扎实,今天咱们就先聊到这里,但愿明天你能表现的更好。
  • 我微笑着说:(暗自庆幸:经过了一关是一关)嗯,我会好好准备的。
相关文章
相关标签/搜索