Spring Bean
什么是Spring Bean
在Spring中,构成应用程序主体由Spring IoC容器管理的对象称为Bean。Bean是由Spring IoC实例化、装配和管理的对象。Bean以及他们之间的依赖关系反映在容器使用的配置元数据中。
Spring IoC容器本身,不能识别配置的元数据,因此要将这些配置数据转换成可以识别的格式——BeanDefinition对象
BeanDefinition是Spring中定义Bean的配置元信息接口,包含:
- Bean类名
- Bean行为配置元素,比如作用域、自动绑定的模式、生命周期回调等
- 其他Bean引用,也可以称为合作者或者依赖
- 配置设置,如Bean属性(Properties)
将一个类声明了Bean的注解有哪些
- @component:通用的注解,可以标记任意类为Spring组件
- @Repository:对应持久层即dao层,主要用于数据库等相关操作
- @Service:对应服务层,处理一些复杂的逻辑,需要用到dao层
- @Controller:对应Spring MVC控制层,用于接受用户请求并且调用Service层返回数据给前端
注入Bean的注解有哪些,以及@Autowired和@Resource的区别是什么
注入Bean的注解:Spring内置的@Autowired和JDK内置的@Resource和@Inject
@Autowired和@Resource的区别:
- @Autowired是Spring提供的注解,@Resource是 JDK 提供的注解
- @Autowired默认的注入方式是byType(根据类型匹配),@Resource默认的注入方式是byName(根据名称匹配)
- 当一个接口被多个类实现的情况下,@Autowired和@Resource都需要通过名称才能匹配到对应的Bean。@Autowired通过通过@Qualifier(value = "")来显示指定名称,@Resource可以通过@Resource(name = ”“)来指定名称
- @Autowired支持在构造函数、字段、方法和参数上使用,@Resource不支持在函数和参数上使用
Bean的作用域有哪些
作用域有这些:
- singleton:IoC容器中只会有一个bena的实例,也是默认作用域,对单例模式的使用
- prototype:每次获取就会得到一个新的bean实例
- request:仅web应用可用,每次http请求都会创建一个bean实例,该bean只在当前http request内有效
- session:仅web应用可用,每一次来自新session的http请求都会产生一个新的bean实例,仅在当前http session内有效
- application/global-session:仅web应用可用,每个web应用创建一个bean实例,仅在当前应用启动时间内有效
- websocket:仅web应用可用,每次websocket对话产生一个bean
Spring IoC
什么是IoC
IoC即控制反转,又称为依赖倒置原则,它要点在于:程序依赖于抽象接口,而不依赖于具体实现。作用是降低代码耦合度
实现方式有两种:
- 依赖注入:对象不通过new()的方式创建,而是将依赖的对象在外部创建好后,通过构造器、函数参数等方式注入给类使用
- 依赖查找:容器中受控对象通过容器的API来查找自己依赖的资源和协作对象
对IoC的理解
Spring IoC是Spring框架的核心,它实现了一种基于容器的对象管理机制。在Spring IoC中,控制权由应用程序代码转移到了Spring框架中,Spring框架负责创建对象、管理对象之间的依赖关系、调用对象的方法等操作。应用程序只需要声明需要使用的对象和依赖关系即可,无须负责对象的创建和管理,从而实现了控制反转。
Spring容器通过配置信息或者注解中的信息,自动创建和管理对象之间的依赖关系。然后将这些对象注入到应用程序中,从而避免了硬编码和耦合性的问题
Spring IoC的实现方式是依赖注入,如构造函数注入、Setter方法注入、字段注入等
Bean的生命周期
分为以下几步:
实例化:
- 实例化:在堆空间中申请内存,对象的属性值一般是默认值,调用createBeanInstance反射创建对象的过程(1. 获取Class对象 2. 通过Class对象获取构造器 3. 构造器创建对象)。
属性赋值 :
- 设置对象属性:对自定义属性进行赋值,通过调用populateBean方法中的set方法赋值
初始化:
- 检查Aware的相关接口并设置相关依赖:对容器对象属性赋值,调用invokeAwareMethods。比如对BeanFactory对象进行判断并进行相关赋值
- BeanPostProcessor前置处理:spring为修改bean的强大扩展点,根据自己的业务逻辑实现。
- 检查是否配置init-method:InitializingBean 和 init-method 是 Spring 为 bean 初始化提供的扩展点。
- BeanPostProcessor后置处理:spring为修改bean的强大扩展点,根据自己的业务逻辑实现。例如为当前对象提供代理实现,例如Spring AOP
销毁:
- 注册Destruction相关回调接口
- 如果Bean实现了DisposableBean这个接口,会根据spring配置的destory-method属性,调用实现的destory方法
Spring DI
什么是依赖注入,依赖注入有哪些实现方式
不通过new()的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
有以下方式:
- Setter方法注入:可选的注入方式,好处是在有变更的情况下可以重新注入
- 构造器注入:Spring倡导的注入方式,因为构造器注入返回给客户端的时候一定是完整的
- Autowired注入:平常用@Autowired标记字段
- 接口回调注入:实现Spring定义的一些内建接口,例如BeanFactoryAware,会进行BeanFactory的注入,不提倡
三种方式的各自优点:
- 基于setter注入,只有对象是需要被注入的时候,才会注入依赖,而不是在初始化的时候就注入。
- 基于构造器注入,会固定依赖注入的顺序,不允许我们创建的bean对象之间存在循环依赖关系,这样Spring能解决循环依赖的问题。
- 在成员变量上写上注解来注入,这种方式,精短,可读性高,不需要多余的代码,也方便维护。
循环依赖是什么,如何解决
在Spring容器中出现相互依赖的情况,即两个或多个Bean之间相互依赖,形成了一个循环依赖链。例如,Bean A依赖Bean B,Bean B依赖Bean A,这样就构成了一个相互依赖。
通过三级缓存解决循环依赖:
- Spring在创建对象时,首先从一级缓存(singletonObjects)中查找是否存在已经创建的对象,如果存在则返回Bean对象
- 若一级缓存不存在,则从二级缓存(earlySingletonObjects)中查找是否存在该对象的早期对象(还未进行属性填充和初始化的半成品对象),若存在则返回早期对象
- 若二级缓存也不存在,则将正在创建的Bean对象放入三级缓存(singletonFactories)中,并在创建过程中依赖注入,即为该对象注入依赖的其他Bean对象。此时,如果其他Bean对象中依赖了正在创建的Bean对象,Spring直接从三级缓存中获取正在创建的对象,而不是新建一个
- 当Bean对象创建完成后,Spring将其从三级缓存中移除,并将其加入一级缓存,以便下次Bean对象获取的时候直接从一级缓存中得到
三级缓存中,三个缓存结构分别放什么
- 一级缓存:成品
- 二级缓存:半成品
- 三级缓存:lamada表达式
如果只有一个map,可以解决循环依赖问题吗
理论上可以,实际上不可行。因为使用两个map的意义是将成品和半成品区分开,半成品是不能直接暴露给对象使用的
如果只有两个map,可以解决循环依赖问题吗
可以解决。但是有前提:没有代理对象的时候,当不使用AOP的时候,两个缓存就可以解决循环依赖问题
为什么三级缓存的map用lamada表达式
对象在什么时候时候暴露出去或者被其他对象引用是没办法提前决定好的,只有在被调用的那一刻才可以进行原始对象还是代理对象的判断,使用lamada表达式类似于一种回调机制,当被需要调用的时候才真正执行。
Spring AOP
AOP的核心概念
主要是以下几个概念:
- 切面(Aspect):切面是一个类,它包含了一组横切关注点和响应的逻辑。一个切面通常会跨越几个对象,因此它不仅定义了横切关注点,还定义了横切关注点与业务的逻辑关系。
- 连接点(Joinpoint):连接点是在程序执行期间可以插入切面的点。例如方法调用、异常抛出等
- 切入点(Poingcut):切入点是一组连接点的集合,它定义了在哪些连接点上应用切面。例如所有的方法调用、异常抛出等
- 通知(Advice):通知是在切面在特定连接点执行的代码。Spring AOP提供了五种类型的通知:前置通知(Before)、后置通知(After)、返回通知(After-running)、异常通知(After-throwing)和环绕通知(Around)
- 切面织入(Weaving):切面织入是将切面应用到目标对象并创建代理对象的过程
对AOP的理解
AOP概念
总的来说,AOP是面向切面编程,通过代理的方式,在调用想要的对象和方法的时候,进行拦截处理,执行切入的逻辑,然后再调用真正的实现方法
应用场景
- 日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。
- 性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。
- 事务管理:@Transactional注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。@Transactional注解就是基于 AOP 实现的。
- 权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用@PreAuthorize注解一行代码即可自定义权限校验。
- 接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。
- 缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新。
动态代理
AOP有两种实现方式:静态代理和动态代理
静态代理:代理类在编译阶段生成,在编译阶段将通知织入java代码,也称编译期增强。AspectJ使用的是静态代理。
动态代理:代理类在程序运行时创建,AOP框架不会去修改字节码,而是在内存中临时生成一个代理对象,在运行期间对业务方法进行增强,不会生成新类。动态代理有两种实现方式:
- JDK动态代理:要求被代理的类必须实现一个接口,它通过反射来接受被代理的类,并使用InvocationHandler接口(作预处理和后处理的逻辑抽象)和Proxy类实现代理
- CGLIB动态代理:CGLIB是一个代码生成的类,它可以在运行时动态地生成某个类的子类,通过基础的方式实现,如果目标类没有实现接口,Spring AOP会选择使用CGLIB来动态代理目标类。它解决了JDK动态代理的两个缺点,目标类有父类会导致父类信息丢失和目标类没有实现接口无法代理的问题。代理类实现了MethodInterceptor(作预处理和后处理的逻辑抽象),使用FastClass直接调用目标类方法。
Spring设计模式
- 工厂模式:在各种BeanFactory以及ApplicationContext创建过程用到
- 模板模式:在各种BeanFactory以及ApplicationContext创建过程用到
- 代理模式:Spring AOP
- 策略模式:加载资源的方式,使用了不同的方法,比如ClassPathResource,AOP的实现采取了不同的代理,JDK和CGLIB
- 单例模式:bean的默认创建模式
- 观察者模式:spring中的ApplicationEvent
- 适配器模式:MethodBeforeAdviceAdapter
- 装饰者模式:Wrapper或者Decorator
Spring事务
事务的实现原理
总的来说,spring的事务是由aop来实现的,首先要生成具体的代理对象,然后按照aop的整套流程来执行具体的操作逻辑。正常情况下通过通知来实现,但是在aop中通过TransactionInterceptor来实现,然后调用invoke来实现具体的逻辑:
- 先做准备工作,解析各个方法上事务相关的属性,根据具体的属性判断是否开启新事务
- 当需要开启的时候,获取数据库连接,关闭自动提交功能,开启事务
- 执行具体的sql逻辑操作
- 在操作过程中如果执行失败了,会通过completeTransactionAfterThrowing来完成事务的回滚,回滚的具体逻辑是通过doRollBack实现,实现的时候也是先获取连接对象,通过连接对象来回滚
- 如果执行过程中没有意外发生,通过completeTransactionAfterReturning实现,具体逻辑通过doCommit实现,实现的时候也是先获取连接对象,通过连接对象来回滚
- 执行完成后需要清除相关的事务信息cleanupTransactionInfo
事务失效
在开发过程中,可能会遇到@Transactional进行事务管理会出问题,这里讨论事务的默认传播行为是REQUIRED。可能失效的场景:
如果使用的是MySQL且引擎是MYISAM,则事务不会起作用,因为MYISAM不支持事务
注解@Transactional只能加在public方法上,不能加在其他
如果在开启事务后捕获异常,使用了try-catch,但没有将异常抛出到外层,事务不会起效果
在不同类之间的方法调用中,如果A方法开启了事务,B方法没有开启事务,B方法调用了A方法:
- 如果B方法发生了异常,但不是A方法产生的,则异常不会使A方法的事务回滚,此时事务无效
- 如果B方法发生了异常,但是A方法产生的,则异常会使A方法的事务回滚,此时事务有效
- 在B方法上加上注解,这样A和B在同一个事务,事务有效
- 简单地说,不同类之间方法的调用,异常发生在无事务的方法中,而不是被调用的方法中产生,被调用方的事务无效。只有异常发生在开启事务的方法内,事务才有效。
在同一个类的不同方法之间调用中,如果A方法调用了B方法,不管A方法有没有开启事务,由于Spring的代理机制B方法都是无效的。因为动态代理最终是要调用原始对象的方法,在原始方法中调用另一个原始方法,是不会再触发代理了。
如果使用了Spring + MVC,则context:component-scan重复扫描问题可能会引起事务失效。
事务的传播机制
事务的传播是指不同方法的嵌套过程中,事务应该如何处理,是用同一个事务还是不同的事务,当出现异常是回滚还是提交,两个方法之间的相关影响。
有7种传播机制:
- REQUIRED:如果当前没有事务,则创建一个新的事务;如果当前已经存在事务,则加入该事务。这是默认的传播行为。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。
- MANDATORY:必须在一个已存在的事务中执行,否则就抛出TransactionRequiredException异常。
- REQUIRES_NEW:创建一个新的事务,并在该事务中执行;如果当前存在事务,则将当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
- NEVER:以非事务方式执行操作,如果当前存在事务,则抛出IllegalTransactionStateException异常。
- NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务。
@Transactional注解
@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
属性:
Spring常用注解
Spring常用的注解
- @Component、@Controller、@Service、@Repository:使用在类上,用于实例化Bean
- @Autowired:使用在字段上根据类型依赖注入
- @Qualifier:结合@Autowired一起使用根据名称依赖注入
- @Scope:标记Bean的作用范围
- @Configuration:指定当前类是一个配置类,当创建容器时会从该类上加载注解
- @Bean:使用在方法上,标注该方法的返回值存储到IoC容器中
- @Import:使用@Import导入的类会被spring加载到IoC容器中
- @Aspect、@Before、@After、@Around、@Poincut:用于切面编程(AOP)
Spring MVC常用的注解
- @RequestMapping:用于映射请求路径,可以定义在类和方法上。用在类上表示所有的方法都是以该地址作为父路径
- @RequestBody:注解实现http请求的json数据,将json转换为java对象
- @PathVirable:从请求路径下获得参数(/user/id),传递给方法的形式参数
- @RequestParam:指定请求参数的名称
- @ResponseBody:注解实现将controller方法返回对象转换为json对象响应给客户端
- @RequestHeader:获取指定的请求头数据
- @RestController:@Controller+@ResponseBody
Springboot常见的注解
- @SpringBootConfiguration:组合了@Configuration,实现配置文件的功能
- @EnableAutotConfiguration:打开自动配置的功能,也可以关闭某个自动配置的类
- @ComponentScan:Spring组件扫描
Spring MVC的执行流程
SpringBoot的自动装配
什么是SpringBoot
自动装配可以说是 Spring Boot 的核心,没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 redis 的话,直接在项目中引入对应的 starter 即可。也就是通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
SpringBoot如何实现自动装配
- 首先注解@SpringBootApplication由三个注解@ComponentScan,@EnableAutoConfiguration,@SpringBootConfiguration。
- 其中@EnableAutoConfiguration通过@import注解将AutoConfiguration.class这个类导进来
- 该类会加载所有jar包的META-INFO下面的spring-factories配置文件,这里用到了spring里面的SPI机制
- 这个文件是key-value的形式,key是EnableAutoConfiguration的全路径名,value是各个需要配置的类。然后springboot默认加载了100多个类,然后再根据Condition加载我们需要的类,比如在配置文件中加入了对应的配置,对应的配置类才会生效
MyBatis执行流程
- 读取MyBatis配置文件,mybatis-config.xml加载运行环境和映射文件
- 构造会话工厂SqlSessionFactory
- 会话工厂构建SqlSession对象(包含了SQL语句的所有方法)
- 操作数据库接口,Executor执行器,同时负责查询缓存的维护
- Executor接口的执行方法中有一个MapperStatement类型的参数,封装了映射信息
- 输入参数映射
- 输出结果映射
MyBatis二级缓存
一级缓存是基于ParpetualCache的HashMap本地缓存,其存储作用域为SqlSession,各个SqlSession之间缓存相互隔离,当Session进行flush或者close之后,该Session的Cache就将清空,默认打开一级缓存
二级缓存是基于namespace和mapper的作用域起作用,不是依赖于SQL session,默认也是采用ParpetualCache,HashMap存储
注意事项:
- 对于缓存的更新机制,当某一个作用域(一级缓存 Session/二级缓存 namespace)做了新增、修改、删除操作后,缓存会被情况
- 二级缓存需要缓存的数据需要实现
- 只有会话提交或者关闭后,一级缓存的数据才会转移到二级缓存中