Spring

Spring架构的核心?

  1. IOC容器:开发者只需要定义Bean和依赖关系,Spring容器就可以创建和组装对象
  2. AOP:面向切面编程,允许开发者定义横切关注点,通过AOP可以将这些关注点模块化,提高代码的可维护性
  3. 事务管理:支持声明式和编程式,轻松的进行事务管理,无需关系具体的事务API
  4. MVC框架:支持URL到页面控制器的映射

IOC:控制反转,创建和获取对象的思想,传统的开发需要使用new关键字来创建对象,如果使用IOC思想的话,我们可以通过IOC容器帮我们实例化对象

AOP:面向切面编程,将与业务无关的代码封装起来,减少系统的重复代码,降低模块间的耦合度。AOP的底层是动态代理,如果要代理的对象实现了某个接口,那么AOP就会使用JDK动态代理去创建代理对象;对于没有实现接口的对象,就无法使用JDK动态代理去代理了,这时候AOP就会使用Cglib生成一个被代理对象的子类来作为代理

  • 通过IOC管理容器对象的依赖关系,然后通过AOP将横切关注点统一注入需要的业务逻辑中
  • 使用IOC容器管理Service和Mapper层的关系,通过AOP在Service实现事务管理、日志记录等横切操作。

IOC和AOP是通过什么机制实现的?

  1. IOC容器利用反射动态加载类、创建对象实例、调用对象方法,反射允许在运行时检查类、方法、属性等信息
  2. AOP是依赖注入,容器负责管理应用程序组件之间的依赖关系(构造函数注入、属性注入、方法注入)
  3. IOC容器采用工厂模式来管理对象的创建和生命周期,容器作为工厂负责实例化Bean并管理他们的生命周期,将Bean的实例化过程交给容器来管理
  4. 通常使用BeanFactory 或 ApplicationContext来管理Bean(BeanFactory是IOC容器的基本形式;ApplicationContext是BeanFactory的扩展)

AOP的实现机制

AOP基于动态代理,在运行时动态生成代理对象。允许开发者在运行时指定代理对象的接口和行为。

动态代理是一种在运行时动态代理对象的机制,用于在不修改原始类的情况下对方法调用进行拦截和增强。

  • 基于JDK的动态代理:使用java.lang.reflect.Proxy类 和 java.lang.reflect.InvocationHandler接口实现,需要代理的类实现一个多或多个接口。每个动态代理都必须实现InvocationHandler接口,并且每个代理的实例都关联到一个handler。当通过代理对象调用一个方法时,这个方法的调用就会被转发为由InvocationHandler接口的invoke()方法来进行调用。
  • 基于CGLIB的动态代理:当被代理的类没有实现接口时,会使用CGLIB生成一个被代理的子类作为代理(CGLIB是一个第三方代码生成库,通过继承方式实现代理)

AOP的目的是对面向对象思维的补充。

AOP更像是对不支持多继承的弥补,除开对象的主要特征(”强共性“)被抽象成一条继承链路,对于”弱共性“,AOP可以统一对他们进行抽象和集中处理。

怎么理解IOC?

IOC:控制反转,是一种设计思想,传统的javase是通过new的方式创建对象,是程序主动创建依赖对象

  • 创建对象:原来是new,现在是spring容器创建
  • 初始化对象:原来是对象自己通过构造器 或 setter方法赋值,现在是spring容器自动注入
  • 销毁对象:原来是直接给对象赋null或执行一些销毁操作,现在是spring容器管理生命周期负责销毁对象

AOP常用注解

  • @Aspect:定义切面,标注在切面类上
  • @Pointcut:定义切点,标注在方法上,用于指定连接点
  • @Before:方法执行之前通知
  • @After:方法执行之后通知
  • @Around:方法执行前后都通知
  • @AfterReturning:方法执行后返回结果后通知
  • @AfterThrowing:方法抛出异常后通知
  • @Advice:通知的类型,可以替代@Befort、@After

什么是反射?有什么应用场景?

在程序运行的状态下,任何一个类,都可以获取这个类的所有属性和方法;任何一个对象,都可以调用它的任意属性和方法。反射允许在运行时获取类的信息并动态操作对象。

  • 反射允许程序在运行时获取类的完整结构信息
  • 通过反射API动态的创建对象实例
  • 在运行时动态地调用对象的方法
  • 允许运行时访问和修改对象的字段值
  1. 依赖注入:开发者通过xml配置文件或注解的方式声明组件之间的依赖关系,程序启动时,spring容器会扫描这些配置或注解,利用反射来实例化Bean
  2. 动态代理的实现:AOP,允许开发者自定义切面,而不需要修改业务逻辑代码。

Spring解决循环依赖

三种循环依赖的情况:

  1. 通过构造方法进行依赖注入时产生的循环依赖问题
  2. 通过setter方法进行依赖注入且在多例模式下产生的循环依赖问题
  3. 通过setter方法进行依赖注入切实在单例模式下产生的循环依赖问题

只有第三种循环依赖被spring解决了,其他两种在遇到循环依赖问题是,spring都会产生异常。

实现步骤:

  1. 实例化Bean:在实例化Bean时会创建一个空的Bean对象,存入一级缓存中
  2. 属性赋值:开始对Bean进行属性赋值,如果发现循环依赖,会把当前Bean对象提前暴露给后续需要依赖的Bean(通过提前暴露的方式解决循环依赖)
  3. 初始化Bean:完成属性赋值后,将Bean进行初始化,并将其放入二级缓存中
  4. 注入依赖:继续对Bean机械能依赖注入,如果发现循环依赖,就从二级缓存中获取以及完成初始化的Bean实例。

使用了三级缓存解决循环依赖(三个Map):

一级缓存:存储完整的Bean

二级缓存:已经被实例化,但是还没完全初始化的Bean。避免在多重循环依赖(A依赖B,A依赖C)的情况下重复创建动态代理

三级缓存:

  • 缓存的是函数接口,lambda表达式把方法传进去(把Bean的实例和Bean的名字传进去)
  • AOP创建
  • 不会立即调用(会在第二次getBean(A)时才会调用三级缓存=>创建动态代理),如果在实例化后立即调用BeanA,而不去管BeanA的循环依赖问题,那么所有的Bean都会在实例化的时候调用动态代理,Spring还是希望在初始化的时候创建动态代理
  1. 创建BeanA,如果一级缓存中有BeanA,就直接返回
  2. 实例化BeanA,保存到三级缓存中
  3. 对BeanA属性赋值,发现依赖BeanB,就去一级缓存中找BeanB。
  4. 发现一级缓存没有BeanB,就把BeanB也加入三级缓存
  5. 对BeanB属性赋值,发现依赖BeanA,就去一级缓存中找BeanA;继续去二级缓存找,也没有;继续去三级缓存中找BeanA,发现BeanA在三级缓存中,创建动态代理,把BeanA放入二级缓存(避免重复创建)
  6. BeanB的属性BeanA赋值成功,就把BeanB放入一级缓存中,移除掉BeanB的二三级缓存
  7. 把BeanB的完整对象返回给BeanA,BeanA里的属性BeanB就已经赋值结束,BeanA也成功返回

Spring的注解

@Autowired、@Component、@Configuration、@Bean、@Service、@Repository、@Controller

Spring的事务什么时候会失效?

  1. 一个事务方法发生未捕获的异常,异常未被处理或传播到事务边界之外
  2. 事务传播属性设置不当:多个事务之间存在事务嵌套,事务传播属性配置不正确,导致事务失效
  3. 多数据源的事务管理:事务管理没有正确配置或存在多个@Transactional注解时
  4. 跨方法调用事务:一个事务内部调用另一个方法,这个被调用的方法没有@Transactional注解,外层事务可能失效
  5. 事务在非public的方法中会失效

Bean是否单例?

Bean默认都是单例的,每个Bean的实例只会被创建一次,并存储在Spring容器的缓存中,便于在后续的请求中重复使用。

也可以通过设置scope属性为prototype来将Bean设置为多例模式

如果Bean单例是有状态的,可以在使用时考虑线程安全问题。

在bean加载/销毁前后,如果想要实现某些逻辑,怎么做?

  1. 在xml配置中,通过init-method 和 destory-method属性来指定Bean初始化后和销毁前需要调用的方法

  2. 实现InitializingBean 和 DisposableBean接口,并分别实现afterPropertiesSet()和destory()方法

  3. 使用@PostConstruct和@PreDestory注解

  4. 使用@Bean的initMethod和destoryMethod属性

SpringMVC

MVC分层?

  • 视图(view):为用户提供使用界面
  • 模型(model):代表一个存储数据的对象(pojo),可以带有逻辑,主要用于承载数据,并对用户提交请求进行计算的模块。(分为业务处理Bean【Service、Mapper】、数据承载Bean【User类】)
  • 控制器(controller):把用户的请求转发给model处理,并根据model的计算结果向用户提供相应响应。

c5bd467e69d942eab1d74acdadec6495.jpeg

  1. 用户通过view向页面服务器发请求
  2. 服务端controller收到请求后对请求进行解析,找到对应的model后,将请求交给model处理
  3. 将处理结果再交给controller
  4. 根据处理结果返回给view,页面选然后发给用户

SpringBoot

SpringBoot自动装配原理

SpringBoot自动装配原理是基于Spring的条件话配置和@EnableAutoConfiguration注解实现的,允许开发者在项目中引入相关的依赖,根据这些以来自动配置应用程序的上下文和功能。

SpringBoot在启动时会扫描外部引用jar包中的META-INF/spring.factories文件,将文件中的配置信息加载到Spring容器中,并执行类中的各种操作。对外部jar来说,只需要按照SpringBoot定义的标准,就能把自己的功能装配进SpringBoot

SpringBoot的注解

@SpringBootApplication、@Controller、@RestController、@Service、@Repository、@Component、@Autowired、@Value、@RequestMapping、@GetMapping…、@Configuration

Mybatis

Mybatis里的#和$的区别?

  • 处理#{}时,会创建预编译的SQL语句,将SQL中的#{}替换成?,在执行SQL时会为预编译SQL中的占位符(?)赋值,预编译的SQL语句执行效率高,也可以防止SQL注入
  • 处理&{}时,只是创建简单的SQL语句,在执行SQL语句时,先将参数直接拼到SQL里,不能防止SQL注入,因为参数是直接拼接到SQL语句中的,如果参数未经过验证,可能会产生安全问题。

SpringCloud

SpringCloud和SpringBoot的区别?

SpringBoot是用于构建单个Spring应用的框架,SpringCloud是用于构建分布式文件系统中微服务架构的工具(提供了服务注册与发现、负载均衡、断路器、网关等)

通过SpringBoot构建微服务应用,用SpringCloud来实现微服务架构中的各种功能。

常用的组件:

  1. 注册中心(nacos):解决了”如何发现新节点以及检查各节点运行状态的问题“
  2. 负载均衡:解决了”如何发现服务以及负载均衡如何实现“,微服务在调用时,先通过注册中心看看该服务拥有哪些节点,注册中心将可用的节点返回给服务调用者(服务发现)。因为服务高可用性的要求,服务调用者会接收到多个节点,必须从中进行选择,所以服务调用者必须内置负载均衡器。
  3. 服务通信:解决了”服务间如何进行消息通信的问题“,提供了Feign和RestTemplate两种技术屏蔽底层的实现细节
  4. 配置中心:解决了”如何集中管理个节点配置文件的问题“,通过部署配置中心服务器,将各节点配置文件从服务中剥离,集中转存到配置中心。
  5. 集中日志管理:解决了“如何收集个节点日志并统一管理”
  6. 服务保护:解决了“如何对系统进行链路保护,避免服务雪崩的问题”,微服务之间相互调用,如果某个微服务出现高延迟,就需要引入服务保护组件来实现微服务的快速降级。

如何实现一个用户的请求一直位于同一台服务器上

通过“一致性哈希算法”来实现,根据请求的客户端ip通过哈希算法得到一个数值,利用该数值取模映射出对应的后端服务器,这样就能保证同一个客户端或想通参数的请求每次都使用同一台服务器。

服务熔断和服务降级

服务熔断:对微服务雪崩效应的一种链路保护机制。

比如服务A调用B,B调用C。

如果C出现了问题,那么随着B发往C的请求不断增加,B也会出现问题;随着A发往B的请求不断增加,由于B无法给正确的响应,那么A也会出现问题。

所以当链路中某个微服务不可用或相应的时间太长,应该要进行服务熔断,不再有该节点微服务的调用,快速返回错误的相应信息。

服务降级:在服务压力剧增的时候,根据业务使用情况对服务和页面有策略的不处理或采用一些简单的方式进行处理,从而释放服务器的资源以保证核心业务的正常高效运行。(舍小保大)

服务降级是从整个系统的负荷情况触发,对某些负荷比较高的情况,为了预防某些功能出现负荷过载或响应慢的情况,在内部暂时舍弃对一些非核心接口和数据的请求,而直接返回一个提前准备好的fallback处理错误信息,虽然提供的是一个有损的服务,但是却保证了整个系统的稳定性和可用性。