Spring面试题

Spring面试题

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

1.谈谈你对AOP的理解

​ AOP:将程序中的交叉业务逻辑(⽐如安全,⽇志,事务等),封装成⼀个切⾯,然后注⼊到⽬标对象 (具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进⾏增强,⽐如对象中的⽅法进⾏增 强,可以在执⾏某个⽅法之前额外的做⼀些事情,在某个⽅法执⾏之后额外的做⼀些事情。

2.AOP 的代理有哪⼏种⽅式

​ AOP 思想的实现⼀般都是基于代理模式 ,在 Java 中⼀般采⽤ JDK 动态代理模式,但是我们都知道,JDK 动态代理 模式只能代理接⼝⽽不能代理类。因此,Spring AOP 会按照下⾯两种情况进⾏切换,因为 Spring AOP 同时⽀持 CGLIB、ASPECTJ、JDK 动态代理。

  1. 如果⽬标对象的实现类实现了接⼝,Spring AOP 将会采⽤ JDK 动态代理来⽣成 AOP 代理类;
  2. 如果⽬标对象的实现类没有实现接⼝,Spring AOP 将会采⽤ CGLIB 来⽣成 AOP 代理类。不过这个选择过程 对开发者完全透明、开发者也⽆需关⼼。

3.请介绍一下IOC和DI

​ IOC名称为控制反转DI为依赖注入,虽然2个名称不一样但是他们是一个事情,因为ioc是一个过程而不是某一个点。在该过程包含了程序员在xml中做了一个bean的配解,配置之后会有Spring做xml解析,解析后会通过反射来获取到该类的对象,然后把该对象存储到全局的map属性中,map存储的是整个容器的相关bean信息,如果整个类在实例化的时候里面包含另外一个bean,spring就会把其他bean注入进来,这就是iOC也成DI,在注入的时候处理用构造方法之外还有工厂、set方法。

4.BeanFactory和ApplicationContext的区别总结

ApplicationContext:

ApplicationContext是BeanFactory的子接口

  • 支持国际化
  • 统一的资源文件访问方式
  • 提供在监听器中注册bean的事件
  • 同时加载多个配置文件
  • 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层

BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

以下是三种较常见的 ApplicationContext 实现方式:

1、ClassPathXmlApplicationContext:从classpath的XML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得

ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);

2、FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。

ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);

3、XmlWebApplicationContext:由Web应用的XML文件读取上下文。

5.Spring中的Bean是什么?

1.bean是对象,一个或者多个不限定
2.bean由Spring中一个叫IoC的容器管理
3.我们的应用程序由一个个bean构成

6.简单介绍一下 Spring bean 的生命周期

Spring中⼀个Bean的创建⼤概分为以下⼏个步骤:

  • Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化(构造方法)
  • Bean实例化后对将Bean的引入和值注入到Bean的属性中(set和get方法)
  • 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
  • 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
  • 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
  • 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
  • 如果Bean 实现了Initial izingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
  • 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
  • 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
  • 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YEPWSIpL-1663400102678)(assets/image-20220915201237944.png)]

7.Spring中Bean的作用域

1、singleton单例,默认的作用域,不管是否使用都怪你创建出来

2、prototype原型,每次使用(注入或调用getBean()方法都会new一个新的对象,一旦被使用或者注入,spring就不再只有Bean的引用,清楚bean并释放资源是调用者的职责;

3、request:请求作用域,不同得HTTP请求将会产生对应不同得bean,并且该bean实例只能在对应得HttpRequest中有效,其他作用域无效,仅适用于WebApplicationContext环境

4、session:会话作用域,每次一次新的会话都会产生一个新的对象,不同得HTTP请求将会产生对应不同得bean,并且该bean实例只能在对应得HttpSession中有效,其他作用域无效

5、globalSession:global session为整个HTTP请求中,在作用域方面就是application;

8.Spring框架中的单例Bean是线程安全的么?

答: 不是安全的。

Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。
注:单例bean是指IOC容器中就只有这么一个bean,是全局共享的,有多少个线程来访问用的都是这个bean。

如果Bean是有状态的,那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作
用域 把 "singleton"改为’‘protopyte’ 这样每次请求Bean就相当于是 new Bean() 这样就可以保证线程的
安全了。

有状态就是有数据存储功能。比如:一个Service里有个count的变量计数。
无状态就是不会保存数据
controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。

9.Spring事务的实现方式和原理以及隔离级别?

在使⽤Spring框架时,可以有两种使⽤事务的⽅式,⼀种是编程式的,⼀种是申明式的,

@Transactional注解就是申明式的。

原理:
⾸先,事务这个概念是数据库层⾯的,Spring只是基于数据库中的事务进⾏了扩展,以及提供了⼀些能让程序员更加⽅便操作事务的⽅式。
⽐如我们可以通过在某个⽅法上增加@Transactional注解,就可以开启事务,这个⽅法中所有的sql都会在⼀个事务中执⾏,统⼀成功或失败。
在⼀个⽅法上加了@Transactional注解后,Spring会基于这个类⽣成⼀个代理对象,会将这个代理对象作为bean,当在使⽤这个代理对象的⽅法时,如果这个⽅法上存在@Transactional注解,那么代理逻辑会先把事务的⾃动提交设置为false,然后再去执⾏原本的业务逻辑⽅法,如果执⾏业务逻辑⽅法没有出现异常,那么代理逻辑中就会将事务进⾏提交,如果执⾏业务逻辑⽅法出现了异常,那么则会将事务进⾏回滚。
当然,针对哪些异常回滚事务是可以配置的,可以利⽤@Transactional注解中的rollbackFor属性进⾏配置,默认情况下会对RuntimeException和Error进⾏回滚。
隔离级别:
spring事务隔离级别就是数据库的隔离级别:外加⼀个默认级别
read uncommitted(读未提交)
read committed(提交读、不可重复读)(oracle默认)
repeatable read(可重复读)(mysql默认)
serializable(可串⾏化)

10.Spring事务在哪几种情况下会失效?

1、发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而
是UserService对象本身!
解决方法很简单,让那个this变成UserService的代理类即可!即用@Autowired去取IOC容器中的代理对象。

2、方法不是public的(访问权限)

@Transactional 只能用于 public 的方法上,否则事务不会生效,如果要用在非 public 方法上,可 以开启AspectJ 代理模式。

3、数据库不支持事务(因为spring事务是基于数据库的事务) {数据库引擎是myisam 不支持事务}

4、没有被spring管理(就是说方法上加了@Transactional 注解,但是类上并没有加@Service、@Component等注解,没有放到IOC容器中交给spring管理,则事务不生效)

5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)

详情:https://www.zhihu.com/question/506754250/answer/2352581793

img

11.循环依赖与二级缓存解决方案

循环依赖的本质就是你的完整对象实例要依赖与其他实例,而其他实例的完整对象也同样依赖于你,相互之间的依赖从而导致没法完整创建对象而导致失败/报错.

对象 A 还没创建成功,在创建的过程中要依赖另一个对象 B;而另一个对象 B 在创建中要依赖 A,这种肯定是无解的

public class CircularTest {
    public static void main(String[] args) {
        A a = new A();
    }
    static class A {
        private B classTwo = new B();
    }
    static class B {
        private A classOne = new A();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PSRN4sCf-1663400102681)(assets/image-20220916110932412.png)]

二级缓存解决方法:

可以在new一个原始对象时,把这个对象放入到一个map中去,然后进行设置B属性并且判断map中有没有b属性,如果没有就去创建b对象,在创建b对象时,也把b的原始对象放入到a中去,此时设置A属性时,判断map中有没有a对象,如果有就设置B对象的嵌套属性完成B对象的生命周期创建,此时A对象在单例池中科院找到B对象,A对象就可以创建成功了。

主要方法:通过map来做一个熔断 但是在Spring中还是有点小问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e5yS7j6T-1663400102681)(assets/image-20220916112245397.png)]

12.Spring三级缓存解决循环依赖

缓存层级名称描述
第一层缓存singletonObjects单例对象缓存池,存放的 Bean 已经实例化、属性赋值、完全初始化好(成品)
第二层缓存earlySingletonObjects早期单例对象缓存池,存放的 Bean 已经实例化但尚未属性赋值、未执行 init 方法(半成品)
第三层缓存singletonFactories单例工厂的缓存
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

 //一级缓存(单例池,经过完成生命周期的对象会放入其中)
 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    //二级缓存(刚实例化还未初始化的原始对象会放入其中)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
 
    //三级缓存(存放创建某个对象的工厂)
 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // Spring首先从singletonObjects(一级缓存)中尝试获取
  Object singletonObject = this.singletonObjects.get(beanName);
  // 若是获取不到而且对象在建立中,则尝试从earlySingletonObjects(二级缓存)中获取
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
          ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
          if (singletonFactory != null) {
            //若是仍是获取不到而且允许从singletonFactories经过getObject获取,则经过singletonFactory.getObject()(三级缓存)获取
              singletonObject = singletonFactory.getObject();
              //若是获取到了则将singletonObject放入到earlySingletonObjects,也就是将三级缓存提高到二级缓存中
              this.earlySingletonObjects.put(beanName, singletonObject);
              this.singletonFactories.remove(beanName);
          }
        }
    }
  }
  return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

  • A 创建过程中需要 B, 于是 A 将自己放到三级缓存里面,去实例化 B
  • B 实例化的时候发现需要 A,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存
    找到了A,然后把三级缓存中的 A 放到二级缓存,并删除三级缓存中的 A
  • B 顺利初始化完毕,将自己放到一级缓存中(此时 B 中的 A 还是创建中状态,并没有完全初始化),删除三级缓存中的 B
    然后接着回来创建 A,此时 B 已经完成创建,直接从一级缓存中拿到 B,完成 A 的创建,并将 A 添加到单例池,删除二级缓存中的 A

img

13.Spring 框架中都用到了哪些设计模式?

(1)工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象

(2)单例模式:Bean默认为单例模式

(3)策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略

(4)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术

(5)模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate

(6)适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller

(7)观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。

(8)桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库

14.SpringMVc的工作流程

(1) 用户通过浏览器发起 HttpRequest 请求到前端控制器 (DispatcherServlet)。

(2)DispatcherServlet 将用户请求发送给处理器映射器 (HandlerMapping)。

(3)处理器映射器 (HandlerMapping)会根据请求,找到负责处理该请求的处理器,并将其封装为处理器执行链 返回 (HandlerExecutionChain) 给 DispatcherServlet

(4) DispatcherServlet 会根据 处理器执行链 中的处理器,找到能够执行该处理器的处理器适配器(HandlerAdaptor) --注,处理器适配器有多个

(5) 处理器适配器 (HandlerAdaptoer) 会调用对应的具体的 Controller

(6)Controller 将处理结果及要跳转的视图封装到一个对象 ModelAndView 中并将其返回给处理器适配器 (HandlerAdaptor)

(7)HandlerAdaptor 直接将 ModelAndView 交给 DispatcherServlet ,至此,业务处理完毕

(8)业务处理完毕后,我们需要将处理结果展示给用户。于是DisptcherServlet 调用 ViewResolver,将 ModelAndView 中的视图名称封装为视图对象

(9)ViewResolver 将封装好的视图 (View) 对象返回给 DIspatcherServlet

(10)DispatcherServlet 调用视图对象,让其自己 (View) 进行渲染(将模型数据填充至视图中),形成响应对象 (HttpResponse)

(11)前端控制器 (DispatcherServlet) 响应 (HttpResponse) 给浏览器,展示在页面上。

用户。于是DisptcherServlet 调用 ViewResolver,将 ModelAndView 中的视图名称封装为视图对象

(9)ViewResolver 将封装好的视图 (View) 对象返回给 DIspatcherServlet

(10)DispatcherServlet 调用视图对象,让其自己 (View) 进行渲染(将模型数据填充至视图中),形成响应对象 (HttpResponse)

(11)前端控制器 (DispatcherServlet) 响应 (HttpResponse) 给浏览器,展示在页面上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lnh951Ej-1663400102682)(assets/image-20220916195403806.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值