Chlx's Live

Back

Spring#

IoC容器#

  1. 创建容器
  2. 创建一个配置类 怎么加载这些分散的配置类?
  3. 实例化AnnotationConfigApplicationContext 落后方式:使用 XML 配置文件 关闭容器
  • close():是主动、手动调用的方法。你必须在代码里明确地调用它,容器才会关闭。
  • registerShutdownHook():是被动、自动调用的机制。你不需要手动调用它,它会注册一个“钩子”,当 JVM (Java 虚拟机) 即将关闭时,这个钩子会被自动触发,从而关闭容器。

使用注解配置IoC容器 每个Bean通过@Component@Autowired注入 Pasted image 20250922213739

@SpringBootApplication 注解内部就包含了一个 @ComponentScan,它默认会扫描启动类所在的当前包及其所有子孙包,将所有分散的配置类和组件一网打尽

DI 依赖注入#

IoC思想的具体实现形式,主要包含对象注入和值注入

  • @Autowired注解,默认是按照引用类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)
  • @Resource 默认按名称(By Name)注入
  • 隐式构造器注入:如果一个类只有一个构造方法,那么 Spring 默认就会使用这个构造方法进行依赖注入,不需要你在上面写 @Autowired

可用@Autowired(required=false)允许可选注入

存在相同类型bean怎么办: 结合@Qualifier指定bean的ID bean的默认ID是将类名的首字母小写 或者可以起别名

Pasted image 20250905145053

Bean的初始化和清理方法上标记@PostConstruct@PreDestroy

自动装配#

自动装配的目的就是让当前模块和包, 在极少量配置的情况下注入第三方组件和包的 Bean。 加载配置类的三种思路

  1. 通过@componentScan 指定组件扫描范围(默认的范围失效)
  2. @Import 导入普通类-导入配置类-导入选择器(ImportSelector接口实现类) ImportSelector 是 Spring框架提供的一个接口。当一个 @Configuration 类通过 @Import 注解导入了一个 ImportSelector 的实现类时,Spring 容器在初始化时会执行这个实现类,并将其返回的类名数组所对应的类都加载为 Bean。
  3. 第三方依赖自定义的 @EnableXxxxx注解 用第三方帮你封装的

原理: 它的逻辑非常复杂,但核心思想是: @EnableAuto Configuration 通过 Spring Factories 机制从所有第三方 JAR 包中发现候选配置类 → 一系列 @Conditional 条件筛选 → 递归解析 @Import/@Bean 并注册 BeanDefinition→ 最终按依赖顺序实例化并注入所有单例 Bean

属性绑定#

解决 外部配置数据 到 Bean 属性 的映射

@ConfigurationProperties面向对象(批量)的绑定。 将配置文件如application.yml中的属性批量绑定到 Java 类的字段上,替代难以复用和管理的@Value注解

  • @PropertySource(去哪找数据): 它的职责是把非默认的配置文件(比如你自己建了一个 custom-pay.properties)加载到 Spring 的 Environment 环境变量池中。

  • @ConfigurationProperties(怎么用数据): 它的职责是拿着一个前缀(比如 pay.wechat),去 Environment 池子里把对应的数据捞出来,塞进当前 Java 对象的字段里。

Spring Boot 会自动加载 application.propertiesapplication.yml 文件。通常不需要显式使用 @PropertySource

为什么 @Value 不需要写 Setter 方法就能注入,而 @ConfigurationProperties 必须写 Setter 方法? 因为它们底层操作内存的方式完全不同:

  • @Value 的暴力美学(Field 注入): 它底层直接拿到类的 Field 对象,通过调用 field.setAccessible(true) 打破 Java 的封装性,然后直接向内存地址里塞值。
  • @ConfigurationProperties 的优雅规范(JavaBeans 内省): 它遵循了标准的 JavaBeans 规范。绑定器底层使用了 java.beans.Introspector 来分析你的类。它会去寻找名为 setMaxReplyLength(int value) 的公开方法。 重点: 如果你只写了属性,没生成 Setter 方法(或者没加 Lombok 的 @Data),Spring Boot 的绑定器即使在 application.yml 里找到了 200,也会因为找不到 Setter 方法而默默放弃绑定。最终你的变量依然是硬编码的 100
松散绑定和参数校验#

松散绑定:智能命名适配

  • 核心点:打通了 Java 开发者与运维人员的命名习惯差异。

参数校验 (Validation):启动期的安全卫士

  • 核心点:践行“快速失败 (Fail-Fast)”原则。与其让系统带着错误的配置带病运行、半夜崩溃,不如在启动时就直接报错停机。

条件装配bean#

@Conditional

Bean管理#

@Scope("scopeName") 定义 Spring Bean 的作用域,即 Bean 实例的生命周期和可见范围。常用的作用域包括:

  • singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例
  • prototype : 每次获取都会创建一个新的 bean 实例。
bean的生命周期#

默认singleton的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化 @PostConstruct@PreDestroy 注意的是destroy只有在容器关闭的时候,才会执行

第三方的类/@Configuration#

这些类都是在jar包中,我们没有办法在类上面添加注解 怎么办: @Bean 标记在方法上,并且这个方法必须位于一个 @Configuration 类中 声明返回值就是我想要注册到容器中的一个 Bean 实例。

另一个使用场景:对象的创建过程非常复杂,需要一些前置逻辑判断

如果说 @Component / @Service 是让 Spring 自动去“抓取”你自己写的代码,那么 @Configuration + @Bean 就是让你手动去“组装”那些你无法控制、或者极其复杂的对象,然后再塞给 Spring

AOP#

AOP 提供了底层运行机制,自定义注解精准标记了目标,通知类封装了规则,而通知则是具体执行的动作

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,降低模块间的耦合度

通知类(Aspect Class / 切面类) 和通知#

通知就是将共性功能抽取出来后形成的方法 是具体的拦截动作

五大通知类型#

设置当前通知方法与切入点之间的绑定关系 @Before(前置通知):方法执行前干活(例如:检查参数、校验 Token)

@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行

JoinPoint 与 ProceedingJoinPoint 有区别

JoinPoint#

抽象了连接点,它代表了当前正在被拦截的那个方法及其所有的上下文信息。

spring自动把 joinPoint 对象传给你的通知方法了,你的通知代码就获得了当前被拦截方法的所有元数据,如目标类名、方法名、方法参数等

切入点表达式#

作用是筛选连接点

切入点@PointCut注解,该注解的作用是将公共的切入点表达式抽取出来,像一个变量名一样可以复用

  1. execution指示器 最常用,基于方法的签名结构
  • *:匹配任意符号(常用)
  • .. :匹配多个连续的任意符号(常用)
  • +:匹配子类类型
  1. @annotation基于自定义注解的方式来匹配切入点方法。灵活 适合做特定业务场景下的定制化拦截

通知的执行顺序:#

  1. 不同的切面类当中,默认情况下通知的执行顺序是与切面类的类名字母排序是有关系的
  2. 可以在切面类上面加上@Order注解

开启代理#

在原生 Spring 中,必须通过 @EnableAspectJAutoProxy 开启 AOP 代理。但在 Spring Boot 中,只要引入了 spring-boot-starter-aop 依赖,自动装配机制会自动配置 AopAutoConfiguration,默认开启代理功能,所以我们只需要在切面类上加上 @Aspect@Component 即可生效。

AOP原理#

SpringAOP的本质或者可以说底层实现是通过代理模式/动态代理 当一个 Bean 被 AOP 切入(即,它的某些方法匹配了某个切入点),那么 Spring IoC 容器中最终存储和管理的,确实是这个 Bean 的代理 (Proxy) 对象

spring事务#

@Transactional#

作用:出现异常就会进行事务的回滚操作 位置:业务层的方法、类、接口 影响范围不同

  • 在Spring的事务管理中,默认只有运行时异常 RuntimeException才会回滚。
  • 如果还需要回滚指定类型的异常,可以通过rollbackFor属性来指定。
propagation 控制事务传播行为#

常见值

  1. REQUIRED(默认值)加入或者创建 如果当前已存在事务(即调用它的外部方法已经开启了事务),那么当前方法就会加入到这个已存在的事务中,成为这个大事务的一部分。如果当前没有事务,那么Spring就会为当前方法创建一个新的事务。

默认的是“命运共同体”,一损俱损

  1. REQUIRES_NEW 总是新的

Pasted image 20250924195449

在配置类开启注解式事务驱动 @EnableTransactionManagement spring中,需要手动创建 TransactionManager Bean, 并关联 DataSource

原理#

当你在一个方法上加上 @Transactional 时,Spring 会为这个类生成一个代理对象(Proxy)。当你通过代理对象调用这个方法时,Spring 的事务拦截器(TransactionInterceptor)会介入,偷偷帮你做以下几件事:

  1. 拦截请求:拿到数据库连接。
  2. 开启事务:执行 connection.setAutoCommit(false)
  3. 放行:调用你写的实际业务代码(目标对象的方法)。
  4. 提交/回滚
    • 如果业务代码正常执行完,拦截器执行 connection.commit()
    • 如果业务代码抛出异常,拦截器执行 connection.rollback()

事务失效#

事务方法非 public 修饰 对于 CGLIB,它无法代理非 public 方法

自调用(在同个类中调用事务方法)  原因:自调用时 this 引用指向原始对象而非代理对象,导致 AOP 增强逻辑被绕过

在“同类内部调用”这个场景下,失效原理和 @Async 完全一模一样,都是因为绕过了代理对象

异常类型不被捕获或者在 try-catch 中吞掉异常  原因:Spring 事务默认只在抛出未检查异常(即 RuntimeException 和 Error)时回滚)

事务传播行为设置不当

在非 Spring 管理的 Bean 中使用

带事务的方法(父) 调用 异步方法(子)

同一个方法同时加 @Transactional@Async 事务可以生效,但不推荐,原因是在 Spring 默认的 AOP 优先级中,@Async 的切面优先级高于 @Transactional

异步@Async#

Spring 的 @Async 注解是通过 动态代理 实现的。

  • 正常情况:当你从外部(类 A)调用类 B 的异步方法时,你持有的实际上是类 B 的代理对象(Proxy)。调用时,代理对象会拦截请求,将其提交给线程池执行,然后立即返回。
  • 同类调用情况:当你直接在类 B 的内部方法 methodA() 中调用 this.methodB()(异步方法)时,你是通过 this 引用调用的。this 指向的是**目标对象(Target)**本身,而不是 Spring 创建的代理对象。

结论:绕过了代理对象,Spring 就无法拦截调用,异步逻辑也就不会生效,方法会在当前线程同步执行。

SpringMVC#

控制层注解#

@RequestMapping 设置当前controller方法请求访问路径

若使用method属性定义请求方式,重复性太高 简化: RESTul请求动作映射 @GetMapping

响应#

类注解 @RestController = Controller + ResponseBody Pasted image 20250905091819

参数接收#

  • 接收json数据: @RequestBody修饰方法形参 JSON数据的键名与方法形参对象的属性名相同 才能自动映射(或者@JsonProperty手动映射,也可全局统一配置命名策略) 读取请求体 转json为对象

对于非json格式数据,从 URL 的查询参数 (?key=value)中获取 : @RequestParam(“查询参数”)注解将请求参数绑定给方法形参 如果请求参数名与形参变量名相同,可以省略@RequestParam 对于集合类型,默认情况下不会自动绑定请求参数,必须通@RequestParam 显式声明

对于日期类型 @DateTimeFormat 可以统一格式

对于路径参数 @PathVariable /users/{id} ,使用{参数名称}描述路径参数,只能提取非 JSON 类型的简单数据

全局异常处理器#

  • @RestControllerAdvice //表示当前类为REST风格的异常处理器 = @ControllerAdvice + @ResponseBody(处理返回结果给前端)

@ExceptionHandler //方法上面加。value属性指定捕获哪种类型的异常进行处理

tip 留个ex.printStackTrace 不然什么错误都看不到了

原理:所有从 Controller 抛出的异常都会被 DispatcherServlet 的内置 try-catch 机制捕获 无需throws:抛出 RuntimeException(非受检异常),Java 语法不要求为其添加 throws 声明 Pasted image 20250924211915

拦截器 Interceptor#

  1. 定义拦截器 实现HandlerInterceptor接口

拦截器是Spring框架中提供的,需要交给IOC容器管理。@Component

  1. 注册配置拦截器 创建一个配置类实现 WebMvcConfigurer 接口,并重写 addInterceptors 方法返回个拦截器 Pasted image 20250924212246
  • postHandle在 Controller 执行完、视图渲染之前被调用,主要用于修改 ModelAndView的场景。如果 Controller 里直接写回 JSON,这个阶段一般没什么可做的
  • afterCompletion在整个请求完成之后被调用,且无论是否出现异常都会执行(对于那些已经通过 preHandle 的拦截器),适合做清理/记录/异常处理/释放 ThreadLocal 等收尾工作

拦截器参数#

  • HttpServletRequest request:请求对象
  • HttpServletReponse response:响应对象
  • handler:被调用的处理器对象,本质上是一个方法对象,对反射中的Method对象进行了再包装

Mybatis#

封装了jdbc操作

原理#

现在我们把这些底层零件全部拼装起来,一窥 MyBatis 的全貌:

  1. 项目启动(装载期):MyBatis 读取 XML,把每一个 <select> 标签封装成重型的 MappedStatement 对象,并以接口方法全限定名为 Key,安全地塞进 Configuration.StrictMap 这个改造过的 HashMap 里。

  2. 代码调用(拦截期):你在 Service 里调用 userMapper.getUserById(1)。动态代理类拦截到调用,拼接出字符串 "com.example.mapper.UserMapper.getUserById"

  3. 查字典(寻址期):代理类拿着这个字符串去 StrictMapget,拿到了对应的 MappedStatement

  4. 引擎点火(执行期):代理类把你的参数 1 喂给 MappedStatement 里的 sqlSource,动态生成了带 ? 的原生 SQL。

  5. 落地反馈(映射期):最后调用 JDBC 的 PreparedStatement 执行查询,拿着结果交给 resultMaps 翻译成 Java 对象,返回给你。

双向映射的本质

  • 输入参数的映射 (ParameterMapping):核心是 OGNL,通过反射读取 Java 对象属性,设置到 PreparedStatement 的参数中。
  • 出参映射 (ResultMapping):核心是通过反射调用 Java 对象的 setter 方法或直接对字段赋值,将 ResultSet 的数据填充进去。

spring 整合mybatis#

导入整合需要的jar包 使用SqlSessionFactoryBean封装SqlSessionFactory需要的环境信息: Pasted image 20250924175017 方法中有一个参数为dataSource 自动注入了 使用MapperScannerConfigurer加载Dao接口,创建代理对象保存到IOC容器中: Pasted image 20250924175159

@Mapper注解#

MyBatis 对 DAO 的实现方式 程序运行时,MyBatis用动态代理技术为该接口创建一个实现类对象,并交给IOC容器管理

tips: IDEA可以配置SQL提醒、Mybatis日志输出

参数占位符号 #{?}#

需要传递多个参数的时候,可自动封装到一个对象中但#{…} 必须和对象属性同名 可全局配置 下划线 _ 到驼峰命名的转换 注意sql语句的别名!

Pasted image 20250911092128 resultType (自动结果映射,忽略大小写)无法满足需求时,就需要使用 <resultMap> 标签进行显式、手动的映射 只要表里有主键(或者能唯一标识一行数据的业务键),在写 <resultMap> 时,第一个属性一定要用 <id> 标签映射它,剩下的普通字段再按顺序用 <result> 映射。

${}(字符串拼接):底层相当于直接把变量的值拼接到 SQL 字符串中(如 Statement)。通常只在无法使用占位符的场景下使用,比如动态传入表名、列名或 ORDER BY 的排序字段,使用时必须由后端严格校验数据来源,防止注入风险。

@Options 主键返回#

Pasted image 20250913151655 前者表示使用数据库生成的主键 后者表示把主键复制给对象的哪个属性

多参数精确匹配 @Param#

User findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
//使用 `@Param("xyz")` 就是给每个参数起一个明确的名字(这里是 "username" 和 "password"),以便在 XML 文件中通过 `#{xyz}` 来引用它们。
plaintext

springboot官方骨架可以省略@Param注解 //其实就是这种情况下,编译后字节码文件保留了形参名

为什么表面上看,觉得#{} 里面写上形参名就行了”,那为什么还需要 @Param 呢? A:在 Java 里,方法参数名在 编译之后可能会丢失

(企业级替代方案) 传入实体类或 DTO 此时不需要 @Param,MyBatis 会自动通过 getter 方法读取对象的属性匹配 XML 中的 test 条件。

XML映射配置#

通用配置结构网上搜索即可;复杂的SQL功能使用XML来配置映射语句

  • namespace属性为mapper接口的全限定名(复制引用)
  • id与Mapper接口中的方法名一致,并保持resultType一致(单条记录所封装的类型)
  • XML映射文件位置在src下 同路同名 或者手动指定位置: Pasted image 20250909225914

动态SQL#

<if>:true,则拼接SQL。 <where>:根据查询条件,来生成where关键字,并会自动去除条件前面多余的and或or。 给每一个 <if> 条件后面都加上 and就完事了

e.name like concat('%',#{name},'%')
plaintext

预编译机制(PreparedStatement)定下了一个死规矩:SQL指令的结构(骨架)和数据是两回事。骨架在第一步编译后就固定了,第二步传入的数据永远只能是数据,绝对不能改变SQL的结构和含义。 LIKE '%?%'% 失去通配符含义,正是这个安全机制的直接体现。而 concat() 函数之所以能工作,是因为它本身就是SQL“骨架”的一部分,数据库在第一步编译时就知道要去执行一个拼接函数,这是计划内的工作。

<foreach> 遍历集合

<set>标签用来替换set关键字,同时可以自动去除掉更新字段后面多余的逗号

PageHelper#

  • PageHelper实现分页查询时,SQL语句的结尾一定一定一定不要加分号(;) 因为PageHelper要帮你在sql语句上拼接上limit啊
  • PageHelper只会对紧跟在其后的第一条SQL语句进行分页处理。 页码输入负数怎么办? 在PageHelper中,我们可以通过合理化参数配置,来解决这个问题。直接在application.yml中,引入reasonable配置

Mybatis-plus#

手动引入依赖

然后dao层的mapper接口extends baseMapper<>

注意需要配置mp主键auto 使用数据库的主键 不然就是雪花算法

mp条件查询 Pasted image 20250926135525 LambdaQueryWrapper 是 MyBatis-Plus 提供的一种查询构造器,用于构建 SQL 查询条件。专门通过 Lambda 表达式 的方式来引用实体类的字段,从而避免硬编码 字段名,提高代码的可读性和类型安全性

ServiceImpl<M, T> 是 MyBatis-Plus 提供的一个实现了 IService<T> 接口的通用 Service 实现类 理解为一个“开箱即用”的服务层模板

ServiceImpl 源码: 通过依赖注入调用 BaseMapper 的方法,ServiceImpl 提供业务层面的封装,BaseMapper 负责底层 SQL 执行

Maven#

仓库 本地 中央 私人 依赖管理 pom.xml 中 <dependency><exclusions> lifecycle clean package 等 Maven 插件可用于扩展 Maven 的功能 Maven Wrapper 是一个用于管理和使用 Maven 的工具,它允许在没有预先安装 Maven 的情况下运行和构建 Maven 项目

多模块管理,继承和聚合#

统一管理依赖版本#

在maven中,可以在父工程的pom文件中通过 <dependencyManagement> 来统一管理依赖版本。只是管理不引入 也可以通过自定义属性及属性引用的形式 Maven打包方式:

  • jar:普通模块打包,springboot项目基本都是jar包(内嵌tomcat运行)
  • war:普通web程序打包,需要部署在外部的tomcat服务器中运行
  • pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理

聚合:将多个依赖关系复杂的模块组织成整体,同时进行项目的构建。 聚合工程:一个不具有业务功能的“空”工程(有且仅有一个pom文件) 【PS:一般来说,继承关系中的父工程与聚合关系中的聚合工程是同一个】 作用:快速构建项目

SSM+Maven
https://lixuan.live/blog/ssm-maven
Author Chlx
Published at 2025年11月12日
Comment seems to stuck. Try to refresh?✨