Spring后端框架复习总结

之前写的博客太杂,最近想把后端框架的知识点再系统的过一遍,主要是Spring Boot和Mybatis相关,带着自己的理解使用简短的话把一些问题总结一下,尤其是开发中和面试中的高频问题,基础知识点可以参考之前写java后端专栏,这篇不再赘述。

Spring

什么是AOP?底层原理?

详细介绍–>SpringBoot 事务与AOP

什么是AOP?

aop是面向切面编程,在spring中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,一般比如可以做为公共日志保存,事务处理等

我们当时在后台系统中,就是使用aop来记录了系统的操作日志。主要思路是这样的,使用aop中的环绕通知+切点表达式,这个表达式就是要找到要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取到这些参数以后,保存到数
据库。

AOP的底层原理?

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。

  • JDK动态代理只提供接口的代理,不支持类的代理
    • JDK会在运行时为目标类生成一个动态代理类$proxy*.class .
    • 该代理类是实现了接目标类接口,并且代理类会实现接口所有的方法增强代码
    • 调用时先去调用处理类进行增强,再通过反射的方式进行调用目标方法。从而实现AOP

  • 如果代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
    • CGLIB的底层是通过ASM(一种用于直接生成或修改字节码的框架,全称为 “Abstract Syntax Notation One”)在运行时动态的生成目标类的一个子类。(还有其他相关类)会生成多个。并且会重写父类所有的方法增强代码,
    • 调用时先通过代理类进行增强,再直接调用父类对应的方法进行调用目标方法。从而实现AOP。
      • CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
      • CGLIB除了生成目标子类代理类,还有一个FastClass(路由类),可以(但不是必须))让本类方法调用进行增强,而不会像jdk代理那样本类方法调用增强会失效

  • jck动态代理生成类速度快,调用慢,cglib生成类速度慢,但后续调用快。就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。


IOC的工作流程;控制反转与依赖注入原理

详细介绍–>SpringBoot Web 分层解耦

Spring IOC的工作流程

回答这个问题需要回答:1、IOC是什么 2、Bean的声明方式 3、IOC的工作流程(面试时间短可以直接回答这个)

1、控制反转(Inversion of Control, IoC),核心思想是把对象的管理权限交给了容器,应用程序如果需要使用某个对象的实例直接从IOC容器里去获取就行。这种设计的好处在于降低了程序里对象与对象之间的耦合性,使得程序的整个体系结构变得更加灵活。

2、Spring里提供了很多方式去声明一个Bean,比如在XML配置文件里通过<bean>标签、通过@Service等注解、通过配置类里@Bean注解去声明等等。Spring在启动时会去解析这些Bean然后保存到IOC容器里。

3、IOC的工作流程大致可以分为两个阶段。第一个阶段是IOC容器初始化阶段,这个阶段主要是根据程序里面定义的XML或者注解等Bean的声明方式,通过解析和加载后生成BeanDefinition,然后把BeanDefinition注册到IOC容器里,通过XML或者注解等声明的Bean都会解析得到一个BeanDefinition实体,里面包含bean的一些定义和基本属性。最后把这个BeanDefinition保存到一个Map集合里从而去完成IOC的初始化。
IOC容器的作用就是对这些注册的Bean的定义信息进行处理和维护,它是IOC容器控制反转的一个核心。第二个阶段是完成Bean的初始化和依赖注入,第二阶段会做两个事情。第一个是通过反射去针对没有设置lazy-init属性的单例bean进行初始化,第二个是完成Bean的依赖注入。最后一个阶段就是Bean的使用,通常我们通过@Autowired注解,或者通过BeanFactory.getBean()从IOC容器里去获取指定bean的实例。另外针对设置了懒加载属性和非单例bean的实例化是在每一次获取bean对象的时候调用bean的初始化方法来完成实例化的,并且Spring IOC容器不会去管理这些Bean。



控制反转与依赖注入原理:

简介

控制反转(Inversion of Control, IoC):是一种将对象的创建和初始化过程从应用程序代码中抽离出来,并将这部分控制移交给 Spring 容器的设计模式。

依赖注入(Dependency Injection, DI):是实现 IoC 的一种方式。 DI 通过注入所需的依赖对象来实现对象之间的松耦合。这意味着类不再直接创建其依赖项,而是通过外部提供这些依赖项。

目的:高内聚低耦合

  • 高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 “高内聚”。
  • 低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。

实现原理

  • IoC 容器:Spring 提供了一个 IoC 容器,通常是 ApplicationContext。这个容器负责管理 beans 的生命周期和配置。

  • Bean:在 Spring 中,每一个被容器管理的对象都称为一个 bean。你可以使用各种注解(如 @Component, @Service, @Repository, @Controller 等)或者 XML 配置文件来声明一个 bean。

  • 依赖注入的方式:

    • 构造器注入:通过类的构造器注入依赖
    • Setter 注入:通过 setter 方法注入依赖
    • 字段注入:直接在字段上注入依赖。
  • 自动装配(@Autowired):Spring 通过 @Autowired 注解自动寻找和装配需要的依赖项。Spring 容器会在适当的(构造器、setter、字段)上注入所需的 bean 实例。

  • 配置类和注解:在 Spring Boot 中,通常使用配置类(使用 @Configuration 注解的类)和 Java 注解来简化配置。例如,@SpringBootApplication 是一个常用的复合注解,包含了@Configuration, @EnableAutoConfiguration, 和 @ComponentScan 等注解



事务底层原理?多线程事务能不能保证一致性?

详细介绍–>SpringBoot 事务与AOP

spring实现的事务本质就是AOP完成,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。其中AOP底层是基于JDK动态代理和cglib动态代理实现的,这个上面有介绍。

Spring多线程事务能不能保证一致性

假设A方法被声明了@Transactional,然后在A方法里new一个线程,去执行B方法,那么这种情况下A和B能保证原子性或者数据一致性么?A失败了B会回滚吗?或者B失败了A会回滚吗?

答案:不能。因为Spring底层是使用ThreadLocal来保存事务信息的比如数据库连接Connection,所以一个线程永远只能有一个事务,Spring的事务是无法实现事务一致性的。
解决办法:可以自己解决,比如通过编程式的事务,自己控制提交和回滚,或者说通过分布式事务的思路,2PC,3PC,SAGA,MQ的消息最终一致性都可以解决。


编程式事务:这种方式是通过编程的方式来控制事务的提交和回滚。在Spring中,可以使用TransactionTemplate或者PlatformTransactionManager来实现。例如,你可以在A方法中创建一个新的事务,然后在B方法中使用相同的事务。如果A或B中有任何异常,你可以捕获这个异常并决定是否回滚事务。这种方式需要你手动管理事务,包括开始事务、提交事务、回滚事务等。


分布式事务:这是一种更复杂的解决方案,通常用于处理跨多个数据库或服务的事务。这种方法包括以下几种策略:
2PC(两阶段提交):这是一种原子性协议,它保证了所有参与者要么都提交事务,要么都不提交。它分为两个阶段:准备阶段和提交阶段。在准备阶段,所有参与者都会被询问是否可以提交事务,只有当所有参与者都同意提交事务时,才会进入提交阶段。否则,事务将被回滚。
3PC(三阶段提交):这是两阶段提交的改进版,它添加了一个超时机制,以防止在等待其他参与者响应时发生阻塞。
SAGA:这是一种长寿命事务的解决方案,它将一个大事务分解为多个小事务,每个小事务都可以独立地提交或回滚。如果某个小事务失败,SAGA会执行一系列的补偿操作来保证数据的一致性。
MQ(消息队列):这是一种最终一致性的解决方案,它使用消息队列来保证事务的一致性。如果A方法执行成功,它会发送一个消息到消息队列,然后B方法会监听这个消息队列,当它收到消息时,就会执行相应的操作。



为什么禁用@Transactional?

为什么有些公司禁止使用@Transactional声明式事务?

有几个方面的考虑:

1、在方法上面增加@Transactional的声明式事务,如果一个方法中存在较多耗时的操作,很容易引发长事务的问题,而长事务会带来锁的竞争,影响性能,同时也会导致数据库的连接池被消耗尽,影响程序正常执行

2、如果方法存在嵌套调用,而被嵌套调用的方法也声明了@Transactional事务,那么这个时候就会出现事务嵌套调用的行为,容易引起事务混乱,程序运行结果出现异常等问题

3、@Transactional的声明式事务是将事务控制逻辑放在注解里,如果项目复杂度增加,事务控制会变得更加复杂,导致代码的可读性和维护性下降,所以为了避免这类问题,有些公司会推荐使用编程式事务,这样可以更加灵活的去控制事务的范围,减少事务的锁定时间,提高系统性能



事务失效场景?

详细介绍–>SpringBoot 事务与AOP

Spirng通过@transactional注解来进行事务控制,但很多场景会导致事务失效。

第一个,如果方法上异常捕获处理,自己try catch处理了异常,没有抛出,就会导致事务失效,所以一般处理了异常以后,别忘了抛出去就行了

第二个,如果报RuntimeException以外的错也会导致事务失效,若在@Transactional上配置rollbackFor属性为Exception,这样别管是什么异常,都会回滚事务

默认情况下,只有出现RuntimeException(运行时异常)才会回滚事务。假如我们想让所有的异常都回滚,需要来配置@Transactional注解当中的rollbackFor属性,通过rollbackFor这个属性可以指定出现何种异常类型回滚事务

第三,如果方法是private修饰的,也会导致事务失效,因为AOP底层是基于cglib动态代理实现的,子类是不能重载父类的private方法的,所以无法很好的利用代理,会导致@Transactional失效。

ps:因为Spring事务是基于代理来实现的,所以某个加了@Transactional的方法只有是代理对象调用时,那么这个注解才会生效,所以如果是被代理对象来调用这个方法,那么@Transactional是不会生效的。

第四,@Transactional注解在了final或static修饰的方法上。因为被final修饰的方法是无法被重写的,所以代理对象是无法调用的。而static修饰的方法属于类对象不属于对象实例,所以代理对象也是无法调用的。

第五,当前类没有被Spring容器所管理,没有配置成bean。如果当前类没有被Spring 容器所管理,那么Spring就无法为该类生成代理对象,从而Spring 的事务会失效。

请添加图片描述

第六,同一个类中方法的调用,导致的 AOP失效,从而导致@Transactional注解失效。这也是我们开发中最容易犯错的一种场景。

请添加图片描述

同一个类中的方法调用,属于this 调用,并不会使用代理对象,所以 AOP会失效。没有办法通过动态代理给你进行增强。不过,该问题可以使用如下方式来解决,因为我们从spring容器中自动装配的bean假如实现了声明式事务或者AOP,那么就会为你创建动态代理。

请添加图片描述

除了上面的自动装配,还可以这样:((UserService)AopContext.currentProxy()).methodB()
也可以拿到当前的动态代理对象,这个方法的原理是当我们调用了一个动态代理的方法(methodA()),会把当前的动态代理对象存入到currentProxy的ThreadLocal中,那么此时再通过AopContext获得currentProxy,就可以拿到当前正在调用的动态代理对象,这种写法呢比自己注入进来更加优雅,且不会存在循环依赖问题。

第七,多个事务方法不在同一个线程内执行。如下代码片段中,事务方法addUser()中调用了另外一个事务方法roleService.addRole(),注意,这两个方法不是在同一个线程中执行的!这会导致什么问题呢?我们上面刚刚说过了,spring不能保证多线程下的事务一致性,这两个事务方法获取到的数据库连接是2个不同的数据库连接!不同的数据库连接会导致事务失效!解决办法在上面的Spring多线程事务能不能保证一致性中有介绍。

请添加图片描述

请添加图片描述



Bean的生命周期及常用自定义方法

详细介绍–>SpingBoot原理

在这里插入图片描述

简单来说分为五步:

  • 1.实例化:当容器需要初始化一个尚未初始化的Bean,或者初始化Bean时需要注入另一个尚未初始化的依赖时,容器会调用doCreateBean()方法进行实例化。实际上,这是通过反射的方式创建出一个Bean对象。
  • 2.属性赋值:Bean实例创建完成后,接下来是为这个Bean对象进行属性填充,也就是注入这个Bean依赖的其他Bean对象。</
  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要准备Java后端面试,你可以按照以下路线进行复习: 1. 熟悉Java基础知识,包括面向对象编程的概念、Java语法、集合框架等。这是面试的基础,需要掌握Java的核心概念和常用语法。 2. 深入理解Java的多线程和并发编程。这是Java后端开发中常见的需求,需要了解线程的生命周期、线程间的通信方式、锁机制等。同时,掌握并发编程中的常见问题和解决方案,比如如何避免死锁、线程安全等。 3. 学习常见的Java框架和技术,如SpringSpring BootMyBatis等。这些框架后端开发中非常常见,需要了解它们的原理、用法和常见的应用场景。 4. 掌握数据库相关的知识,包括SQL语法、数据库设计和优化等。在面试中,你可能会被问到如何编写高效的SQL查询语句,如何设计关系型数据库的表结构等。 5. 熟悉常用的网络协议和Web开发技术,如HTTP、TCP/IP、RESTful API等。这些知识对于后端开发至关重要,需要了解它们的基本原理和使用方法。 6. 学习常见的系统设计和架构模式,如分布式系统、微服务架构等。在面试中,你可能需要设计一个可扩展、高可用性的系统,并对其进行优化和调优。 除了上述的通用性复习路线,你还可以根据自己所面试岗位的具体要求来进行有针对性的复习。查看面试岗位的JD,了解公司对于Java后端开发的需求和要求,然后重点复习相关的知识和技能。 尽管求职环境有些困难,但是只要你做好充分的准备,提前了解岗位要求并有针对性地复习,相信你能够应对好Java后端面试的挑战。加油!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Java后端面试该复习什么?只需一张图](https://blog.csdn.net/weixin_70730532/article/details/126725468)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [Java后端面试该复习什么?只需一张图|原创](https://blog.csdn.net/sinat_32873711/article/details/126535341)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值