2024年java面试准备--spring篇

2023-10-16 10:12:17 浏览数 (1)

前言:Spring面试内容比较多,分成两部分整理总结,这是第一部分~~

什么是spring?

Spring可以做很多事情,它为企业级开发提供给了丰富的功能,但是这些功能的底层都依赖于它的两个核心特性,也就是控制反转(Inversion of Control,IOC)和面向切面编程(aspect-oriented programming,AOP)。

为了降低Java开发的复杂性,Spring采取了以下4种关键策略

  • 基于POJO的轻量级和最小侵入性编程;
  • 通过依赖注入和面向接口实现松耦合;
  • 基于切面和惯例进行声明式编程;
  • 通过切面和模板减少样板式代码。
反射原理以及使用场景

Java反射:

是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且都能够调用它的任意一个方法;

反射原理:

Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;其中class代表的时类对 象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组成部分。 ​ 如何得到Class的实例:

1.类名.class(就是一份字节码) 2.Class.forName(String className);根据一个类的全限定名来构建Class对象 3.每一个对象多有getClass()方法:obj.getClass();返回对象的真实类型

使用场景:

  • 开发通用框架 - 反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Filter 等),为了保证框架的通用性,需要根据配置文件运行时动态加载不同的对象或类,调用不同的方法。
  • 动态代理 - 在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式。这时,就需要反射技术来实现了。 JDK:spring默认动态代理,需要实现接口 CGLIB:通过asm框架序列化字节流,可配置,性能差
  • 自定义注解 - 注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。
  • 操作权限不够的类属性和方法
Spring框架的设计目标,设计理念,和核心是什么?

Spring设计目标:Spring为开发者提供一个一站式轻量级应用开发平台;

Spring设计理念:在JavaEE开发中,支持POJO和JavaBean开发方式,使应用面向接口开发,充分支持OO(面向对象)设计方法;Spring通过IoC容器实现对象耦合关系的管理, 并实现依赖反转,将对象之间的依赖关系交给IoC容器,实现解耦;

Spring框架的核心:IoC容器和AOP模块。通过IoC容器管理POJO对象以及他们之间的耦合关系;通过AOP以动态非侵入的方式增强服务。

IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

Spring的优缺点是什么?

优点 ①. 方便解耦,简化开发

Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。

②. AOP编程的支持

Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。

③. 声明式事务的支持

只需要通过配置就可以完成对事务的管理,而无需手动编程。

④. 方便程序的测试

Spring对Junit4支持,可以通过注解方便的测试Spring程序。

⑤. 方便集成各种优秀框架

Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。

⑥. 降低JavaEE API的使用难度

Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。

缺点 Spring明明一个很轻量级的框架,却给人感觉大而全 Spring依赖反射,反射影响性能 使用门槛升高,入门Spring需要较长时间

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

工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例; 单例模式:Bean默认为单例模式。 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术; 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被自动更新,如Spring中listener的实现–ApplicationListener。

事务

spring事务传播机制

声明式事务虽然优于编程式事务,但也有不足,声明式事务管理的粒度是方法级别,而编程式事务是可以精确到代码块级别的。

要想实现事务管理和业务代码的抽离,就必须得用到 Spring 当中的AOP,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

  1. 声明式

在配置文件中设置以下6项

(1)、required

如果客户端没有事务 在bean中新起一个事务

如果客户端有事务bean 中就加进去

子事务

主事务

结果

异常

正常,并try-catch异常

均回滚

正常

异常

均回滚

正常

异常,并try-catch异常

不回滚

(2)、 requiresNew

不管客户端有没有事务服务器段都新起一个事务

如果客户端有事务就将事务挂起

子事务

主事务

结果

异常

正常,并try-catch异常

子回滚,主不回滚

正常

异常

子不回滚,主回滚

异常

正常

均回滚

(3)、supports

如果客户端没有事务服务端也没有事务

如果客户端有事务服务端就加一个事务

(4)、mandatcry

如果客户端没有事务服务端就会报错

如果客户端有事务服务端就加事务

(5)、notSupported

不管客户端有没有事务服务端都没有事务

如果客户端有事务服务端就挂起

(6)、never

不管客户端有没有事务服务端都没有事务

如果客户端有事务就报错

(7)、NESTED

如果当前存在事务,则在嵌套事务内执行。

如果当前没有事务,则进行与REQUIRED类似的操作

子事务

主事务

结果

异常

正常,并try-catch异常

子回滚,主不回滚

正常

异常

均回滚

异常

正常

均回滚

  1. 编程式事务

Javax.transaction.UserTranscation

JTA 事务可以精确到事务的开始和结束

spring事务失效原因
  1. service没有托管给spring 失效原因: spring事务生效的前提是,service必须是一个bean对象 解决方案: 将service注入spring
  2. 抛出受检异常 失效原因: spring默认只会回滚非检查异常和error异常 解决方案: 配置rollbackFor
  3. 业务自己捕获了异常(try-catch) 失效原因: spring事务只有捕捉到了业务抛出去的异常,才能进行后续的处理,如果业务自己捕获了异常,则事务无法感知。 解决方案: 1、将异常原样抛出; 2、设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  4. 切面顺序导致 失效原因: spring事务切面的优先级顺序最低,但如果自定义的切面优先级和他一样,且自定义的切面没有正确处理异常,则会同业务自己捕获异常的那种场景一样 解决方案: 1、在切面中将异常原样抛出; 2、在切面中设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  5. 非Public方法 失效原因: spring事务默认生效的方法权限都必须为public 解决方案: 1、将方法改为public; 2、修改TansactionAttributeSource,将publicMethodsOnly改为false
  6. 父子容器 失效原因: 子容器扫描范围过大,将未加事务配置的serivce扫描进来 解决方案: 1、父子容器个扫个的范围; 2、不用父子容器,所有bean都交给同一容器管理
  7. 方法用final/static修饰 失效原因: 因为spring事务是用动态代理实现,因此如果方法使用了final修饰,则代理类无法对目标方法进行重写,植入事务功能 解决方案: 1、方法不要用final修饰
  8. 调用本类方法 失效原因: 本类方法不经过代理,无法进行增强 解决方案: 1、注入自己来调用; 2、使用@EnableAspectJAutoProxy(exposeProxy = true) AopContext.currentProxy()
  9. 多线程调用 失效原因: 因为spring的事务是通过数据库连接来实现,而数据库连接spring是放在threadLocal里面。同一个事务,只能用同一个数据库连接。而多线程场景下,拿到的数据库连接是不一样的,即是属于不同事务
  10. 错误的传播行为 失效原因: 使用的传播特性不支持事务
  11. 使用了不支持事务的存储引擎 失效原因: 使用了不支持事务的存储引擎。比如mysql中的MyISAM
  12. 数据源没有配置事务管理器 注: 因为springboot,他默认已经开启事务管理器。
  13. 被代理的类过早实例化 失效原因: 当代理类的实例化早于AbstractAutoProxyCreator后置处理器,就无法被AbstractAutoProxyCreator后置处理器增强

Bean

Bean的生命周期

(1)默认情况下,IOC容器中bean的生命周期分为五个阶段:

  1. 调用构造器 或者是通过工厂的方式创建Bean对象
  2. 给bean对象的属性注入值
  3. 调用初始化方法,进行初始化, 初始化方法是通过init-method来指定的
  4. 使用
  5. IOC容器关闭时, 销毁Bean对象

(2)当加入了Bean的后置处理器后,IOC容器中bean的生命周期分为七个阶段:

. 调用构造器 或者是通过工厂的方式创建Bean对象

  1. 给bean对象的属性注入值
  2. 执行Bean后置处理器中的 postProcessBeforeInitialization
  3. 调用初始化方法,进行初始化, 初始化方法是通过init-method来指定的
  4. 执行Bean的后置处理器中 postProcessAfterInitialization
  5. 使用
  6. IOC容器关闭时, 销毁Bean对象
Bean作用域

名称

作用域

singleton

单例对象,默认值的作用域

prototype

每次获取都会创建⼀个新的 bean 实例

request

每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP request内有效。

session

在一次 HTTP session 中,容器将返回同一个实例

global-session

将对象存入到web项目集群的session域中,若不存在集群,则global session相当于session

默认作用域是singleton,多个线程访问同一个bean时会存在线程不安全问题

依赖注入三种方式(Ioc的三种实现方式)
  1. 构造方法注入
  2. setter注入
  3. 基于注解的注入
实例化bean的三种方式
  1. 无参构造方法实例化(Spring默认,常用,需要bean类中存在无参构造方法);
  2. 静态工厂实例化
  3. 实例化工厂实例化
IOC容器初始化加载Bean流程:
代码语言:javascript复制
@Override
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) {
  // 第一步:刷新前的预处理 
  prepareRefresh();
  //第二步: 获取BeanFactory并注册到 BeanDefitionRegistry
  ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  // 第三步:加载BeanFactory的预准备工作(BeanFactory进行一些设置,比如context的类加载器等)
  prepareBeanFactory(beanFactory);
  try {
    // 第四步:完成BeanFactory准备工作后的前置处理工作 
    postProcessBeanFactory(beanFactory);
    // 第五步:实例化BeanFactoryPostProcessor接口的Bean 
    invokeBeanFactoryPostProcessors(beanFactory);
    // 第六步:注册BeanPostProcessor后置处理器,在创建bean的后执行 
    registerBeanPostProcessors(beanFactory);
    // 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析); 
    initMessageSource();
    // 第八步:注册初始化事件派发器 
    initApplicationEventMulticaster();
    // 第九步:子类重写这个方法,在容器刷新的时候可以自定义逻辑 
    onRefresh();
    // 第十步:注册应用的监听器。就是注册实现了ApplicationListener接口的监听器
    registerListeners();
    //第十一步:初始化所有剩下的非懒加载的单例bean 初始化创建非懒加载方式的单例Bean实例(未设置属性)
    finishBeanFactoryInitialization(beanFactory);
    //第十二步: 完成context的刷新。主要是调用LifecycleProcessor的onRefresh()方法,完成创建
    finishRefresh();
    }
  ……
} 

总结:

四个阶段

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction

多个扩展点

  • 影响多个Bean
    • BeanPostProcessor
    • InstantiationAwareBeanPostProcessor
  • 影响单个Bean
    • Aware

完整流程

  1. 实例化一个Bean--也就是我们常说的new
  2. 按照Spring上下文对实例化的Bean进行配置--也就是IOC注入
  3. 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,也就是根据就是Spring配置文件中Bean的id和name进行传递
  4. 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现setBeanFactory(BeanFactory)也就是Spring配置文件配置的Spring工厂自身进行传递
  5. 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,和4传递的信息一样但是因为ApplicationContext是BeanFactory的子接口,所以更加灵活
  6. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization()方法,BeanPostProcessor经常被用作是Bean内容的更改,由于这个是在Bean初始化结束时调用那个的方法,也可以被应用于内存或缓存技
  7. 如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。
  8. 如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization(),打印日志或者三级缓存技术里面的bean升级
  9. 以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例,当然在Spring配置文件中也可以配置非Singleton,这里我们不做赘述。
  10. 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,或者根据spring配置的destroy-method属性,调用实现的destroy()方法
spring的bean是线程安全的吗?

spring的默认bean作用域是单例的,单例的bean不是线程安全的,但是开发中大部分的bean都是无状态的,不具备存储功能,比如controller、service、dao,他们不需要保证线程安全。

如果要保证线程安全,可以将bean的作用域改为prototype,比如像Model View。

另外还可以采用ThreadLocal来解决线程安全问题。ThreadLocal为每个线程保存一个副本变量,每个线程只操作自己的副本变量。

IOC

什么是Spring IOC 容器?

IoC(Inverse of Control:控制反转)是⼀种设计思想,就是将原本在程序中⼿动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语⾔中也有应⽤,并⾮ Spring 特有。

IoC 容器是 Spring⽤来实现 IoC 的载体,将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注⼊。这样可以很⼤程度上简化应⽤的开发,把应⽤从复杂的依赖关系中解放出来。 IoC 容器就像是⼀个⼯⼚⼀样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即可,完全不⽤考虑对象是如何被创建出来的。

DI 依赖注入

DI:(Dependancy Injection:依赖注入)站在容器的角度,将对象创建依赖的其他对象注入到对象中。

如何实现一个IOC容器
  1. 配置文件配置包扫描路径
  2. 递归包扫描获取.class文件
  3. 反射、确定需要交给IOC管理的类
  4. 对需要注入的类进行依赖注入
  • 配置文件中指定需要扫描的包路径
  • 定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解
  • 从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储
  • 遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这些对象
  • 遍历这个IOC容器,获取到每一个类的实例,判断里面是有有依赖其他的类的实例,然后进行递归注入
Spring 的 IoC支持哪些功能

Spring 的 IoC 设计支持以下功能:

  1. 依赖注入
  2. 依赖检查
  3. 自动装配
  4. 支持集合
  5. 指定初始化方法和销毁方法
  6. 支持回调某些方法(但是需要实现 Spring 接口,略有侵入) 其中,最重要的就是依赖注入,从 XML 的配置上说,即 ref 标签。对应 Spring RuntimeBeanReference 对象。 对于 IoC 来说,最重要的就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。
IOC初始化过程

IOC容器初始化是由refresh()方法来启动的,这个方法标志着IOC容器的正式启动。Spring将IOC容器启动的过程分开,并使用不同的模块来完成,如使用ResourceLoader,BeanDefinition等模块, IOC容器的启动主要包括三个过程:

Resource定位过程: Resource定位指beanDefinition的资源定位,由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供了统一的接口。这个过程类似于容器寻找数据的过程。

BeanDefinition的载入: BeanDefinition载入过程指的是把用户定义好的Bean表示为容器内部的数据结构,这个容器的数据结构其实就是BeanDefinition。实际上BeanDefinition就是POJO对象在容器的抽象,通过BeanDefinition来定义的数据结构,像是世间万物在java中的抽象,java的对象又在容器中的抽象就是BeanDefinition。

向容器注册BeanDefinition: 这个注册过程是通过调用BeanDefinitionRegistry接口来完成的,就是把载入过程中解析得到的BeanDefinition向IOC容器进行注册。通过下面的源码可以得到,注册过程就是在IOC容器将BeanDefinition注入到一个HashMap中,IOC容器就是通过这个HashMap来持有BeanDefinition数据的。

0 人点赞