SpringBoot 学习笔记

文章目录

  • 一、IoC
  • 二、AOP
  • 三、bean
    • 3.1 bean 生命周期
    • 3.2 三种依赖注入方式
    • 3.3 bean 线程安全
  • 四、SpringMVC
  • 五、常用注解
    • 5.1 @Scope
    • 5.2 @PostConstruct 和 @PreDestroy
    • 5.3 @Component 和 @Bean
    • 5.4 @Autowired 和 @Resource
  • 六、基于 ApplicationContextAware 实现工厂模式
  • 七、事务失效
  • 八、三级缓存与循环依赖


一、IoC

IoC(Inversion of Control)控制反转:控制反转是一种设计思想,它将组件的创建和管理交给容器,从而降低组件之间的耦合度。控制是指实例化以及管理对象的权力,反转是指将控制权交给 IoC 容器。IoC 容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在 IoC 容器中统称为 bean。


二、AOP

面向切面编程的核心思想就是将横切关注点从核心业务逻辑中分离出来,形成一个个的切面。横切关注点指的是一些分散在多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等操作),如果我们在每个类或对象中都重复实现这些行为,那么会导致代码的冗余、复杂和难以维护。

Spring AOP 基于动态代理实现。当目标对象实现了某个接口时,Spring AOP 会使用 JDK 动态代理来生成对应接口的代理对象。对于没有实现接口的对象,Spring AOP 会使用 CGLIB 生成一个目标对象的子类作为代理对象。在运行时,代理类会替代原始目标类来执行方法调用,而代理类中的功能是基于切面类定义和实现的。

AOP 中的核心概念:

  • 目标对象:被代理或操作的对象。
  • 连接点:目标对象中定义的所有可被 AOP 控制的方法均为连接点。
  • 切入点:被实际增强的连接点,具体范围由切入点表达式控制。
  • 通知:拦截到连接点之后要执行的增强逻辑,比如一些共性功能。
  • 切面:切入点 + 通知。

切入点表达式用来描述切入点方法的表达式,主要用来决定目标对象中的哪些连接点需要加入通知,主要包括使用 execution() 根据方法的签名来匹配和使用 @annotation() 根据注解匹配。


三、bean

3.1 bean 生命周期

  • 初始化容器
    • 通过反射实例化 bean,创建对象。
    • 执行构造方法。
    • 如果实现了 Aware 相关依赖,如 ApplicationContextAware,执行对应方法。
    • 属性赋值与依赖注入,解决循环依赖。
    • 执行 @PostConstruct 注解标识的方法。
    • 如果项目中实现了 BeanPostProcessor 接口,将当前类作为参数执行自定义的 postProcessBeforeInitialization 方法。
    • 如果实现了 InitializingBean 接口,执行 afterPropertiesSet 方法。
    • 如果配置了自定义的 init-method,即 @Bean(initMethod = ""),执行对应方法。
    • 如果项目中实现了 BeanPostProcessor 接口,将当前类作为参数执行自定义的 postProcessAfterInitialization 方法。
  • 获取并使用 bean
  • 销毁容器
    • 如果配置了自定义的 destroy-method,即 @Bean(destroyMethod = ""),执行对应方法。
    • 如果实现了 DisposableBean 接口,执行 destory() 方法。

3.2 三种依赖注入方式

  • 字段注入:实现简单,但是存在注入对象不能用 final 修饰、难以进行单元测试等问题,因此并不推荐使用字段注入。
  • 构造器注入:唯一一个注入对象可以使用 final 修饰的注入方法,同时能够检测循环依赖。但是,当一个类有很多依赖项时构造函数的参数列表可能会变得很长。此外,如果一个类有可选的依赖项,可能需要创建多个构造函数重载,灵活性较低。
  • setter 方法注入:灵活性较高,不过注入对象也不能用 final 修饰。

总的使用原则是:强制的依赖就用构造器注入,可选、可变的依赖就用 setter 注入。

3.3 bean 线程安全

bean 是否线程安全,取决于其作用域和状态。

以最常用的两种作用域 prototypesingleton 为例:

  • prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。
  • singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题,具体要看 bean 是否有状态,如果是无状态 bean,那就不存在线程安全问题,如果是有状态 bean,那就存在线程安全问题。这里的有无状态指的是对于成员变量,除了查询以外,是否还会对其进行修改。

对于有状态单例 bean 的线程安全问题,有以下几种解决办法:

  • 在 bean 中尽量避免定义可变的成员变量。
  • 通过 ThreadLocal 或互斥锁控制成员变量的修改和访问。
  • 采用 prototype 作用域。

四、SpringMVC

SpringMVC 技术与 Servlet 技术功能等同,均属于表现层开发技术。

在这里插入图片描述


五、常用注解

5.1 @Scope

声明 bean 的作用域,常见的有以下四种:

  • singleton:Spring 中默认的作用域,IoC 容器中只有唯一的 bean 实例,这意味着所有客户端共享相同的 bean 实例。
  • prototype:每次获取都会创建一个新的 bean 实例,这意味着每个客户端都拥有自己的 bean 实例。
  • request:bean 的生命周期与 HTTP 请求的生命周期相对应,每个 HTTP 请求都会创建一个新的 bean 实例,该 bean 仅在该请求内可见。
  • session:每一次来自新 session 的 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 session 内有效。

使用示例:

@Component
@Scope("singleton")
public class Solution {
}

5.2 @PostConstruct 和 @PreDestroy

@PostConstruct@PreDestroy 并非由 Spring 提供,而是 Java 自带的注解。@PostConstruct 会在依赖注入完成后被自动调用,并且只会被调用一次,用于完成一些初始化操作。而 @PreDestroy 则会在容器销毁 bean 的时候回调执行,用于完成相关的销毁操作。

bean 初始化过程中的执行顺序为:

Constructor(构造方法)-> @Autowired(依赖注入)-> @PostConstruct(初始化方法)

使用示例:

@Component
public class Solution {
    @PostConstruct
    void init() {
        System.out.println("init...");
    }
    
    @PreDestroy
    void destroy() {
        System.out.println("destroy...");
    }
}

5.3 @Component 和 @Bean

  • @Component 注解作用于类,标识这是一个 bean,而 @Bean 注解作用于方法,通常方法体中包含产生 bean 的逻辑。
  • @Component 通常是通过类路径扫描来自动侦测 bean 并装配到 Spring 容器中,可以使用 @ComponentScan 定义要扫描的路径,而 @Bean 通常指定了这个方法的返回值将被注册为一个 bean。
  • @Bean 注解比 @Component 注解的自定义性更强,可以在方法内部实现更复杂的 bean 创建和初始化逻辑,同时第三方依赖中的 bean 只能通过 @Bean 声明(因为第三方的 bean 是只读的,没法加 @Component 注解)。

5.4 @Autowired 和 @Resource

  • @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
  • @Autowired 默认的注入方式是根据类型匹配,@Resource 默认的注入方式是根据名称匹配。
  • 当一个接口存在多个实现类的情况下,@Autowired@Resource 除了都能够通过名称匹配到对应的 bean 以外,@Autowired 还可以通过 @Qualifier 注解来显式指定,@Resource 则可以通过 name 属性来显式指定。
  • @Autowired 支持在构造函数、方法、字段和参数上使用,@Resource 主要用于字段和方法上的注入,不支持在构造函数或参数上使用。

六、基于 ApplicationContextAware 实现工厂模式

抽象产品:

package atreus.ink.log;

import org.springframework.stereotype.Component;

@Component
public abstract class AbstractLog {
    
    protected abstract String getName();
    
    protected void doLog() {
        System.out.println("atreus.ink.log.AbstractLog#doLog");
    }
}

具体产品:

package atreus.ink.log;

import org.springframework.stereotype.Component;

@Component
public class JavaLog extends AbstractLog {
    
    @Override
    public String getName() {
        return "JavaLog";
    }
    
    @Override
    public void doLog() {
        super.doLog();
        System.out.println("atreus.ink.log.JavaLog#doLog");
    }
}
package atreus.ink.log;

import org.springframework.stereotype.Component;

@Component
public class CppLog extends AbstractLog {
    
    @Override
    public String getName() {
        return "CppLog";
    }
    
    @Override
    public void doLog() {
        super.doLog();
        System.out.println("atreus.ink.log.CppLog#doLog");
    }
}

工厂:

package atreus.ink.log;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class LogFactory implements ApplicationContextAware {

    private static final Map<String, AbstractLog> map = new HashMap<>();
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, AbstractLog> beansOfType = applicationContext.getBeansOfType(AbstractLog.class);
        beansOfType.forEach((k, v) -> map.put(v.getName(), v));
    }
    
    public static AbstractLog getLog(String name) {
        return map.get(name);
    }
}

测试:

package atreus.ink.log;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class LogFactoryTest {
    
    @Test
    void getLogTest() {
        AbstractLog javaLog = LogFactory.getLog("JavaLog");
        javaLog.doLog();
        System.out.println("----------");
        AbstractLog cppLog = LogFactory.getLog("CppLog");
        cppLog.doLog();
        Assertions.assertTrue(true);
    }
}
atreus.ink.log.AbstractLog#doLog
atreus.ink.log.JavaLog#doLog
----------
atreus.ink.log.AbstractLog#doLog
atreus.ink.log.CppLog#doLog

七、事务失效

Spring 事务失效场景:

  • 数据库引擎不支持事务,比如 MySQL 的 MyISAM 引擎就不支持事务。
  • 使用事务的类没有交由 Spring 管理,需要对事务类加 @Repository@Service@Controller@Component 注解。
  • 方法不是 public 的,@Transactional 只能用于 public 的方法上,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。
  • 自调用问题,由于 Spring 事务管理基于动态代理,而代理对象无法拦截类的内部调用,如果确实需要自调用,可以使用通过 AopContext.currentProxy() 手动获取代理对象或者在内部注入自己对应的 bean。
  • 异常在函数内部被处理,只有将异常抛出 Spring 才能检测到异常并进行回滚。

八、三级缓存与循环依赖

Spring 三级缓存(以 singleton 作用域为例):

  • 一级缓存singletonObjects,缓存已经完成实例化和初始化的成品 bean。
  • 二级缓存earlySingletonObjects,缓存已经被其他对象引用的半成品 bean,这些 bean 被提前暴露。
  • 三级缓存singletonFactories,缓存未被其他对象引用的半成品 bean 的工厂,使用时通过这些工厂创建 bean。

三级缓存解决循环依赖(假设 A 和 B 之间存在循环依赖):

  • 实例化 A,此时 A 还未完成属性赋值和初始化,A 只是一个半成品。
  • 为 A 创建一个 bean 工厂,并放入到 singletonFactories 中。
  • 发现 A 需要注入 B 对象,但是一、二、三级缓存均未发现对象 B。实例化 B,此时 B 还未完成属性赋值和初始化,B 只是一个半成品。
  • 为 B 创建一个 bean 工厂,并放入到 singletonFactories 中。
  • 发现 B 需要注入 A 对象,此时在三级缓存中发现了对象 A,从三级缓存中通过 bean 工厂得到对象 A,并将对象 A 移入二级缓存。
  • 将对象 A 注入到对象 B 中。
  • 对象 B 完成属性填充,执行初始化方法,移入一级缓存,此时对象 B 已经是一个成品。
  • 对象 A 得到对象 B,将对象 B 注入到对象 A 中。
  • 对象 A 完成属性填充,执行初始化方法,移入一级缓存,此时对象 A 也已经是一个成品。

两级缓存也能解决循环依赖,为什么还需要第三级缓存,第三级缓存又为什么缓存 bean 工厂而不是 bean呢?

其实第三级缓存的主要目的是延迟代理对象的创建,如果没有循环依赖的话,第三级缓存可以将代理对象的创建延迟到初始化完成之后,不需要提前

具体来说,如果创建的 bean 是有代理的,那么注入的就应该是代理 bean,而不是原始的 bean。按照 Spring 的设计原则,Spring 会在完成属性赋值与依赖注入并且执行完初始化方法之后再为其创建代理

对于三级缓存来说,如果需要创建代理,在没有循环依赖的情况下,Spring 首先会为已经实例化的 bean 在三级缓存中创建一个工厂,然后进行属性赋值、依赖注入和初始化,最后创建代理放入一级缓存,也即在完成初始化等操作后创建代理类。如果出现了循环依赖,Spring 会通过三级缓存中工厂类的方法去提前创建代理对象,放入二级缓存,在完成初始化等操作后再放入一级缓存,这种情况下就是先创建代理再初始化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/410311.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

docker创建mongodb数据库容器

介绍 本文将通过docker创建一个mongodb数据库容器 1. 拉取mongo镜像 docker pull mongo:3.63.6版本是一个稳定的版本&#xff0c;可以选择安装此版本。 2. 创建并启动主数据库 容器数据卷配置 /docker/mongodb/master/data # 数据库数据目录&#xff08;宿主机&am…

下载huggingface数据集到本地并读取.arrow文件遇到的问题

文章目录 1. 524MB中文维基百科语料&#xff08;需要下载的数据集&#xff09;2. 下载 hugging face 网站上的数据集3. 读取 .arrow 文件报错代码4. 纠正后代码 1. 524MB中文维基百科语料&#xff08;需要下载的数据集&#xff09; 2. 下载 hugging face 网站上的数据集 要将H…

springboot+vue前后端分离适配cas认证的跨域问题

0. cas服务搭建参考:CAS 5.3服务器搭建_cas-overlay-CSDN博客 1. 参照springsecurity适配cas的方式, 一直失败, 无奈关闭springssecurity认证 2. 后端服务适配cas: 参考前后端分离项目(springbootvue)接入单点登录cas_前后端分离做cas单点登录-CSDN博客 1) 引入maven依赖 …

Word页码怎么设置?6个提升效率好方法!

“我刚刚编辑完一个Word文档&#xff0c;想给它加上页码&#xff0c;但是我还不知道应该怎么操作。大家平常是怎么给Word设置页码的呢&#xff1f;” 在使用Word编辑文档时&#xff0c;页码的设置是一个常见的需求。无论是为了方便阅读&#xff0c;还是为了符合特定的格式要求&…

亿道丨三防平板丨如何从多方面选择合适的三防加固平板?

在如今这个信息爆炸的时代&#xff0c;移动设备已经成为我们生活和工作的必备工具。然而&#xff0c;在一些特殊的场合中&#xff0c;普通的平板电脑可能无法满足需求&#xff0c;比如工厂车间、野外作业、极端天气等环境下。此时&#xff0c;三防平板就成了不二之选。那么&…

怎么免费找回误删文件?这5个数据恢复工具能救你一命

如果不小心删除了文件&#xff0c;不要慌张&#xff0c;今天这个视频将为大家推荐5个最好的文件恢复软件。 误删文件是很多人在日常生活中都会遇到的问题&#xff0c;而找回丢失的数据更是至关重要。现在&#xff0c;有许多文件恢复软件可以帮助您快速找回丢失的重要文件。这些…

flutter sliver 多种滚动组合开发指南

flutter sliver 多种滚动组合开发指南 视频 https://youtu.be/4mho1kZ_YQU https://www.bilibili.com/video/BV1WW4y1d7ZC/ 前言 有不少同学工作中遇到需要把几个不同滚动行为组件&#xff08;顶部 appBar、内容固定块、tabBar 切换、tabBarView视图、自适应高度、横向滚动&a…

【前端素材】推荐优质后台管理系统Uena平台模板(附源码)

一、需求分析 后台管理系统&#xff08;或称作管理后台、管理系统、后台管理平台&#xff09;是一种专门用于管理网站、应用程序或系统后台运营的软件系统。它通常由一系列功能模块组成&#xff0c;为管理员提供了管理、监控和控制网站或应用程序的各个方面的工具和界面。以下…

sonar-java 手写一个规则-单元测试分析

前言 最近做项目&#xff0c;定制sonar规则&#xff0c;提高Java代码质量&#xff0c;在编写的sonar规则&#xff0c;做验证时&#xff0c;使用单元测试有一些简单的心得感悟&#xff0c;分享出来。 自定义规则模式 sonar的自定义规则很简单&#xff0c;一般而言有2种模式可…

基于自适应波束成形算法的matlab性能仿真,对比SG和RLS两种方法

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于自适应波束成形算法的matlab性能仿真,对比SG和RLS两种方法. 2.测试软件版本以及运行结果展示 MATLAB2022a版本运行 3.核心程序 ........................…

智慧农业之农产品溯源管理

总体设计 系统采用RFID无线射频识别技术对产品的生产、加工、运输、包装、配送等各个环节实施全程监控与可追溯。利用射频识别(RFID)、红外感应器、激光扫描器、传感器等信息传感设备,把任何物品与互联网连接起来,进行信息交换和通讯,以实现智能化识别、定位、跟踪、监控…

stable diffusion学习笔记 手部修复

图片手部修复原理 某张图片在生成后&#xff0c;仅有手部表现不符合预期&#xff08;多指&#xff0c;畸形等&#xff09;。这种情况下我们通常使用【局部重绘】的方式对该图片的手部进行【图生图】操作&#xff0c;重新绘制手部区域。 但是仅采用重绘的方式也很难保证生成的…

Redis 分布式锁

什么是分布式锁 在一个分布式的系统中&#xff0c;也会涉及到多个节点访问同一个公共资源的情况。此时就需要通过锁来做互斥控制&#xff0c;避免出现类似于“线程安全”的问题。 而 java 的 synchronized 或者 C 的 std::mutex&#xff0c;这样的锁都是只能在当前进程中生效…

leetcode-hot100-双指针

剪枝&#xff0c;减少不必要的计算 283. 移动零 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nums [0] 输出: [0] 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 第一印象&#xff1a;使用一个辅助数组&#xff0c;同时以…

el-submenu is-opened 展开/闭合;el-submenu is-opened保持一个子菜单的展开控制

写了个mes系统目录 点击子菜单展开后&#xff0c;上一级菜单没有默认关闭。主流后台管理系统大部分都是保持一个子菜单关闭状态、 问度娘无果后&#xff0c;查询官网&#xff0c;一个属性搞定。 unique-opened 是否只保持一个子菜单的展开 加在 <el-menu 组件上即可 完整代…

react中修改state中的值无效?

// 初始化state state {personArr:[{name:张三,id:1},{name:李四,id:2},{name:王五,id:3}] }componentDidMount(){const newName 赵六const indexUpdate 1const newArr this.state.personArr.map((item,index)>{if(indexUpdate index){return {...item,name:newName}}e…

Java 过滤器深入了解学习

Java 过滤器深入了解学习 生活不能等待别人来安排&#xff0c;要自己去争取和奋斗&#xff1b;而不论其结果是喜是悲&#xff0c;但可以慰藉的是&#xff0c;你总不枉在这世界上活了一场。有了这样的认识&#xff0c;你就会珍重生活&#xff0c;而不会玩世不恭&#xff1b;同时…

Netty权威指南——基础篇1(同步阻塞IO-BIO)

1 Linux网络I/O模型简介 1.1 简述 Linux的内核将所有外部设备都看做一个文件来操作&#xff0c;对一个文件的读写操作会调用内核提供的命令&#xff0c;返回一个file descriptor(fd&#xff0c;文件描述符)。而对一个socket的读写也会有相应的描述符&#xff0c;称为socketfd(…

SpringBoot原理篇

文章目录 SpingBoot原理1. 配置优先级2. Bean管理2.1 获取Bean2.2 Bean作用域2.3 第三方Bean 3. SpringBoot原理3.1 起步依赖3.2 自动配置3.2.1 概述3.2.2 常见方案3.2.2.1 概述3.2.2.2 方案一3.2.2.3 方案二 3.2.3 原理分析3.2.3.1 源码跟踪3.2.3.2 Conditional 3.2.4 案例3.2…

深入探究Nginx的使用方法

目录 引言 一、网络状态页 二、Nginx 第三方模块 三、变量 &#xff08;一&#xff09;内置变量 &#xff08;二&#xff09;自定义变量 四、自定义日志 &#xff08;一&#xff09;有关日志的配置信息 &#xff08;二&#xff09;error日志的设置 1.日志的等级 2.自…