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

@SpringBootApplication 注解内部就包含了一个 @ComponentScan,它默认会扫描启动类所在的当前包及其所有子孙包,将所有分散的配置类和组件一网打尽
DI 依赖注入#
IoC思想的具体实现形式,主要包含对象注入和值注入
@Autowired注解,默认是按照引用类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)- @Resource 默认按名称(By Name)注入
- 隐式构造器注入:如果一个类只有一个构造方法,那么 Spring 默认就会使用这个构造方法进行依赖注入,不需要你在上面写
@Autowired
可用@Autowired(required=false)允许可选注入
存在相同类型bean怎么办: 结合@Qualifier指定bean的ID bean的默认ID是将类名的首字母小写 或者可以起别名

Bean的初始化和清理方法上标记@PostConstruct和@PreDestroy
自动装配#
自动装配的目的就是让当前模块和包, 在极少量配置的情况下注入第三方组件和包的 Bean。 加载配置类的三种思路
- 通过@componentScan 指定组件扫描范围(默认的范围失效)
- @Import
导入普通类-导入配置类-导入选择器(ImportSelector接口实现类)
ImportSelector是 Spring框架提供的一个接口。当一个@Configuration类通过@Import注解导入了一个ImportSelector的实现类时,Spring 容器在初始化时会执行这个实现类,并将其返回的类名数组所对应的类都加载为 Bean。 - 第三方依赖自定义的 @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.properties 或 application.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注解,该注解的作用是将公共的切入点表达式抽取出来,像一个变量名一样可以复用
- execution指示器 最常用,基于方法的签名结构
*:匹配任意符号(常用)..:匹配多个连续的任意符号(常用)+:匹配子类类型
- @annotation基于自定义注解的方式来匹配切入点方法。灵活 适合做特定业务场景下的定制化拦截。
通知的执行顺序:#
- 不同的切面类当中,默认情况下通知的执行顺序是与切面类的类名字母排序是有关系的
- 可以在切面类上面加上@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 控制事务传播行为#
常见值
- REQUIRED(默认值)加入或者创建 如果当前已存在事务(即调用它的外部方法已经开启了事务),那么当前方法就会加入到这个已存在的事务中,成为这个大事务的一部分。如果当前没有事务,那么Spring就会为当前方法创建一个新的事务。
默认的是“命运共同体”,一损俱损
- REQUIRES_NEW 总是新的

在配置类开启注解式事务驱动 @EnableTransactionManagement spring中,需要手动创建 TransactionManager Bean, 并关联 DataSource
原理#
当你在一个方法上加上 @Transactional 时,Spring 会为这个类生成一个代理对象(Proxy)。当你通过代理对象调用这个方法时,Spring 的事务拦截器(TransactionInterceptor)会介入,偷偷帮你做以下几件事:
- 拦截请求:拿到数据库连接。
- 开启事务:执行
connection.setAutoCommit(false)。 - 放行:调用你写的实际业务代码(目标对象的方法)。
- 提交/回滚:
- 如果业务代码正常执行完,拦截器执行
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

参数接收#
- 接收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 声明

拦截器 Interceptor#
- 定义拦截器 实现HandlerInterceptor接口
拦截器是Spring框架中提供的,需要交给IOC容器管理。@Component
- 注册配置拦截器
创建一个配置类实现
WebMvcConfigurer接口,并重写addInterceptors方法返回个拦截器
postHandle:在 Controller 执行完、视图渲染之前被调用,主要用于修改ModelAndView的场景。如果 Controller 里直接写回 JSON,这个阶段一般没什么可做的afterCompletion:在整个请求完成之后被调用,且无论是否出现异常都会执行(对于那些已经通过preHandle的拦截器),适合做清理/记录/异常处理/释放 ThreadLocal 等收尾工作。
拦截器参数#
- HttpServletRequest request:请求对象
- HttpServletReponse response:响应对象
- handler:被调用的处理器对象,本质上是一个方法对象,对反射中的Method对象进行了再包装
Mybatis#
封装了jdbc操作
原理#
现在我们把这些底层零件全部拼装起来,一窥 MyBatis 的全貌:
-
项目启动(装载期):MyBatis 读取 XML,把每一个
<select>标签封装成重型的MappedStatement对象,并以接口方法全限定名为 Key,安全地塞进Configuration.StrictMap这个改造过的 HashMap 里。 -
代码调用(拦截期):你在 Service 里调用
userMapper.getUserById(1)。动态代理类拦截到调用,拼接出字符串"com.example.mapper.UserMapper.getUserById"。 -
查字典(寻址期):代理类拿着这个字符串去
StrictMap里get,拿到了对应的MappedStatement。 -
引擎点火(执行期):代理类把你的参数
1喂给MappedStatement里的sqlSource,动态生成了带?的原生 SQL。 -
落地反馈(映射期):最后调用 JDBC 的
PreparedStatement执行查询,拿着结果交给resultMaps翻译成 Java 对象,返回给你。
双向映射的本质:
- 输入参数的映射 (ParameterMapping):核心是 OGNL,通过反射读取 Java 对象属性,设置到 PreparedStatement 的参数中。
- 出参映射 (ResultMapping):核心是通过反射调用 Java 对象的
setter方法或直接对字段赋值,将 ResultSet 的数据填充进去。
spring 整合mybatis#
导入整合需要的jar包
使用SqlSessionFactoryBean封装SqlSessionFactory需要的环境信息:
方法中有一个参数为dataSource 自动注入了
使用MapperScannerConfigurer加载Dao接口,创建代理对象保存到IOC容器中:

@Mapper注解#
MyBatis 对 DAO 的实现方式 程序运行时,MyBatis用动态代理技术为该接口创建一个实现类对象,并交给IOC容器管理
tips: IDEA可以配置SQL提醒、Mybatis日志输出
参数占位符号 #{?}#
需要传递多个参数的时候,可自动封装到一个对象中但#{…} 必须和对象属性同名
可全局配置 下划线 _ 到驼峰命名的转换
注意sql语句的别名!
resultType (自动结果映射,忽略大小写)无法满足需求时,就需要使用 <resultMap> 标签进行显式、手动的映射
只要表里有主键(或者能唯一标识一行数据的业务键),在写 <resultMap> 时,第一个属性一定要用 <id> 标签映射它,剩下的普通字段再按顺序用 <result> 映射。
${}(字符串拼接):底层相当于直接把变量的值拼接到 SQL 字符串中(如 Statement)。通常只在无法使用占位符的场景下使用,比如动态传入表名、列名或 ORDER BY 的排序字段,使用时必须由后端严格校验数据来源,防止注入风险。
@Options 主键返回#
前者表示使用数据库生成的主键
后者表示把主键复制给对象的哪个属性
多参数精确匹配 @Param#
User findByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
//使用 `@Param("xyz")` 就是给每个参数起一个明确的名字(这里是 "username" 和 "password"),以便在 XML 文件中通过 `#{xyz}` 来引用它们。plaintextspringboot官方骨架可以省略@Param注解 //其实就是这种情况下,编译后字节码文件保留了形参名
为什么表面上看,觉得
#{}里面写上形参名就行了”,那为什么还需要@Param呢? A:在 Java 里,方法参数名在 编译之后可能会丢失
(企业级替代方案) 传入实体类或 DTO 此时不需要 @Param,MyBatis 会自动通过 getter 方法读取对象的属性匹配 XML 中的 test 条件。
XML映射配置#
通用配置结构网上搜索即可;复杂的SQL功能使用XML来配置映射语句
- namespace属性为mapper接口的全限定名(复制引用)
- id与Mapper接口中的方法名一致,并保持resultType一致(单条记录所封装的类型)
- XML映射文件位置在src下 同路同名 或者手动指定位置:

动态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条件查询
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:一般来说,继承关系中的父工程与聚合关系中的聚合工程是同一个】 作用:快速构建项目