Chiriri's blog Chiriri's blog
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)

Iekr

苦逼后端开发
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)
  • Java基础

  • 框架

    • Spring
      • 说说Spring 里用到了哪些设计模式?
      • IOC 和 AOP
        • 谈谈你对IOC 和 AOP 的理解?他们的实现原理是什么?
        • Spring AOP 和 AspectJ AOP 有什么区别?
        • 什么是IOC、DI 及其两者的优点 、 有哪几种注入方式?
        • 谈谈对AOP理解?
        • AOP 的应用场景有哪些呢
        • JDK 动态代理和 CGLIB 代理有什么区别?
        • 动态代理有哪些?
        • Spring 在使用AOP时一般是怎么选择动态代理的方式
        • AOP原理
        • IOC原理
      • Bean
        • FactoryBean 和 BeanFactory有什么区别?
        • SpringBean的生命周期
        • bean有几种作用域
        • 单例bean和原型bean
        • 常用的 Bean 映射工具有哪些
        • Spring框架中的单例bean是线程安全的吗?
        • Spring是怎么解决循环依赖的?
        • 生成代理对象产生的循环依赖
        • 使用@DependsOn产生的循环依赖
        • 多例循环依赖
        • 构造器循环依赖
        • 什么是循环依赖 ?
        • 三级缓存
        • 原理执行流程
        • 源码解读
        • 代码入口
        • 第一层
        • 第二层
        • 第三层
        • 返回第二层
        • 返回第一层
        • 为什么要有三级缓存 ?
        • 我们能干掉第二级缓存么 ?
        • 为什么要三级缓存?二级不行吗?
        • Spring如何对bean进行管理
      • Spring 事务
        • Spring 事务实现原理
        • Spring事务的种类
        • Spring的事务传播行为
        • Spring中的隔离级别
        • 事务失效的场景?
      • 为什么要用Spring及其优点?
      • Spring有哪些模块组成?
      • Spring有几种配置方式?
      • Spring的自动装配
      • Spring Boot 的主要优点
      • Spring Boot Starters 是什么
      • Spring Boot 的启动流程
      • @SpringBootApplication 注解
      • SpringBoot 自动配置的原理
      • Spring Boot 中常用注解
      • @Autowired和@Resource之间的区别
      • Spring Boot 如何读取配置文件
        • @value
        • @ConfigurationProperties
        • @PropertySource
      • Spring Boot 加载配置文件的优先级
      • Spring Boot 参数检验
        • 检验注解
        • 检验请求体
        • 检验请求参数
      • Spring如何处理线程并发问题?
      • Spring 核心类有哪些?各有什么作用?
      • SpringMVC 的原理
      • Spring、Spring MVC、Spring Boot 之间什么关系?
    • Dubbo
    • Spring Cloud
    • Mybatis
  • 数据库

  • 消息队列

  • 408

  • 大数据

  • 面试
  • 框架
Iekr
2021-12-26
目录

Spring

# Spring

# 说说 Spring 里用到了哪些设计模式?

tag: momenta 、 数字马力 、 贝壳 、 小天才 、 得物 、 招行

count:8

as:springboot 自动装配用到了那些设计模式

什么是模板模式,有什么应用场景,Spring 中有哪些模板模式的应用?

  • 单例模式 :Spring 中的 Bean 默认情况下都是单例的。每次获取相同恶的类的时候,获取的对象是同一个。不会就行多次的创建。单例模式还分为饿汉式(主动创建单例)及懒汉式(需要时再创建单例)。

  • 工厂模式 :工厂模式主要是通过 BeanFactory(延迟注入)和 ApplicationContext(完全注入) 来生产 Bean 对象,用来创建对象的实例;我们无需知道类如何创建,直接从工厂中获取即可。

  • 代理模式 :最常见的 AOP 的实现方式就是通过代理来实现,Spring 主要是使用 JDK 动态代理和 CGLIB 代理。

    • 当要代理的对象实现接口的时候,我们就会使用 JDK Proxy 来生成代理对象。
    • 如果代理的对象没有实现接口的话,我们就回使用 CGLIB 生成一个被代理对象的子类作为代理对象。
  • 模板方法模式 :主要是一些对数据库操作的类用到,比如 JdbcTemplate、JpaTemplate,因为查询数据库的建立连接、执行查询、关闭连接几个过程,非常适用于模板方法。

  • 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如 Spring 中 listener 的实现 --ApplicationListener。

# IOC 和 AOP

# 谈谈你对 IOC 和 AOP 的理解?他们的实现原理是什么?

tag: 携程 、 数字马力 、 泓齐网科 、 国遥新天地 、 京东 、 软通 、 得物 、 亚信 、 海隆 、 税友 、 途虎 、 用友 、 瑞幸 、 云之重器 、 快手 、 传音 、 金蝶 、 淘天 、 工行 、 招行 、 华创云鼎 、 阿里 、 饿了么 、 用友 、 七牛云 、 联想 、 字节 、 途牛 、 哔哩哔哩 、 货拉拉

count:48

as:ioc 与 aop 区别

Spring 的核心技术(AOP 和 IOC)

IOC 的好处

注解和 aop 有什么关系

IOC 叫做控制反转,指的是通过 Spring 来管理对象的创建、配置和生命周期,这样相当于把控制权交给了 Spring,不需要人工来管理对象之间复杂的依赖关系,这样做的好处就是解耦。在 Spring 里面,主要提供了 BeanFactory 和 ApplicationContext 两种 IOC 容器,通过他们来实现对 Bean 的管理。

AOP 叫做面向切面编程,他是一个编程范式,目的就是提高代码的模块性。Srping AOP 基于动态代理的方式实现,如果是实现了接口的话就会使用 JDK 动态代理,反之则使用 CGLIB 代理,Spring 中 AOP 的应用主要体现在 事务、日志、异常处理等方面,通过在代码的前后做一些增强处理,可以实现对业务逻辑的隔离,提高代码的模块化能力,同时也是解耦。Spring 主要提供了 Aspect 切面、JoinPoint 连接点、PointCut 切入点、Advice 增强等实现方式。

# Spring AOP 和 AspectJ AOP 有什么区别?

Spring AOP 基于动态代理实现,属于运行时增强。

AspectJ 则属于编译时增强,主要有 3 种方式:

  1. 编译时织入:指的是增强的代码和源代码我们都有,直接使用 AspectJ 编译器编译就行了,编译之后生成一个新的类,他也会作为一个正常的 Java 类装载到 JVM。
  2. 编译后织入:指的是代码已经被编译成 class 文件或者已经打成 jar 包,这时候要增强的话,就是编译后织入,比如你依赖了第三方的类库,又想对他增强的话,就可以通过这种方式。

img

  1. 加载时织入:指的是在 JVM 加载类的时候进行织入。

总结下来的话,就是 Spring AOP 只能在运行时织入,不需要单独编译,性能相比 AspectJ 编译织入的方式慢,而 AspectJ 只支持编译前后和类加载时织入,性能更好,功能更加强大。

# 什么是 IOC、DI 及其两者的优点 、 有哪几种注入方式?

tag: 美团 、 亚信 、 海隆 、 传音 、 工行 、 淘天 、 快手 、 得物 、 数字马力 、 汇川技术 、 TCL

count:15

as:ioc 的注入方式有几种(分别是什么)

如何去初始化一个 bean 的流程有了解不?

spring 依赖注入,会注入哪些类

SpringBoot 如何实现依赖注入?

Spring bean 是怎么注入的?

Spring 初始化 Bean 前要做什么?有几种方式

  • IOC:控制反转,把创建对象的控制权利由代码转移到 spring 的配置文件中。 最直观的表达就是,IOC 让对象的创建不用去 new 了,可以由 spring 自动生产,使用 java 的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
  • DI:依赖注入,在程序运行期间,由外部容器动态地将依赖对象注入到组件中。简单定义就是当一个对象需要另一个对象时,可以把另一个对象注入到对象中去。 优点就是把应用的代码量降到最低,达到松散耦合度。

注入的方式:

  • 构造注入
  • Set 注入
  • 接口注入

Spring 提供以下几种集合的配置元素:

<list> 类型用于注入一列值,允许有相同的值。

<set> 类型用于注入一组值,不允许有相同的值。

<map> 类型用于注入一组键值对,键和值都可以为任意类型。

<props> 类型用于注入一组键值对,键和值都只能为 String 类型。

# 谈谈对 AOP 理解?

tag: 快手 、 美团 、 数字马力

count:6

as:

aop 面向切面编程,关键在于代理模式,Spring AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。动态代理可以减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。


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

  • JDK 代理:基于接口的代理,不支持类的代理。核心 InvocationHandler 接口和 Proxy 类,InvocationHandler 通过 invoke () 方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy 利用 InvocationHandler 动态创建一个符合某一接口的的实例,生成目标类的代理对象。
  • CGLIB 动态代理:如果代理类没有实现 InvocationHandler 接口(或者说是基于父子类的),那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现 AOP。CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用 CGLIB 做动态代理的

# AOP 的应用场景有哪些呢

tag: 淘天 、 华创云鼎 、 阿里 、 一嗨租车 、 字节 、 数字马力 、 苏小研 、 英飞拓

count:10

as:AOP 实际应用场景,好处,spring 中如何实现?

AOP,在哪里用到了,有什么注解

1、日志记录,在记录日志的时候,我创建个自定义注解,aop 的切面就是这个注解,使用环绕通知在方法中,我们通过传入的参数(joinPoint)获取对应的类和方法,从而获取前端传来的参数和其他主要信息,实现记录日志的效果。

2、权限验证

3、效率检查(个人在代码上,喜欢用注解 + 切面,实现校验,redis 分布式锁等功能)

4、事务管理(Spring 的事务就是用 AOP 实现的)

# JDK 动态代理和 CGLIB 代理有什么区别?

tag: 数字马力 、 TCL 、 茄子科技

count:4

as:AOP Bean 容器代理模式

JDK 动态代理主要是针对类实现了某个接口,AOP 则会使用 JDK 动态代理。他基于反射的机制实现,生成一个实现同样接口的一个代理类,然后通过重写方法的方式,实现对代码的增强。

而如果某个类没有实现接口,AOP 则会使用 CGLIB 代理。他的底层原理是基于 asm 第三方框架,通过修改字节码生成成成一个子类,然后重写父类的方法,实现对代码的增强。

# 动态代理有哪些?

tag: 瑞幸 、 得物

count:3

as:动态代理有哪些?有什么区别?

动态代理有了解过吗 有哪些动态代理类

# Spring 在使用 AOP 时一般是怎么选择动态代理的方式

tag: 京东

count:2

as:

# AOP 原理

tag: Meta App 、 得物 、 税友 、 传音 、 联想 、 字节 、 贝壳 、 数字马力 、 快手 、 百度 、 一嗨租车

count:18

as:如何设计 aop 程序?

aop 原理和实现

aop 底层是基于哪个设计模式?讲下动态代理模式

AOP 的动态代理底层怎么实现的?为什么一个注解就能实现了?如果有父类或者上层接口那么具体在哪?

AOP,切面如何实现

# IOC 原理

tag: 京东 、 工行 、 快手 、 招行 、 数字马力

count:6

as:ioc 如何实现的

如果自己 new 一个类的对象怎么放到容器中,自己实现 spring 容器效果

ioc 怎么拿到一个 bean

# Bean

# FactoryBean 和 BeanFactory 有什么区别?

tag: 用友 、 理想 、 一嗨租车

count:3

as:

BeanFactory 是 Bean 的工厂, ApplicationContext 的父类,IOC 容器的核心,负责生产和管理 Bean 对象。

FactoryBean 是 Bean,可以通过实现 FactoryBean 接口定制实例化 Bean 的逻辑,通过代理一个 Bean 对象,对方法前后做一些操作。

# SpringBean 的生命周期

tag: 软通 、 用友 、 云之重器 、 数字马力 、 美团 、 快手 、 百度 、 信也 、 闪送 、 贝壳 、 得物 、 百度 、 哔哩哔哩

count:17

as:springBean 加载过程

Bean 生命周期中涉及的接口和方法

Bean 的执行流程

image-20241009152717260

SpringBean 生命周期简单概括为 4 个阶段:

  1. 通过 BeanDefinition 获取 bean 的定义信息。
  2. 实例化通过构造函数创建 bean,可以将当前的 bean 理解为一个空壳。,创建一个 Bean 对象
  3. 填充属性进行依赖注入,对 bean 中的属性进行赋值。,为属性赋值
  4. 初始化
    • 如果实现了 xxxAware 接口,通过不同类型的 Aware 接口拿到 Spring 容器的资源,处理 Aware 接口,也就是一些以 Aware 结尾的接口,就比如:beanNameAware,beanFactoryAware,applicationContextAware。如果实现了个接口的话,我们需要重写一些方法。
    • 如果实现了 BeanPostProcessor 接口,则会回调该接口的 postProcessBeforeInitialzation 和 postProcessAfterInitialization 方法,加载 BeanPostProcessor 对象,并执行处理器的前置初始化方法 (PostProcessorAfterInitiazilation (opens new window) 方法)。
    • 如果配置了 init-method 方法,则会执行 init-method 配置的方法,执行初始化方法,包括 IntializingBean 和自定义的初始化方法。
  5. 加载 BeanPostProcessor 对象,并执行处理器的后置初始化方法 (PostProcessorBeforeInitiazilation 方法)。在此后置处理器中我们可以通过 aop 对原始的 bean 做增强也就是进行代理,代理就包括 JVM 代理和 CGLIB 代理。
  6. 销毁
    • 容器关闭后,如果 Bean 实现了 DisposableBean 接口,则会回调该接口的 destroy 方法
    • 如果配置了 destroy-method 方法,则会执行 destroy-method 配置的方法

# bean 有几种作用域

tag: 小天才 、 一嗨租车

count:2

as:Spring 的为什么默认是单例 Bean,你觉得这样设计有什么好处

Spring 容器中的 bean 可以分为 5 个范围:

  1. singleton(单例):默认,每个容器中只有一个 bean 的实例,单例的模式由 BeanFactory 自身来维护。
  2. prototype(原型):每次注入或者通过 Spring 应用上下文获取的时候,都会为每一个 bean 请求提供一个实例。
  3. request(请求):为每一个网络请求创建一个实例,在请求完成以后,bean 会失效并被垃圾回收器回收。
  4. session(会话):与 request 范围类似,确保每个 session 中有一个 bean 的实例,在 session 过期后,bean 会随之失效。
  5. global-session:全局作用域,global-session 和 Portlet 应用相关。当你的应用部署在 Portlet 容器中工作时,它包含很多 portlet。如果你想要声明让所有的 portlet 共用全局的存储变量的话,那么这全局变量需要存储在 global-session 中。全局作用域与 Servlet 中的 session 作用域效果相同。

他们是什么时候创建的:

  • 一个单例的 bean, 而且 lazy-init 属性为 false(默认), 在 Application Context 创建的时候构造
  • 一个单例的 bean,lazy-init 属性设置为 true, 那么,它在第一次需要的时候被构造。
  • 其他 scope 的 bean, 都是在第一次需要使用的时候创建

他们是什么时候销毁的:

  • 单例的 bean 始终 存在与 application context 中,只有当 application 终结的时候,才会销毁
  • 和其他 scope 相比,Spring 并没有管理 prototype 实例完整的生命周期,在实例化,配置,组装对象交给应用后,Spring 不再管理。只要 bean 本身不持有对另一个资源(如数据库连接或会话对象)的引用,只要删除了对该对象的所有引用或对象超出范围,就会立即收集垃圾。
  • Request: 每次客户端请求都会创建一个新的 bean 实例,一旦这个请求结束,实例就会离开 scope, 被垃圾回收。
  • Session: 如果用户结束了他的会话,那么这个 bean 实例会被 GC.

# 单例 bean 和原型 bean

tag: boss

count:1

as:

# 常用的 Bean 映射工具有哪些

我们经常在代码中会对一个数据结构封装成 DO、SDO、DTO、VO 等,而这些 Bean 中的大部分属性都是一样的,所以使用属性拷贝类工具可以帮助我们节省大量的 set 和 get 操作。

常用的 Bean 映射工具有:Spring BeanUtils、Apache BeanUtils、MapStruct、ModelMapper、Dozer、Orika、JMapper 。

由于 Apache BeanUtils 、Dozer 、ModelMapper 性能太差,所以不建议使用。MapStruct 性能更好而且使用起来比较灵活,是一个比较不错的选择。

# Spring 框架中的单例 bean 是线程安全的吗?

tag: 快手

count:2

as:Spring 中创建对象是单例模式,单例模式如何保证线程安全问题?

不安全,Spring 框架中的单例 bean 不是线程安全的。谈到 beans 线程安全那么就看 bean 是否有多种状态,如果始终只有一种状态 就是线程安全的,否则需要自己去保证线程的安全,可以采用将 singleton 变为 prototype。

在 spring 框架中有个注解叫 @Scope 可以设置 bean 的状态,默认就是 singleton 也就是单例。

bean 进行注入的时都是无状态的,其不会被修改的。所以没有线程安全的问题。但是如果 bean 中有成员变量时就可能会有线程安全的问题,因为该成员变量可能会被多个线程修改,为了解决这个问题我们可以加锁或将 bean 设置为多例。(@Scope 设置为 prototype)

# Spring 是怎么解决循环依赖的?

tag: 美团 、 携程 、 软通 、 用友 、 快手 、 七牛云 、 联想 、 美的 、 数字马力 、 途牛 、 贝壳 、 招行 、 字节 、 百度 、 阿里

count:24

as:Springboot 能否解决循环依赖注入,如何解决?

spring 中解决循环依赖为什么要用三级缓存,二级为什么不行呢 二级缓存无法解決动态代理产生 bean 的循环依赖,需要三级缓存

循环依赖咋解决?用构造器方法和用 setter 注入都可以解决吗?

项目中如果出现循环依赖问题,说明是 spring 默认无法解决的循环依赖,要看项目的打印日志,属于哪种循环依赖。目前包含下面几种情况:

img

# 生成代理对象产生的循环依赖

这类循环依赖问题解决方法很多,主要有:

  1. 使用 @Lazy 注解,延迟加载
  2. 使用 @DependsOn 注解,指定加载先后关系
  3. 修改文件名称,改变循环依赖类的加载顺序

# 使用 @DependsOn 产生的循环依赖

这类循环依赖问题要找到 @DependsOn 注解循环依赖的地方,迫使它不循环依赖就可以解决问题。

# 多例循环依赖

这类循环依赖问题可以通过把 bean 改成单例的解决。

# 构造器循环依赖

这类循环依赖问题可以通过使用 @Lazy 注解解决。

首先,Spring 解决循环依赖有两个前提条件:

  1. 不全是构造器方式的循环依赖
  2. 必须是单例

基于上面的问题,我们知道 Bean 的生命周期,本质上解决循环依赖的问题就是三级缓存,通过三级缓存提前拿到未初始化的对象。

image-20241009153329372

第一级缓存:用来保存实例化、初始化都完成的对象

第二级缓存:用来保存实例化完成,但是未初始化完成的对象

第三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象。缓存创建 bean 的 factory,这些 factory 用于创建代理对象和普通对象。

image-20241009153223527

假设一个简单的循环依赖场景,A、B 互相依赖。

image-20241009153340792

A 对象的创建过程:

  1. 创建对象 A,实例化的时候把 A 对象工厂放入三级缓存

image-20241009153353005

  1. A 注入属性时,发现依赖 B,转而去实例化 B
  2. 同样创建对象 B,注入属性时发现依赖 A,一次从一级到三级缓存查询 A,从三级缓存通过对象工厂拿到 A,把 A 放入二级缓存,同时删除三级缓存中的 A,此时,B 已经实例化并且初始化完成,把 B 放入一级缓存。

img

  1. 接着继续创建 A,顺利从一级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除二级缓存中的 A,同时把 A 放入一级缓存
  2. 最后,一级缓存中保存着实例化、初始化都完成的 A、B 对象

img

因此,由于把实例化和初始化的流程分开了,所以如果都是用构造器的话,就没法分离这个操作,所以都是构造器的话就无法解决循环依赖的问题了。

# 什么是循环依赖 ?

一个或多个对象之间存在直接或间接的依赖关系,这种依赖关系构成一个环形调用,有下面 3 种方式。

image-20231007190738778

我们看一个简单的案例,对应情况 2 中两个对象之间相互依赖。

@Service
public class Louzai1 {

    @Autowired
    private Louzai2 louzai2;

    public void test1() {
    } 
}

@Service
public class Louzai2 {
    @Autowired
    private Louzai1 louzai1;

    public void test2() {
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 三级缓存

tag: 小红书

count:1

as:

  • 第一级缓存: singletonObjects ,用于保存实例化、注入、初始化完成的 bean 实例;
  • 第二级缓存: earlySingletonObjects ,用于保存实例化完成的 bean 实例;
  • 第三级缓存: singletonFactories ,用于保存 bean 创建工厂,以便后面有机会创建代理对象。

缓存源码

img

执行逻辑:

  • 先从 “第一级缓存” 找对象,有就返回,没有就找 “二级缓存”;
  • 找 “二级缓存”,有就返回,没有就找 “三级缓存”;
  • 找 “三级缓存”,找到了,就获取对象,放到 “二级缓存”,从 “三级缓存” 移除。

# 原理执行流程

我把 “情况 2” 执行的流程分解为下面 3 步,是不是和 “套娃” 很像 ?

image-20231007191216845

整个执行逻辑如下:

  1. 在第一层中,先去获取 A 的 Bean,发现没有就准备去创建一个,然后将 A 的代理工厂放入 “三级缓存”(这个 A 其实是一个半成品,还没有对里面的属性进行注入),但是 A 依赖 B 的创建,就必须先去创建 B
  2. 在第二层中,准备创建 B,发现 B 又依赖 A,需要先去创建 A
  3. 在第三层中,去创建 A,因为第一层已经创建了 A 的代理工厂,直接从 “三级缓存” 中拿到 A 的代理工厂,获取 A 的代理对象,放入 “二级缓存”,并清除 “三级缓存”
  4. 回到第二层,现在有了 A 的代理对象,对 A 的依赖完美解决(这里的 A 仍然是个半成品),B 初始化成功;
  5. 回到第一层,现在 B 初始化成功,完成 A 对象的属性注入,然后再填充 A 的其它属性,以及 A 的其它步骤(包括 AOP),完成对 A 完整的初始化功能(这里的 A 才是完整的 Bean)
  6. 将 A 放入 “一级缓存”

# 源码解读

该案例 Spring 的版本为 5.2.15.RELEASE

# 代码入口

image-20231007191521496

image-20231007191538876

这里需要多跑几次,把前面的 beanName 跳过去,只看 louzai1

image-20231007191600493

image-20231007191618834

# 第一层

image-20231007191216845

进入 doGetBean() ,从 getSingleton() 没有找到对象,进入创建 Bean 的逻辑

image-20231007192257113

image-20231007192325055

进入 doCreateBean() 后,调用 addSingletonFactory()

image-20231007192404442

往三级缓存 singletonFactories 塞入 louzai1 的工厂对象

image-20231007192422703

image-20231007192433370

进入到 populateBean() ,执行 postProcessProperties() ,这里是一个策略模式,找到下图的策略对象

image-20231007192453156

正式进入该策略对应的方法

image-20231007192507179

下面都是为了获取 louzai1 的成员对象,然后进行注入

image-20231007193003313

image-20231007193018810

image-20231007193032585

image-20231007193041999

进入 doResolveDependency (),找到 louzai1 依赖的对象名 louzai2

image-20231007193100391

需要获取 louzai2 的 bean,是 AbstractBeanFactory 的方法

image-20231007193117550

正式获取 louzai2 的 bean

image-20231007193142905

到这里,第一层套娃基本结束,因为 louzai1 依赖 louzai2,下面我们进入第二层套娃

# 第二层

image-20231007191216845

获取 louzai2 的 bean,从 doGetBean (),到 doResolveDependency (),和第一层的逻辑完全一样,找到 louzai2 依赖的对象名 louzai1。

前面的流程全部省略,直接到 doResolveDependency ()。

image-20231007193311379

正式获取 louzai1 的 bean

image-20231007193328196

到这里,第二层套娃结束,因为 louzai2 依赖 louzai1,所以我们进入第三层套娃

# 第三层

image-20231007191216845

获取 louzai1 的 bean,在第一层和第二层中,我们每次都会从 getSingleton () 获取对象,但是由于之前没有初始化 louzai1 和 louzai2 的三级缓存,所以获取对象为空。

image-20231007193402850

image-20231007193412219

到了第三层,由于第三级缓存有 louzai1 数据,这里使用三级缓存中的工厂,为 louzai1 创建一个代理对象,塞入二级缓存

image-20231007193431964

这里就拿到了 louzai1 的代理对象,解决了 louzai2 的依赖关系,返回到第二层

# 返回第二层

返回第二层后,louzai2 初始化结束,这里就结束了么?二级缓存的数据,啥时候会给到一级呢?

还记得在 doGetBean () 中,我们会通过 createBean () 创建一个 louzai2 的 bean,当 louzai2 的 bean 创建成功后,我们会执行 getSingleton (),它会对 louzai2 的结果进行处理

image-20231007193511982

我们进入 getSingleton (),会看到下面这个方法

image-20231007193525352

这里就是处理 louzai2 的 一、二级缓存的逻辑,将二级缓存清除,放入一级缓存

image-20231007193542594

# 返回第一层

与返回第二层相同,louzai1 初始化完毕后,会把 louzai1 的二级缓存清除,将对象放入一级缓存。

image-20231007193627474

到这里,所有的流程结束,我们返回 louzai1 对象

# 为什么要有三级缓存 ?

我们先说 “一级缓存” 的作用,变量命名为 singletonObjects,结构是 Map<String, Object>,它就是一个单例池,将初始化好的对象放到里面,给其它线程使用,如果没有第一级缓存,程序不能保证 Spring 的单例属性。

“三级缓存” 的作用,变量命名为 singletonFactories,结构是 Map<String, ObjectFactory<?>>,Map 的 Value 是一个对象的代理工厂,所以 “三级缓存” 的作用,其实就是用来存放对象的代理工厂。

  • 那这个对象的代理工厂有什么作用呢?

答:它的主要作用是存放半成品的单例 Bean,目的是为了 “打破循环”

回到上面的情况 2 中两个对象相互依赖的情况

在创建 A 对象时,会把实例化的 A 对象存入 “三级缓存”,这个 A 其实是个半成品,因为没有完成依赖属性 B 的注入,所以后面当初始化 B 时,B 又要去找 A,这时就需要从 “三级缓存” 中拿到这个半成品的 A,打破循环。

  • 为什么 “三级缓存” 不直接存半成品的 A,而是要存一个代理工厂呢 ?

答:是因为 AOP。

在解释这个问题前,我们可以看一下这个代理工厂的源码,让大家有一个更清晰的认识

直接找到创建 A 对象时,把实例化的 A 对象存入 “三级缓存” 的代码,直接用前面的两幅截图。

image-20231007194438466

image-20231007194456944

下面我们主要看这个对象工厂是如何得到的,进入 getEarlyBeanReference () 方法

image-20231007191835400

image-20231007191941755

image-20231007194522855

image-20231007194531539

通过上图,我们知道这个对象工厂的作用:

  • 如果 A 有 AOP,就创建一个代理对象;
  • 如果 A 没有 AOP,就返回原对象。

那 “二级缓存” 的作用就清楚了,就是用来存放对象工厂生成的对象,这个对象可能是原对象,也可能是个代理对象。

# 我们能干掉第二级缓存么 ?

@Service
public class A {

    @Autowired
    private B b;

    @Autowired
    private C c;

    public void test1() {
    }
}

@Service
public class B {
    @Autowired
    private A a;

    public void test2() {
    }
}

@Service
public class C {

    @Autowired
    private A a;

    public void test3() {
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

根据上面的套娃逻辑,A 需要找 B 和 C,但是 B 需要找 A,C 也需要找 A。

假如 A 需要进行 AOP,因为代理对象每次都是生成不同的对象,如果干掉第二级缓存,只有第一、三级缓存:

  • B 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A1。
  • C 找到 A 时,直接通过三级缓存的工厂的代理对象,生成对象 A2。

看到问题没?你通过 A 的工厂的代理对象,生成了两个不同的对象 A1 和 A2,所以为了避免这种问题的出现,我们搞个二级缓存,把 A1 存下来,下次再获取时,直接从二级缓存获取,无需再生成新的代理对象。

所以 “二级缓存” 的目的是为了避免因为 AOP 创建多个对象,其中存储的是半成品的 AOP 的单例 bean。

如果没有 AOP 的话,我们其实只要 1、3 级缓存,就可以满足要求。

3 级缓存的作用:

  • 一级缓存:为 “Spring 的单例属性” 而生,就是个单例池,用来存放已经初始化完成的单例 Bean;
  • 二级缓存:为 “解决 AOP” 而生,存放的是半成品的 AOP 的单例 Bean;
  • 三级缓存:为 “打破循环” 而生,存放的是生成半成品单例 Bean 的工厂方法。

# 为什么要三级缓存?二级不行吗?

可以只使用二级缓存来解决循环依赖吗?

在没有 Aop 的时候就可以使用一级和三级缓存来解决依赖。但是呢,如果存在 aop 的问题,那就必须使用三级缓存,因为在二级缓存中可以保证即使存在多个半成品 bean 的引用时,始终会返回相同的代理对象。避免 Bean 有多个代理对象。

不可以,主要是为了生成代理对象。

因为三级缓存中放的是生成具体对象的匿名内部类,他可以生成代理对象,也可以是普通的实例对象。

使用三级缓存主要是为了保证不管什么时候使用的都是一个对象。

假设只有二级缓存的情况,往二级缓存中放的显示一个普通的 Bean 对象, BeanPostProcessor 去生成代理对象之后,覆盖掉二级缓存中的普通 Bean 对象,那么多线程环境下可能取到的对象就不一致了。

img

# Spring 如何对 bean 进行管理

tag: boss

count:1

as:Spring 如何对 bean 进行管理,有哪些 bean

# Spring 事务

tag: 小米 、 字节 、 快手 、 京东 、 联想 、 万得

count:6

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring 是无法提供事务功能的。

# Spring 事务实现原理

tag: 小米 、 瑞幸 、 云之重器 、 字节 、 用友 、 数字马力 、 快手 、 数字马力 、 一嗨租车

count:12

as:Spring 是如何实现事务管理的?

Spring 事务和 MySQL 事务的区别。Spring 事务能保证 Redis 的回滚吗

@transactional 底层原理

SpringBoot 中事务基于什么实现的

spring 注入→代理模式→事务注解→A 调用被事务修饰的 B 过

Spring 的事务管理机制通过 AOP 和 PlatformTransactionManager 实现,提供了声明式和编程式两种方式来管理事务。声明式事务管理通过 @Transactional 注解来简化事务的配置,而编程式事务管理提供了更细粒度的控制。无论是哪种方式,Spring 的事务管理都能有效地支持事务的一致性和隔离性,提高应用程序的可靠性和性能。

# Spring 事务的种类

tag: 同余

count:2

as:

spring 支持编程式事务管理和声明式事务管理两种方式:

  1. 编程式事务管理使用 TransactionTemplate。
  2. 声明式事务管理建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过 @Transactional 注解的方式,便可以将事务规则应用到业务逻辑中。 声明式事务管理要优于编程式事务管理,这正是 spring 倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

# Spring 的事务传播行为

tag: 字节 、 邦盛 、 百度 、 万得

count:5

as:

spring 事务的传播行为说的是,当多个事务同时存在的时候,spring 如何处理这些事务的行为。

传播行为 描述‎
PROPAGATION_REQUIRED 支持当前事务;如果不存在,请创建一个新。‎
PROPAGATION_REQUIRES_NEW ‎创建新事务,暂停当前事务(如果存在)。‎
PROPAGATION_NESTED 如果当前事务存在,则在嵌套事务中执行,否则行为‎‎类似于 PROPAGATION_REQUIRED‎‎。‎
PROPAGATION_NEVER 不支持当前事务;如果当前事务存在,则引发异常。‎
PROPAGATION_SUPPORTS ‎支持当前事务;如果不存在,则以非事务方式执行。‎
PROPAGATION_NOT_SUPPORTED 不支持当前事务;而是始终以非事务性方式执行。‎
PROPAGATION_MANDATORY 支持当前事务;如果当前事务不存在,则引发异常。‎
  1. PROPAGATION_REQUIRED :如果当前不存在事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
  2. PROPAGATION_SUPPORTS :支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
  3. PROPAGATION_MANDATORY :支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
  4. PROPAGATION_REQUIRES_NEW :创建新事务,无论当前存不存在事务,都创建新事务。
  5. PROPAGATION_NOT_SUPPORTED :以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  6. PROPAGATION_NEVER :以非事务方式执行,如果当前存在事务,则抛出异常。
  7. PROPAGATION_NESTED :如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按 REQUIRED 属性执行。

# Spring 中的隔离级别

tag: 同余 、 数字马力 、 货拉拉

count:3

as:

隔离级别 描述‎
ISOLATION_DEFAULT 使用基础数据存储的默认隔离级别。
ISOLATION_READ_COMMITTED 指示防止脏读;可能发生非可重复读取和幻像读取。
ISOLATION_READ_UNCOMMITTED ‎指示可能发生脏读、非可重复读取和幻像读取。‎
ISOLATION_REPEATABLE_READ ‎指示防止脏读和非可重复读;可能会发生幻像读取。‎
ISOLATION_SERIALIZABLE ‎指示防止脏读、非可重复读取和幻像读取。‎
  1. ISOLATION_DEFAULT :这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
  2. ISOLATION_READ_UNCOMMITTED :读未提交,允许另外一个事务可以看到这个事务未提交的数据。表示可能发生脏读、非可重复读取和幻像读取。‎此级别允许一个事务更改的行在提交该行中的任何更改之前由另一个事务读取(“脏读取”)。如果回滚任何更改,则第二个事务将检索到无效行。
  3. ISOLATION_READ_COMMITTED :读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。解决脏读问题,可能发生非可重复读取和幻像读取。‎
  4. ISOLATION_REPEATABLE_READ :可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。行锁,‎表示防止脏读和非可重复读;可能会发生幻像读取。‎
  5. ISOLATION_SERIALIZABLE :一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。表锁,防止脏读,不可重复读外,还避免了幻读。‎

# 事务失效的场景?

tag: 瑞幸 、 云之重器 、 哈啰 、 快手 、 卓望 、 数字马力 、 哔哩哔哩 、 一嗨租车

count:9

as:为什么作用在非 public 方法上会使事务失效?

  1. 在出现异常后,方法中 try/catch 了该异常并且没有主动抛出异常,这时候就会导致事务失效。解决方法:在方法 try/catch 异常后手动的抛出异常。(就会导致事务不知道出现异常了)
  2. 抛出检查异常时会导致事务失效,spring 中的事务只会对 runtime 异常进行回滚。就比如:Not found Exception 就是检查异常。解决方法:在 @transactional 中设置属性 rollbackFor = Exception.class 。使得事务会对所有的异常进行回滚。
  3. 非 public 方法会导致事务失效。解决方法:将方法的作用域该为 public。
  4. 在非事务方法中调用了事务的方法,此时就会导致事务失效。(就比如某给没有加事务注解的方法调用了加了事务注解的方法)
  5. 回滚异常类型不匹配。(我们可能会设置需要进行回滚的异常,就是 rollback 的值,如果抛出的异常类型不匹配就会导致事务失效)
  6. 事务的传播行为错误。(就比如在事务方法中调用了其他的事务方法,初始化如果其他的时候方法设置开启新事务的话,在其事务成功后,就不会参与外部事务的回滚操作)
  7. 同步方法调用:如果一个事务方法内部调用了另一个事务方法,但这两个方法属于同一个对象实例,那么内部调用不会通过代理对象,从而事务管理不会生效。
  8. 跨事务操作:如果事务方法内部调用了另一个服务,而这个服务没有被事务管理,那么可能导致事务操作失败。

# 为什么要用 Spring 及其优点?

tag: 快手

count:2

as:spring 特性有哪些

Spring 是重量级企业开发框架 Enterprise JavaBean(EJB) 的替代品,Spring 为企业级 Java 开发提供了一种相对简单的方法,通过 依赖注入 和 面向切面编程 ,用简单的 Java 对象(Plain Old Java Object,POJO) 实现了 EJB 的功能

虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的(需要大量 XML 配置) 。

  • spring 属于低侵入式设计,代码的污染极低;
  • spring 的 DI 机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
  • Spring 提供了 AOP 技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
  • spring 对于主流的应用框架提供了集成支持。

# Spring 有哪些模块组成?

  • Spring Core:核心类库,提供 IOC 服务;
  • Spring Context:提供框架式的 Bean 访问方式,以及企业级功能(JNDI、定时任务等);
  • Spring AOP:AOP 服务;
  • Spring DAO:对 JDBC 的抽象,简化了数据访问异常的处理;
  • Spring ORM:对现有的 ORM 框架的支持;
  • Spring Web:提供了基本的面向 Web 的综合特性,例如多方文件上传;
  • Spring MVC:提供面向 Web 应用的 Model-View-Controller 实现。

# Spring 有几种配置方式?

tag: 海隆

count:1

as:spring 的配置文件的格式

# Spring 的自动装配

tag: 快手 、 税友 、 途虎 、 用友 、 得物

count:5

as:自动装配实现原理

在 spring 中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用 autowire 来配置自动装载模式。

在 Spring 框架 xml 配置有 5 种自动装配:

  1. no:默认的方式是不进行自动装配的,通过手工设置 ref 属性来进行装配 bean。
  2. byName:通过 bean 的名称进行自动装配,如果一个 bean 的 property 与另一 bean 的 name 相同,就进行自动装配。
  3. byType:通过参数的数据类型进行自动装配。
  4. constructor:利用构造函数进行装配,并且构造函数的参数通过 byType 进行装配。
  5. autodetect:自动探测,如果有构造方法,通过 construct 的方式自动装配,否则使用 byType 的方式自动装配。

基于注解的方式:

使用 @Autowired 注解来自动装配指定的 bean。在使用 @Autowired 注解之前需要在 Spring 配置文件进行配置, <context:annotation-config /> 。在启动 spring IoC 时,容器自动装载了一个 AutowiredAnnotationBeanPostProcessor 后置处理器,当容器扫描到 @Autowied、@Resource或@Inject 时,就会在 IoC 容器自动查找需要的 bean,并装配给该对象的属性。在使用 @Autowired 时,首先在容器中查询对应类型的 bean:

  • 如果查询结果刚好为一个,就将该 bean 装配给 @Autowired 指定的数据;
  • 如果查询的结果不止一个,那么 @Autowired 会根据名称来查找;
  • 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用 required=false。

# Spring Boot 的主要优点

tag: 国遥新天地 、 阿里 、 超聚变 、 格创东智 、 贝壳 、 卓望 、 数字马力 、 新华三 、 招行 、 招行 、 茄子科技 、 亚信 、 百度 、 哔哩哔哩

count:23

as:SpringBoot 对比 Spring 有哪些区别

SpringBoot 对 Java Web 开发来说有哪些优势?

  1. 开发基于 Spring 的应用程序很容易。
  2. Spring Boot 项目所需的开发或工程时间明显减少,通常会提高整体生产力。
  3. Spring Boot 不需要编写大量样板代码、XML 配置和注释。
  4. Spring 引导应用程序可以很容易地与 Spring 生态系统集成,如 Spring JDBC、Spring ORM、Spring Data、Spring Security 等。
  5. Spring Boot 中大量配置都是默认的,这样能够快速构建一个项目,同时如果有额外的需求,也可以修改默认配置。
  6. Spring Boot 的起步依赖中就有 Tomcat 服务器的 jar 包,不需要我们手动与 Tomcat 打交道。只要像运行普通 Java 程序一样就能运行 Spring Boot Web 项目,如 Tomcat 和 Jetty,可以轻松地开发和测试 web 应用程序。
  7. Spring Boot 提供命令行接口 (CLI) 工具,用于开发和测试 Spring Boot 应用程序,如 Java 或 Groovy。
  8. Spring Boot 提供了多种插件,可以使用内置工具 (如 Maven 和 Gradle) 开发和测试 Spring Boot 应用程序。

# Spring Boot Starters 是什么

tag: 百度 、 华为

count:3

as:stater 的实现

spring 的 starter 原理

starter 是怎么启动的?

Spring Boot Starters 是一系列依赖关系的集合,因为它的存在,项目的依赖之间的关系对我们来说变的更加简单了。

在没有 Spring Boot Starters 之前,我们开发 REST 服务或 Web 应用程序时;如果我们需要使用像 Spring MVC,Tomcat 和 Jackson 这样的库,我们需要手动一个一个添加依赖。但是,有了 Spring Boot Starters 我们只需要一个只需添加一个 spring-boot-starter-web 一个依赖就可以了,这个依赖包含的子依赖中包含了我们开发 REST 服务需要的所有依赖。

# Spring Boot 的启动流程

tag: 美团 、 4399 、 boss 、 招行 、用友 ``

count:7

as:springboot 启动过程

SpringBoot 类加载过程

主要的流程就几个步骤:

  1. 准备环境,根据不同的环境创建不同的 Environment
  2. 准备、加载上下文,为不同的环境选择不同的 Spring Context ,然后加载资源,配置 Bean
  3. 初始化,这个阶段刷新 Spring Context ,启动应用
  4. 最后结束流程

# @SpringBootApplication 注解

tag: 数字马力 、 麦米电气 、 亚信 、 美团 、 货拉拉 、 百度 、 哔哩哔哩

count:9

as:springboot 的 4 个核心注解

SpringBootApplication 注解,分为哪三个注解,详细介绍?

我们 SpringBoot 启动类通常为以下编写方式编写

package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
   ......
}
1
2
3
4
5
6
7
8
9
10
11
12
13

而其中的 @SpringBootConfiguration 注解

package org.springframework.boot;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

}
1
2
3
4
5
6
7
8

可以看出大概可以把 @SpringBootApplication 看作是 @Configuration 、 @EnableAutoConfiguration 、 @ComponentScan 注解的集合。根据 SpringBoot 官网,这三个注解的作用分别是:

  • @EnableAutoConfiguration :启用 SpringBoot 的自动配置机制
  • @ComponentScan : 扫描被 @Component (@Service,@Controller) 注解的 bean,注解默认会扫描该类所在的包下所有的类。
  • @Configuration :允许在上下文中注册额外的 bean 或导入其他配置类,即用来直接或间接注册 Bean 到 IOC 容器中的

# SpringBoot 自动配置的原理

tag: 携程 、 得物 、 腾讯 、 明朝万达 、 数字马力 、 招行 、 百度 、 货拉拉 、 快手 、 美的

count:13

as:spring boot 的核心配置是什么?

spring.factories 作用

@autoconfiguration 实现原理

在 Spring 程序 main 方法中,添加 @SpringBootApplication 或者 @EnableAutoConfiguration 会自动去 maven 中读取每个 starter 中的 spring.factories 文件,该文件里配置了所有需要被创建的 Spring 容器中的 bean

@EnableAutoConfiguration 注解通过 Spring 提供的 @Import 注解导入了 AutoConfigurationImportSelector 类( @Import 注解可以导入配置类或者 Bean 到当前类中)。该选择器会到 jar 中的 /META-INF/spring.factories 中按照条件加载对应的类,并将该类配置到 ioc 中。

这里面添加的注解就包括:@ConditionOnClass 表示当某个类存在时才进行类加载。@ConditionOnMissingBean 表示当某个 bean 不存在的时候才进行类加载。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

AutoConfigurationImportSelector 类中 getCandidateConfigurations 方法会将所有自动配置类的信息以 List 的形式返回。这些配置信息会被 Spring 容器作 bean 来管理。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
            getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
            + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}
1
2
3
4
5
6
7

自动配置信息有了,那么自动配置还差什么呢?

@Conditional 注解。

  • @ConditionalOnClass :其中指定的类必须存在时,才会生效。
  • @ConditionalOnBean :容器中有指定的 Bean 时,才会生效,等等都是对 @Conditional 注解的扩展。

拿 Spring Security 的自动配置举个例子: SecurityAutoConfiguration 中导入了 WebSecurityEnablerConfiguration 类, WebSecurityEnablerConfiguration 源代码如下:

@Configuration
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {

}
1
2
3
4
5
6
7
8

WebSecurityEnablerConfiguration 类中使用 @ConditionalOnBean 指定了容器中必须还有 WebSecurityConfigurerAdapter 类或其实现类。所以,一般情况下 Spring Security 配置类都会去实现 WebSecurityConfigurerAdapter ,这样自动将配置就完成了。

# Spring Boot 中常用注解

tag: 美团 、 苏小研 、 招行 、 恒生 、 亚信

count:9

as:springboot 怎么声明一个 Bean

Spring Bean 相关:

  • @Autowired : 自动导入对象到类中,被注入进的类同样要被 Spring 容器管理。
  • @RestController : @RestController 注解是 @Controller 和 @ResponseBody 的合集,表示这是个控制器 bean, 并且是将函数的返回值直 接填入 HTTP 响应体中,是 REST 风格的控制器。
  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个 Bean 不知道属于哪个层,可以使用 @Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。

处理常见的 HTTP 请求类型:

  • @GetMapping : GET 请求、
  • @PostMapping : POST 请求。
  • @PutMapping : PUT 请求。
  • @DeleteMapping : DELETE 请求。

前后端传值:

  • @RequestParam 以及 @Pathvariable : @PathVariable 用于获取路径参数, @RequestParam 用于获取查询参数。
  • @RequestBody :用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且 Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用 HttpMessageConverter 或者自定义的 HttpMessageConverter 将请求的 body 中的 json 字符串转换为 Java 对象。

image-20241009161643542

image-20241009161651110

image-20241009161658345

# @Autowired 和 @Resource 之间的区别

tag: 得物 、 数字马力 、 超聚变 、 亚信

count:7

as:如果使用 autowired 希望 byname 怎么做

autowird 注解和 resource 注解的区别

Autowired 变量都是单例吗?

Autowired 和 Resource 区别

@Autowired 和 @Resource 可用于:构造函数、成员变量、Setter 方法

  • @Autowired 默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(如果允许为 null,可以设置它 required 属性为 false),如果我们想使用按照名称装配,可以结合 @Qualifier 注解一起使用;
  • @Resource 默认是按照名称来装配注入的,只有当找不到与名称匹配的 bean 才会按照类型来装配注入。可以通过 name 属性指定,如果没有指定 name 属性,当注解标注在字段上,即默认取字段的名称作为 bean 名称寻找依赖对象,当注解标注在属性的 setter 方法上,即默认取属性名作为 bean 名称寻找依赖对象

# Spring Boot 如何读取配置文件

tag: 快手 、 恒生 、 百度

count:4

as:

# @value

使用 @Value ("${property}") 读取比较简单的配置信息:

@Value("${wuhan2020}")
String wuhan2020;
1
2

# @ConfigurationProperties

我们要读取的配置文件 application.yml 内容如下:

library:
  location: 湖北武汉加油中国加油
  books:
    - name: 天才基本法
      description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。
    - name: 时间的秩序
      description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。
    - name: 了不起的我
      description: 如何养成一个新习惯?如何让心智变得更成熟?如何拥有高质量的关系? 如何走出人生的艰难时刻?
1
2
3
4
5
6
7
8
9

通过 @ConfigurationProperties 读取并与 bean 绑定

LibraryProperties 类上加了 @Component 注解,我们可以像使用普通 bean 一样将其注入到类中使用。

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@ConfigurationProperties(prefix = "library")
@Setter
@Getter
@ToString
class LibraryProperties {
    private String location;
    private List<Book> books;

    @Setter
    @Getter
    @ToString
    static class Book {
        String name;
        String description;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

这个时候你就可以像使用普通 bean 一样,将其注入到类中使用:

import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ReadConfigPropertiesApplication implements InitializingBean {

    private final LibraryProperties library;

    public ReadConfigPropertiesApplication(LibraryProperties library) {
        this.library = library;
    }

    public static void main(String[] args) {
        SpringApplication.run(ReadConfigPropertiesApplication.class, args);
    }

    @Override
    public void afterPropertiesSet() {
        System.out.println(library.getLocation());
        System.out.println(library.getBooks());    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# @PropertySource

@PropertySource 读取指定的 properties 文件

import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource("classpath:website.properties")
@Getter
@Setter
class WebSite {
    @Value("${url}")
    private String url;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

使用

@Autowired
private WebSite webSite;

System.out.println(webSite.getUrl());
1
2
3
4

# Spring Boot 加载配置文件的优先级

  1. 项目根目录中的 config 中的配置文件
  2. resources 下 config 中的配置文件
  3. resources 中的配置文件

image-20231115212307913

# Spring Boot 参数检验

# 检验注解

JSR 提供的校验注解:

  • @Null 被注释的元素必须为 null
  • @NotNull 被注释的元素必须不为 null
  • @AssertTrue 被注释的元素必须为 true
  • @AssertFalse 被注释的元素必须为 false
  • @Min (value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max (value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @DecimalMin (value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @DecimalMax (value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size (max=, min=) 被注释的元素的大小必须在指定的范围内
  • @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
  • @Past 被注释的元素必须是一个过去的日期
  • @Future 被注释的元素必须是一个将来的日期
  • @Pattern (regex=,flag=) 被注释的元素必须符合指定的正则表达式

Hibernate Validator 提供的校验注解:

  • @NotBlank (message =) 验证字符串非 null,且长度必须大于 0
  • @Email 被注释的元素必须是电子邮箱地址
  • @Length (min=,max=) 被注释的字符串的大小必须在指定的范围内
  • @NotEmpty 被注释的字符串的必须非空
  • @Range (min=,max=,message=) 被注释的元素必须在合适的范围内

# 检验请求体

我们在需要验证的参数上加上了 @Valid 注解,如果验证失败,它将抛出 MethodArgumentNotValidException 。默认情况下,Spring 会将此异常转换为 HTTP Status 400(错误请求)。

@RestController
@RequestMapping("/api")
public class PersonController {

    @PostMapping("/person")
    public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
        return ResponseEntity.ok().body(person);
    }
}
1
2
3
4
5
6
7
8
9

# 检验请求参数

首先在类上加上 Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。

@RestController
@RequestMapping("/api")
@Validated
public class PersonController {

    @GetMapping("/person/{id}")
    public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {
        return ResponseEntity.ok().body(id);
    }

    @PutMapping("/person")
    public ResponseEntity<String> getPersonByName(@Valid @RequestParam("name") @Size(max = 6,message = "超过 name 的范围了") String name) {
        return ResponseEntity.ok().body(name);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Spring 如何处理线程并发问题?

在一般情况下,只有无状态的 Bean 才可以在多线程环境下共享,在 Spring 中,绝大部分 Bean 都可以声明为 singleton 作用域,因为 Spring 对一些 Bean 中非线程安全状态采用 ThreadLocal 进行处理,解决线程安全问题。

ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

  • 同步机制采用了 **“时间换空间”** 的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。
  • 而 ThreadLocal 采用了 “空间换时间” 的方式。ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。

# Spring 核心类有哪些?各有什么作用?

  • BeanFactory:产生一个新的实例,可以实现单例模式
  • BeanWrapper:提供统一的 get 及 set 方法
  • ApplicationContext: 提供框架的实现,包括 BeanFactory 的所有功能

# SpringMVC 的原理

tag: 税友 、 图森 、 阿里 、 百度 、 快手 、 一嗨租车

count:13

as:springmvc 的执行流程

Spring mvc 调用流程是怎么样的?handler 讲讲?

image-20231024213607249

客户端发起请求到 dispatchservlet,dispatchservlet 根据请求调用 handlerMappering,解析出一个对应的 handler,解析出的 handler 由 handlerAdapter 适配器去处理完成之后得到对应的 moudleAndView, 最后将 view 返回给客户

image-20241009161329689

jsp 版本:

  1. 用户发送请求到 DispatchServlet。
  2. dispatchServlet 调用处理器映射器 HandlerMappering,处理器就会去 Controller 中找到对应的方法通过映射的路径,然后将处理器执行链返回给 dispatchServlet。
  3. dispatchServlet 调用处理器适配器 HandlerAdaptor 找到对应的处理器,该处理器就会处理对应的参数和处理返回值,最终会返回 ModelAndView 给 DispatchServlet。
  4. Dispatch 调用 ViewResolver 视图解析器,并将 ModelAndView 传入,最终返回 View 给 dispatchServlet,通过 View 渲染视图。(就比如:JSP)

前后端分离版本:

  1. 用户发送请求搭配 DispatchServlet。

  2. DispatchServlet 调用处理器映射器 HandlerMappering, 会去 Controller 中找到对应的方法,最终处理器返回执行链给 DispatchServlet。

  3. DispatchServlet 会调用处理器适配器 HandlerAdaptor 找到对应的处理器,该处理器就会处理对应的方法的参数和处理返回值,在方法上添加了 @ReponseBody。

  4. 通过 HttpMessageConverter 将数据转为 Json 返回。

# Spring、Spring MVC、Spring Boot 之间什么关系?

tag: 金蝶

count:2

as:

编辑 (opens new window)
上次更新: 2025/01/01, 10:09:39
设计模式
Dubbo

← 设计模式 Dubbo→

最近更新
01
k8s
06-06
02
进程与线程
03-04
03
计算机操作系统概述
02-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Iekr | Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式