2024年java面试准备--spring篇
前言: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,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
- 声明式
在配置文件中设置以下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异常 | 子回滚,主不回滚 |
正常 | 异常 | 均回滚 |
异常 | 正常 | 均回滚 |
- 编程式事务
Javax.transaction.UserTranscation
JTA 事务可以精确到事务的开始和结束
spring事务失效原因
- service没有托管给spring
失效原因: spring事务生效的前提是,service必须是一个bean对象
解决方案: 将service注入spring - 抛出受检异常
失效原因: spring默认只会回滚非检查异常和error异常
解决方案: 配置rollbackFor - 业务自己捕获了异常(try-catch)
失效原因: spring事务只有捕捉到了业务抛出去的异常,才能进行后续的处理,如果业务自己捕获了异常,则事务无法感知。
解决方案: 1、将异常原样抛出;
2、设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); - 切面顺序导致
失效原因: spring事务切面的优先级顺序最低,但如果自定义的切面优先级和他一样,且自定义的切面没有正确处理异常,则会同业务自己捕获异常的那种场景一样
解决方案: 1、在切面中将异常原样抛出;
2、在切面中设置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); - 非Public方法
失效原因: spring事务默认生效的方法权限都必须为public
解决方案: 1、将方法改为public;
2、修改TansactionAttributeSource,将publicMethodsOnly改为false - 父子容器
失效原因: 子容器扫描范围过大,将未加事务配置的serivce扫描进来
解决方案: 1、父子容器个扫个的范围; 2、不用父子容器,所有bean都交给同一容器管理 - 方法用final/static修饰
失效原因: 因为spring事务是用动态代理实现,因此如果方法使用了final修饰,则代理类无法对目标方法进行重写,植入事务功能
解决方案: 1、方法不要用final修饰 - 调用本类方法
失效原因: 本类方法不经过代理,无法进行增强
解决方案: 1、注入自己来调用; 2、使用@EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy() - 多线程调用
失效原因: 因为spring的事务是通过数据库连接来实现,而数据库连接spring是放在threadLocal里面。同一个事务,只能用同一个数据库连接。而多线程场景下,拿到的数据库连接是不一样的,即是属于不同事务 - 错误的传播行为
失效原因: 使用的传播特性不支持事务 - 使用了不支持事务的存储引擎
失效原因: 使用了不支持事务的存储引擎。比如mysql中的MyISAM - 数据源没有配置事务管理器
注: 因为springboot,他默认已经开启事务管理器。 - 被代理的类过早实例化
失效原因: 当代理类的实例化早于AbstractAutoProxyCreator后置处理器,就无法被AbstractAutoProxyCreator后置处理器增强
Bean
Bean的生命周期
(1)默认情况下,IOC容器中bean的生命周期分为五个阶段:
- 调用构造器 或者是通过工厂的方式创建Bean对象
- 给bean对象的属性注入值
- 调用初始化方法,进行初始化, 初始化方法是通过init-method来指定的
- 使用
- IOC容器关闭时, 销毁Bean对象
(2)当加入了Bean的后置处理器后,IOC容器中bean的生命周期分为七个阶段:
. 调用构造器 或者是通过工厂的方式创建Bean对象
- 给bean对象的属性注入值
- 执行Bean后置处理器中的 postProcessBeforeInitialization
- 调用初始化方法,进行初始化, 初始化方法是通过init-method来指定的
- 执行Bean的后置处理器中 postProcessAfterInitialization
- 使用
- IOC容器关闭时, 销毁Bean对象
Bean作用域
名称 | 作用域 |
singleton | 单例对象,默认值的作用域 |
prototype | 每次获取都会创建⼀个新的 bean 实例 |
request | 每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP request内有效。 |
session | 在一次 HTTP session 中,容器将返回同一个实例 |
global-session | 将对象存入到web项目集群的session域中,若不存在集群,则global session相当于session |
默认作用域是singleton,多个线程访问同一个bean时会存在线程不安全问题
2024年java面试准备--spring篇(二)https://developer.aliyun.com/article/1393088