当前位置: 首页 > news >正文

【Spring框架】——原理篇

Spring框架核心解析

本文将深入探讨Spring框架中最为核心和常见的面试题,旨在帮助开发者构建系统性的知识体系,从容应对技术面试。内容如下:

一、Spring-单例Bean是线程安全的吗?

这是一个经典的面试题,答案是:默认情况下,Spring框架中的单例Bean不是线程安全的。

  • 成员方法值是可变的,需要考虑线程安全
  • 成员变量是没有线程安全的,因为他是无状态的类(service和DAO),无状态就是判断这个成员变量能不能被修改,而他们不能被修改,所以没有线程安全。这是说明了在依赖注入时为什么推荐使用构造方法注入。
  • 方法参数是局部局部变量,之后不会改变,使用没有线程安全的顾虑

原因分析: Spring的IoC容器默认会管理单例(Singleton)作用域的Bean。这意味着容器中每个Bean定义只存在一个共享的实例。当多个线程同时访问这个单例Bean时,如果该Bean包含可变的成员变量(状态),并且没有采取任何同步措施,就会发生线程安全问题(如数据覆盖、脏读、不可重复读等)。

示例:

@Service
public class UnsafeCounterService {private int count = 0; // 可变的状态public void increment() {count++; // 非原子操作,线程不安全}public int getCount() {return count;}
}

多个线程同时调用 increment() 方法会导致最终的 count 值小于预期。

如何保证线程安全?

  1. 无状态Bean(首选方案):将Bean设计为无状态的,即不包含任何可变的成员变量。所有操作都通过局部变量或参数来完成。这是最推荐的方式,因为无状态Bean本质就是线程安全的。
  2. 使用同步机制:在方法或代码块上加 synchronized 关键字,或者使用 ReentrantLock。这会带来性能开销,且需要仔细设计锁的粒度。
  3. 使用ThreadLocal:将可变状态封装到 ThreadLocal 中,这样每个线程都会拥有该变量的一个副本。适用于与线程绑定的数据(如用户会话信息)。
  4. 改变Bean的作用域:将Bean的作用域改为 prototype(原型),这样每次请求都会创建一个新的实例,自然不存在共享问题。但这通常不是解决此类问题的最佳实践,因为它会创建大量对象,增加GC压力。

结论:Spring不负责处理单例Bean的线程安全问题,这需要开发者根据业务场景自行保证。编写无状态的Bean是最佳实践。

二、Spring-AOP场景

1. AOP的核心概念

增强(Advice),在Spring AOP的官方中文文档中常被直译为“通知”,但“增强”这个词更能体现其本质含义。

简单来说:增强就是你想要注入到目标类连接点(特定方法)上的那段“增强”功能的代码。

  • Aspect(切面):封装横切关注点的类,包含 Advice 和 Pointcut
  • Joinpoint(连接点):程序执行过程中明确的点,如方法调用、异常抛出。在Spring AOP中,连接点总是代表方法的执行
  • Advice(通知):切面在特定连接点执行的动作。类型包括:
    • @Before:前置通知
    • @AfterReturning:返回后通知
    • @AfterThrowing:异常通知
    • @After:后置通知(无论成功与否)
    • @Around:环绕通知(功能最强大,可以控制是否执行目标方法)
  • Pointcut(切点):匹配连接点的表达式,定义了通知何时被执行。
  • Weaving(织入):将切面应用到目标对象并创建代理对象的过程。

2. Spring AOP的实现原理 Spring AOP默认使用动态代理

  • 如果目标对象实现了接口,则使用 JDK 动态代理
  • 如果目标对象没有实现任何接口,则使用 CGLIB 字节码生成

可以通过配置 @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用CGLIB。

3. AOP的应用场景

  • 日志记录
  • 事务管理@Transactional 的本质就是AOP)
  • 安全认证和授权
  • 性能监控(方法执行时间)
  • 全局异常处理
  • 缓存

一、日志记录

1、comtroller类

2、aop类

3、自定义注解

二、事务管理

小结:

三、Spring-事务失效的场景

非检查异常:Runtime异常

事务失效是开发中常见的坑,主要场景如下:

  1. 事务方法非public修饰@Transactional 只能用于 public 方法上,应用于其他方法时,事务不会失效,但也不会报错。
  2. 自调用(Same Class Invocation):类内部的方法调用该类的事务方法,如 this.methodB()。这是因为事务基于代理,自调用绕过了代理对象。
    • 解决方案:注入自身的代理对象 @Autowired private MyService self; 然后调用 self.methodB()
  3. 异常被捕获未抛出:事务管理依赖于异常来回滚。如果在方法中捕获了异常 (try-catch) 却没有重新抛出 (throw new RuntimeException(e)),事务将不会回滚。
  4. 抛出的异常类型错误:默认情况下,@Transactional 只在遇到运行时异常 (RuntimeException) 和错误 (Error) 时回滚。遇到受检异常 (Exception) 不会回滚。
    • 解决方案:使用 @Transactional(rollbackFor = Exception.class) 指定回滚的异常类型。
  5. 数据源未配置事务管理器:必须配置 PlatformTransactionManager Bean(如 DataSourceTransactionManager),否则事务不生效。
  6. 在非Spring管理的类中使用:例如,在普通的new出来的对象中使用 @Transactional 注解是无效的。
  7. 数据库引擎不支持事务:如MySQL的MyISAM引擎不支持事务,需要改用InnoDB引擎。

四、Spring-Bean的生命周期

代码演示:

Spring Bean的生命周期非常复杂,但时主要掌握以下核心步骤:

  1. 实例化(Instantiate):通过构造器或工厂方法创建Bean实例。
  2. 属性赋值(Populate):为Bean的属性注入值(依赖注入)。
  3. BeanPostProcessor前置处理:调用 BeanPostProcessor.postProcessBeforeInitialization() 方法。
  4. 初始化(Initialize)
    • 如果Bean实现了 InitializingBean 接口,则调用 afterPropertiesSet() 方法。
    • 如果Bean配置了自定义的 init-method(或 @PostConstruct 注解),则调用此方法。
  5. BeanPostProcessor后置处理:调用 BeanPostProcessor.postProcessAfterInitialization() 方法(AOP代理对象通常在此阶段生成)。
  6. Bean就绪:此时Bean已被完全初始化,存放在Spring容器中,可以被应用程序使用。
  7. 销毁(Destroy)
    • 容器关闭时,如果Bean实现了 DisposableBean 接口,则调用 destroy() 方法。
    • 如果配置了自定义的 destroy-method(或 @PreDestroy 注解),则调用此方法。

简化记忆实例化 -> 属性填充 -> (前置处理) -> 初始化 -> (后置处理/AOP) -> 使用 -> 销毁

五、Spring-Bean的循环依赖(循环引用)

循环依赖是指两个或多个Bean相互持有对方,形成了闭环依赖。例如:A依赖B,B也依赖A。

Spring如何解决循环依赖?(基于三级缓存) Spring通过三级缓存机制巧妙地解决了单例Bean通过setter方法注入的循环依赖问题。

  1. 三级缓存

    • singletonObjects(一级缓存):存放已经完全初始化好的单例Bean。
    • earlySingletonObjects(二级缓存):存放提前曝光的、早期的Bean对象(已实例化但未初始化)。
    • singletonFactories(三级缓存):存放Bean的ObjectFactory,用于生成早期引用。
  2. 解决流程(以A和B循环依赖为例)

    • 创建A,实例化A,将A的ObjectFactory放入三级缓存
    • 为A注入属性时,发现需要B。
    • 开始创建B,实例化B,将B的ObjectFactory放入三级缓存
    • 为B注入属性时,发现需要A。此时从三级缓存中拿到A的ObjectFactory,并调用 getObject() 方法获取A的早期引用(可能是一个代理对象)。将A的早期引用放入二级缓存,并从三级缓存移除。B成功注入A。
    • B完成属性填充、初始化,成为一个完整的Bean,放入一级缓存,并清除二、三级缓存。
    • A接着注入B,完成后续的初始化步骤。A完成后也放入一级缓存。

一级缓存

二级缓存

A对象放到SingletonObjects后earlysingletonobject的原始A对象就会消失。

但是如果刚开始A是代理对象,而最后存储到单例池的却不是代理对象,所以会用到三级缓存

三级缓存

无法解决的循环依赖场景

三级缓存可以去解决初始化过程中的循环依赖,不能解决构造函数产生的循环依赖

@lazy:延迟加载,意思就是什么时候需要对象,再进行ban对象的创建,并不是说在去实例化的时候直接把对象给注入进来

  • 构造器注入的循环依赖:因为实例化Bean时需要构造器参数,而另一个Bean还未创建,无法提前曝光,会抛出 BeanCurrentlyInCreationException
  • 多例(Prototype)作用域的Bean循环依赖:Spring不管理多例Bean的完整生命周期,无法进行曝光和缓存。

小结:

要点:1、三级缓存就已经包含了,一二级缓存,直接解释三级缓存就行了

          2、三级缓存中为什么还需要二级缓存,因为对象都是单例的,都通过三级缓存创建的话可能会产生多例对象

           3.pring解决的主要都是set注入:set注入是构造函授调用之后才完成的,循环依赖当然可以解决 因为对象都创建好了 只是算个半成品

懒加载就是先不去加载B,先把A对象创建出来,等到真正使用B的时候再去实例化B,,这是A已经实例化完毕了

六、SpringMVC-执行流程

视图阶段:

handler:解释当前某一个控制器的某一个方法

前后端分离

相当于化简了ModelAndView阶段,因为前后端分离不用去视图解析

SpringMVC的核心流程清晰且经典,其核心是 DispatcherServlet

七、SpringBoot-自动配置原理

SpringBoot的自动配置是其核心魔法,其原理基于 @SpringBootApplication 注解和条件化配置。

  1. 入口注解:@SpringBootApplication 它是一个复合注解,包含:

    • @SpringBootConfiguration:表明是配置类。
    • @ComponentScan:组件扫描。
    • @EnableAutoConfiguration开启自动配置的核心
  2. @EnableAutoConfiguration 的作用 它利用 @Import 导入了 AutoConfigurationImportSelector

  3. AutoConfigurationImportSelector 这个类的核心方法是 selectImports(),它会从 classpath 下的 META-INF/spring.factories 文件中读取所有 EnableAutoConfiguration 键对应的自动配置类全名(如 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration)。

  4. 自动配置类(XxxAutoConfiguration) 这些配置类是自动配置的蓝本。它们使用 @Configuration 声明,并利用大量的 @ConditionalOnXxx 条件注解(如 @ConditionalOnClass, @ConditionalOnProperty, @ConditionalOnMissingBean)来进行条件化配置。

    • @ConditionalOnClass(DataSource.class):表示类路径下存在 DataSource 类(即引入了数据库驱动)时,配置才生效。
    • @ConditionalOnMissingBean(DataSource.class):表示容器中不存在 DataSource Bean时,才自动配置一个默认的。
  5. 总结流程

    • SpringBoot启动时加载 META-INF/spring.factories 文件中的自动配置类。
    • 根据当前项目的环境、类路径、已有Bean等条件(@ConditionalOnXxx),决定哪些配置类生效。
    • 生效的配置类会向容器中添加预先定义好的Bean。
    • “约定大于配置”:开发者只需引入starter依赖,SpringBoot就会自动配置好大部分组件。如果想自定义,只需自己声明一个Bean即可覆盖默认配置。

八、Spring框架常见的注解

类别注解说明
Spring核心@Component通用组件注解,被其标注的类会被组件扫描。
@Repository标注DAO层组件。
@Service标注Service层业务逻辑组件。
@Controller标注Web层控制器组件。
@Autowired自动依赖注入(按类型)。
@Qualifier与 @Autowired 配合,按名称注入。
@Value注入普通属性或SpEL表达式。
@Configuration声明一个类为配置类。
@Bean在配置类中声明一个Bean。
@Scope指定Bean的作用域(singleton, prototype等)。
@Profile指定配置或Bean在哪个环境下激活。
@PostConstruct标注初始化方法。
@PreDestroy标注销毁方法。
Spring AOP@Aspect声明一个切面。
@Pointcut声明一个切点表达式。
@Before/@After/ etc.声明通知。
Spring事务@Transactional声明式事务管理。
SpringMVC@RequestMapping映射Web请求(路径、方法等)。
@GetMapping/@PostMapping@RequestMapping 的简写。
@RequestParam获取请求参数。
@PathVariable获取URL路径中的变量。
@RequestBody获取请求体,反序列化为对象。
@ResponseBody将方法返回值直接写入响应体。
@RestController@Controller + @ResponseBody
@ExceptionHandler声明异常处理方法(Controller内)。
@ControllerAdvice全局异常处理。
SpringBoot@SpringBootApplication核心启动注解。
@EnableAutoConfiguration开启自动配置。
@ConditionalOnXxx一系列条件注解,用于自动配置。

希望这篇全面解析能帮助您更好地理解和掌握Spring框架的核心知识。

http://www.dtcms.com/a/454538.html

相关文章:

  • 网站建设问题分类和排除方法分析建设一个网站的工作方案
  • 网站设计例子wordpress固定链接html
  • 营销型网站的建站步骤是什么意思推广引流网站
  • 计算机-网络基础
  • 企业cms建站免费制作个人网页
  • UVa 237 Monitoring Wheelchair Patients
  • Linux I2C 子系统
  • 中国建设银行官网站预约纪念币网站关键词优化到首页后怎么做
  • 专业营销型网站建设费用货源网
  • Windows 11基本操作
  • 建设行业个人信息网站哔哩哔哩免费安装
  • 广州网站app制作公司c 网站做微信收款功能
  • 扒完网站代码之后怎么做模板安卓app开发需要学什么
  • 上海800做网站桂林昨晚发生的新闻
  • 【软件安装】
  • 正宗营销型网站建设用自己的手机做网站
  • 网站域名备案与不备案的区别可以免费看国外短视频app
  • 网站标题的设置方法北京建王环境发展有限公司
  • 什么是电子商务网站的建设做博客网站需要工具吗
  • 做电影种子下载网站违法吗东莞网站(建设信科网络)
  • 网站建设后怎么写银川seo公司
  • 湛江网站搜索引擎推广外贸流程询盘
  • 12. Pandas 数据合并与拼接(concat 与 merge)
  • 23ICPC澳门站补题
  • 怎样做淘宝的导购网站推广宣传片制作网站
  • 51zwd做网站淘宝网中国站电脑版登录
  • 快速搭建网站 开源软件开发工程师多少钱一个月
  • vue知识点-列表渲染+key
  • 花茶网站模板装修全包
  • discuz企业网站模板陕西住房城乡建设网站