SpringBoot小知识

1.启动类的那个注解,会默认扫描启动类所在的包及其子包下的所有Bean对象

若想要扫描其它位置的包,在启动类上手动添加注解


@ComponentScan(basePackages = "com.包名")
@SpringBootApplication
public class Demo1Application {

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

}

2.如何使用第三方jar包

1) 注册Maven到本地仓库

2) 把jar包中的Bean对象注入到容器中

33.Jar包的制作及使用

什么是Jar包?
相当于Python的库,在Java中,你可以把自己写好的程序做成Jar包让别人可以导入使用

IDEA中通过Maven来制作Jar包(企业开发唯一推荐方式)

  1. 新建一个Maven项目
  2. (在src/main中)写好Java代码后并完成测试
  3. 在右侧工具栏中选择Maven>生存期>package并双击,Maven就会自动打包

注:src/test目录下的代码不会被打包,仅用于测试

IDEA通过工件制作Jar包(仅个人使用,不推荐)

  1. 文件>项目结构>工件>
  2. 点中间上边的+
  3. 选择Jar>从具有依赖项的模块>确定
  4. 选择一个合适的输出路径,并点击右下角的应用
  5. 确定
  6. 在上方的菜单栏中选择构建
  7. 构建>构建工件>构建
  8. Jar包就出现在你选择的目录里了

IDEA里使用Jar包

  1. 创建一个与src同级的lib文件夹
  2. 将Jar包移动过去
  3. 右键该Jar包,选择添加到库

3.在IDEA中Maven的设置

设置>构建、执行、部署>构建工具>Maven

4.Bean对象的加载流程

  1. 配置Bean(以xml为例,因为较为直观)
   <bean class="com.cc.UserService" name="userService"/>
  1. 通过启动函数new ApplicationContext()来加载Bean
  2. 内部:将配置好的Bean封装进beanDefinition对象
  3. 根据beanDefinition创建Bean实例化对象
  4. 最终将Bean实例化对象放到IOC容器中

5.Spring @Value 注解的真实工作原理(修正版)

目标:用准确、直观、可背诵的方式理解 @Value 到底是谁在管、什么时候生效、怎么实现。


一、@Value 到底是什么?

正确认知

  • @Value 是一个 注解(Annotation)
  • 它:
    • ❌ 不是 Bean
    • ❌ 不是变量
    • ❌ 不由 JVM 管理
  • ✔️ 只是标记在 Bean 的字段 / 方法 / 构造器参数上的元数据

二、真正被管理的对象是谁?

核心事实

Spring 只管理 Bean,不管理单独的变量

@Component
public class Demo {

    @Value("${server.port}")
    private int port;
}
  • Demo 是 Bean(由 Spring 管理)
  • portDemo 的一个字段
  • @Value 的作用是告诉 Spring:
    在 Bean 创建过程中,为该字段注入一个值

三、@Value 的执行时机(非常关键)

❌ 常见错误理解

  • JVM 初始化阶段完成
  • 类加载阶段替换默认值
  • 编译期处理

✅ 正确执行时机

@Value 的赋值发生在 Spring 容器启动阶段,属于 Bean 生命周期的一部分

正确流程

  1. Spring 通过构造方法 实例化 Bean
  2. Spring 进入 依赖注入阶段(populateBean)
  3. 解析 @Value
    • 解析 ${} 占位符
    • 解析 #{} SpEL 表达式
  4. 通过反射为字段赋值
  5. 执行初始化逻辑
@PostConstruct
InitializingBean.afterPropertiesSet()
init-method

👉 @Value 的值一定早于初始化方法完成注入


四、@Value 的底层实现原理(简化版)

关键组件

  • BeanPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • PropertySourcesPlaceholderConfigurer

本质一句话

Spring 在 Bean 创建过程中,通过 BeanPostProcessor 扫描字段上的 @Value 注解,解析配置值,并使用反射为 Bean 字段赋值。


五、@Value 与 @Autowired 的关系

对比理解(实现机制一致)

对比项 @Value @Autowired
是否是注解
是否是 Bean
注入内容 配置值 / 表达式 Bean 对象
执行阶段 Bean 创建期 Bean 创建期
底层方式 反射 + 后处理器 反射 + 后处理器

六、一句话终极总结(面试版)

@Value 本身不是 Bean,也不由 JVM 管理。它只是一个注解。Spring 在容器启动、创建 Bean 的过程中,通过 BeanPostProcessor 解析 @Value,并使用反射为 Bean 的字段注入配置值。


七、硬背浓缩版

@Value 是 Spring 容器在 Bean 生命周期中完成的值注入行为,而不是 JVM 初始化阶段的变量替换。

6.Bean是单例的,会不会有线程安全问题

线程安全问题:多线程并发的情况下,同时对同一个共享资源(Bean)进行读写,就出现线程不安全问题:脏读、数据篡改等

会不会有线程安全问题:会!

但是,只要不在单例Bean中声明一些共享的类成员变量(共享资源)。并且不对共享资源进行读写,也不会出现线程安全问题。

7.Bean的生命周期

  1. 程序员配置@Component、@Bean...@Autowired等注解,来注册Bean对象
  2. 加载Spring容器
  3. 实例化Bean对象
  4. 解析依赖注入(解析@Autowired @Value等)
    1. @Autowired 注入的依赖确实由 Spring 容器提供,但在 Bean 创建阶段,Spring 会使用内部缓存(而非最终单例池)来解析依赖;只有当 Bean 完成依赖注入和初始化后,才会被放入最终的容器 Map,对外提供。
  5. 初始化(调用初始化回调方法,由程序员来配置)。关于初始化回调方法的实现,详见:二十、配置初始化回调方法
  6. 最终放入Map<beanName,bean对象>(将Bean对象放入Spring容器,注:这是最终的、最完整的、对外提供的Map对象)
  7. Spring容器.getBean("beanName")--->Map<beanName,bean对象>

注:从Map中获取Bean对象

  1. Spring容器关闭bean就会销毁调用销毁回调方法,由程序员来配置
    1. 在SpringBoot中当JVM进程结束后会自动调用容器.close()方法,自动销毁
    2. 在Spring中,我们需要手动调用容器.close()方法,才会进行销毁
    3. 当 Spring 应用未执行销毁逻辑而被强制关闭时,Bean 的销毁回调不会执行,连接、线程等资源可能无法释放,属于非优雅终止。会导致你在销毁方法里写的 关闭资源、刷数据、发通知 等逻辑全部丢失,Spring 没有机会做“收尾工作”。这是操作系统层面的“硬杀”

8.循环依赖

Spring只允许「单例 + setter/字段注入」形式的循环依赖,而SpringBoot默认禁止任何循环依赖

Spring允许的循环依赖(只允许这一种)

@Component
class A {
    @Autowired
    B b;
}

@Component
class B {
    @Autowired
    A a;
}

原因是:

  1. A 可以先 new A()
  2. Spring 把 A 的“早期引用”放进三级缓存
  3. 创建 B
  4. B 注入 A
  5. A 再完成注入 B
    👉 前提:必须是「字段 / setter 注入」

不允许的循环依赖

BeanA.java

public class BeanA {  
    @Autowired  
    private BeanB beanB;  
}

BeanB.java

public class BeanB {  
    @Autowired  
    private BeanA beanA;  
}

xunhuanTest.java

@SpringBootTest(classes = xunhuanTest.class)  
public class xunhuanTest {  
    @Bean  
    public BeanA beanA(){  
        return new BeanA();  
    }  
    @Bean  
    public BeanB beanB(){  
        return new BeanB();  
    }  
  
    @Test  
    public void test(){  
        System.out.println("测试");  
    }  
}

原因:
构造器注入是这样的:

new BeanA(BeanB b)  ← 没有 b,A 连 new 都 new 不出来

流程卡死

创建 A
 → 需要 B
   → 创建 B
     → 需要 A
       → A 还没创建出来 ❌

连“半成品对象”都不存在,三级缓存根本无从谈起

解决方案

  1. 在配置文件中放开限制(不推荐)
  2. 把需要依赖的方法添加到本类中
  3. 新建一个类C,把A和B一起要调用的方法移植到C中,让C去依赖A和B。就避免了循环依赖
  4. 暴力方案:利用构造函数进行依赖,并在构造函数上配置@Lazy懒加载。

9.AOP编程:面向切口编程

AOP的核心概念及术语

目标对象(Target)

  • 将要被增强的对象
  • 即包含主业务逻辑的类的对象。
  • 通常会有很多个这样的对象。

切面(Aspect)

  • 关注点模块化,这个关注点可能会横切多个对象。
  • 例如:事务管理是企业级Java应用中典型的横切关注点。
  • 在Spring AOP中,切面可以通过两种方式实现:
    1. 基于模式的方式(schema-based approach)
    2. 使用@Aspect注解的方式(@AspectJ 注解方式)
  • 通俗理解:存放增强代码的那个类就叫切面类,通常用@Aspect标注。

通知(Advice)

  • 定义:在切面的某个特定的连接点上执行的动作。
  • 特点
    • 通知有多种类型,包括“around”、“before”、“after”等。
    • 许多AOP框架(包括Spring)以拦截器作为通知模型,并维护一个以连接点为中心的拦截器链
  • 通俗理解:用来放置增强代码的那个方法。

通知类型

  1. 环绕通知 @Around

    • 可以把代码增强在目标方法的任意位置,功能最通用。
  2. 前置通知 @Before

    • 目标方法执行之前执行。
  3. 后置通知 @After

    • 目标方法执行之后执行(无论是否发生异常)。
  4. 异常通知 @AfterThrowing

    • 目标方法出现异常时执行。
  5. 返回通知 @AfterReturning

    • 目标方法正常返回结果后执行。(在后置通知之前执行)

执行顺序:
正常:前置通知->目标方法->返回通知->后置通知(finally)
异常:前置通知->目标方法->异常通知->后置通知(finally)

连接点(Join point)

  • 定义:在Spring AOP中,一个连接点总是代表一个方法的执行
  • 通俗理解
    1. 实际上就代表被增强的那个方法(即目标方法)。
    2. 它是通知(Advice)和目标方法之间的桥梁
  • 关键作用:如果要获取目标方法的相关信息(如方法名、参数等),就必须通过 JoinPoint 对象。

连接点参数:(本质上都来自JoinPoint类)

参数 来源 可用通知 作用
JoinPoint Spring AOP 除 @Around 外 获取方法信息
ProceedingJoinPoint Spring AOP @Around 控制方法执行
Signature JoinPoint 所有 方法签名
MethodSignature Signature 所有 方法详细信息
Object[] args JoinPoint 所有 方法参数
target JoinPoint 所有 目标对象
this JoinPoint 所有 代理对象
returnValue @AfterReturning AfterReturning 获取返回值
exception @AfterThrowing AfterThrowing 获取异常

顾问(Advisor)

定义与作用

  • 定义:顾问是 Advice(通知)的一种包装体现
  • 本质:Advisor 是 Pointcut(切点)Advice(通知) 的一个结合体。
  • 主要功能:用来管理 Advice 和 Pointcut
  • 对开发者的影响:应用开发者通常无需关心其具体实现细节。

在源码中的体现

  • 在 Spring AOP 的底层源码中,Advisor 会封装切点和通知,将它们组合成一个完整的增强单元。

植入

将通知切入连接点的过程叫植入

10.Spring AOP 切点表达式语法(从基础到进阶)

一、什么是切点表达式(Pointcut)

一句话理解:
切点表达式用于定义在哪些方法上织入增强(Advice)

  • 切点(Pointcut):决定 切哪里
  • 通知(Advice):决定 做什么
  • 切面(Aspect):切点 + 通知

二、核心基础:execution 表达式与within表达式

Spring AOP 中最常用、最核心的切点表达式是 execution

  • execution定位到具体方法
  • within只定位到类

基本结构

execution(访问修饰符 返回值 包名.类名.方法名(参数))
within(包名.类名)
within(包名.*)
within(包名..*)

注:返回修饰符可省略,代表匹配任意返回修饰符(除private)
within不能加返回修饰符

完整示例(了解即可)

execution(public void com.example.service.UserService.addUser(String))

实际开发中几乎不会写得如此精确。


三、通配符规则(重点)

1️⃣ * —— 匹配单个元素

位置 含义
返回值 任意返回值
方法名 任意方法
包名 任意一层包

示例

# 任意返回值
execution(* com.example.service.UserService.addUser(..))

# 任意方法
execution(* com.example.service.UserService.*(..))

# 任意类
execution(* com.example.service.*.*(..))

2️⃣ .. —— 匹配多个

用法 含义
参数位置 任意参数
包路径 任意层级包

示例

# 任意参数
execution(* com.example.service.UserService.addUser(..))

# service 包及其所有子包
execution(* com.example.service..*.*(..))

四、常见切点写法(必会)

1️⃣ 切某个类的所有方法

execution(* com.example.service.UserService.*(..))

2️⃣ 切某个包下所有类的方法

execution(* com.example.service.*.*(..))

3️⃣ 切某个包及其子包(最常见)

execution(* com.example.service..*.*(..))

4️⃣ 按方法名规则匹配

execution(* *.add*(..))

匹配:

  • addUser
  • addOrder
  • addXXX

五、参数匹配(进阶)

1️⃣ 指定参数类型

execution(* com.example.service.UserService.addUser(String))

2️⃣ 指定参数数量

execution(* com.example.service.UserService.addUser(*))

3️⃣ 任意参数(最常用)

execution(* com.example.service.UserService.addUser(..))

六、访问修饰符匹配(了解)

execution(public * *(..))
execution(private * *(..))

注意:Spring AOP 基于代理机制,private 方法无法被增强,实际开发中很少写访问修饰符。


七、使用 @Pointcut 复用切点(推荐)

@Aspect
@Component
public class LogAspect {

    @Pointcut("execution(* com.example.service..*.*(..))")
    public void servicePointcut() {}

    @Before("servicePointcut()")
    public void before() {
        System.out.println("前置通知");
    }
}

优点:

  • 切点集中管理
  • 提高可读性
  • 方便维护和复用

八、结合通知类型使用(实战)

1️⃣ 前置通知

@Before("execution(* com.example.service..*.*(..))")
public void before() {}

2️⃣ 后置通知

@After("execution(* com.example.service..*.*(..))")
public void after() {}

3️⃣ 环绕通知(最常用、最强)

@Around("execution(* com.example.service..*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = pjp.proceed();
    long end = System.currentTimeMillis();
    System.out.println("方法耗时:" + (end - start));
    return result;
}

九、进阶:多个切点组合

逻辑运算符

运算符 含义
&& 并且
`
!

示例

execution(* com.example.service..*.*(..))
&& execution(* *.add*(..))

含义:
service 包及子包中 方法名以 add 开头的方法


十、切点表达式编写经验总结

✅ 推荐(企业级常用)

execution(* com.xxx.project.service..*.*(..))

❌ 不推荐

  • 表达式写得过于精确(维护成本高)
  • 随意切 controller / dao(除非明确需求)
  • private 方法使用 AOP(无效)

11.Spring AOP的底层原理

  1. 创建容器ioc = new applicationContext();
  2. Spring把所有Bean进行创建,进行依赖注入
  3. Spring(new $$SpringCGLIB$$0() ) --->ioc.getBean(UserService.class)
  4. 当实现了AOP后,Spring会根据当前的Bean创建动态代理,作用是为了方便进行AOP操作
  5. 把bean替换成创建的动态代理--->所以自动装配的类就称为new $$SpringCGLIB$$0()

12.动态代理

Proxy 类中使用频率最高的方法是:newProxyInstance(),该方法主要用于生成一个代理对象。

方法签名

public static Object newProxyInstance(ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h)
throws IllegalArgumentException
{
    ……
}

参数说明

该方法共有 3 个参数

1. loader

  • 类型ClassLoader
  • 作用:用于加载代理对象
  • 传入内容:被代理类的类加载器

2. interfaces

  • 类型Class<?>[]
  • 作用:被代理类实现的一些接口
  • 注意:被代理的类需要实现接口

3. h

  • 类型InvocationHandler
  • 作用:实现了 InvocationHandler 接口的对象
  • 功能:用于定义代理对象的方法调用行为

JDK动态代理的底层实现逻辑

IUserService接口

package org.example.c4_aop.advice.autuproxy;  
  
public interface IUserService {  
    void add();  
}

UserService.java:被代理类

package org.example.c4_aop.advice.autuproxy;  
  
public class UserService implements IUserService{  
    @Override  
    public void add(){  
        System.out.println("增加用户");  
    }  
}

MyHandler.java:增强方法

package org.example.c4_aop.advice.autuproxy;  
  
import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
  
public class MyHandler implements InvocationHandler {  
    /*应该接收一个方法,作为被执行的原方法*/  
    Object target;  
  
    public MyHandler(Object  target){  
        this.target = target ;  
    }  
  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  
        /*实现增强代码*/  
        System.out.println("前置增强");  
  
        /*执行目标方法(通过反射的方式)*/  
        Object returnValue = method.invoke(target,args);  
  
        System.out.println("后置增强");  
        return returnValue;  
    }  
}

TestJDKProxy.java

package org.example.c4_aop.advice.autuproxy;  
  
import org.junit.jupiter.api.Test;  
  
import java.lang.reflect.Proxy;  
  
public class TestJDKProxy {  
    @Test  
    public void test(){  
        IUserService userService = (IUserService) Proxy.newProxyInstance(  
                UserService.class.getClassLoader(),//传入被代理类的类加载器  
                UserService.class.getInterfaces(),//传入一个被代理类需要实现的接口列表(要以列表的形式)。该接口最终会被生成的代理类实现  
                new MyHandler(new UserService())//传入一个InvocationHandler对象,该对象会实现代理类中的方法(增强代码)  
        );  
        //生成出来的代理类实现了IUserService接口  
        //在Spring的底层,相当于把代理类和被代理类都放进了容器中,然后Spring会根据代理类去获取被代理类对象,然后调用被代理类的方法  
        userService.add();  
    }  
}

cglib动态代理

cglib是第三方jar包,但是已被Spring整合了
相比于JDK动态代理的特点:语法更简洁、不需要依赖被代理类的接口

IUserService接口

package org.example.c4_aop.advice.autuproxy;  
  
public interface IUserService {  
    void add();  
}

UserService.java:被代理类

package org.example.c4_aop.advice.autuproxy;  
  
public class UserService implements IUserService{  
    @Override  
    public void add(){  
        System.out.println("增加用户");  
    }  
}

MyCallback.java:处理类(里面写增强代码)

package org.example.c4_aop.advice.autuproxy.cglibProxy;  
  
import org.springframework.cglib.proxy.MethodInterceptor;  
import org.springframework.cglib.proxy.MethodProxy;  
  
import java.lang.reflect.Method;  
  
//应该继承Callback接口。MethodInterceptor继承自Callback,是对它的更成熟的封装  
public class MyCallback implements MethodInterceptor {  
    //接收目标对象  
    Object target;  
    public MyCallback(Object target){  
        this.target = target;  
    }  
    @Override  
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {  
        /*增强*/  
  
        /*前置*/  
        System.out.println("前置增强");  
  
        /*目标对象*/  
        //与用method差不多,用哪个都可以  
        Object returnValue = proxy.invoke(target, args);  
        //区别是proxy.invoke不是通过反射的方式,而是直接调用父类的方法
  
        /*后置*/  
        System.out.println("后置增强");  
  
        return returnValue;  
    }  
}

TestCGlibProxy.java:测试类

package org.example.c4_aop.advice.autuproxy.cglibProxy;  
  
import org.example.c4_aop.advice.autuproxy.UserService;  
import org.junit.jupiter.api.Test;  
import org.springframework.cglib.proxy.Enhancer;  
  
public class TestCGlibProxy {  
  
    @Test  
    public void test(){  
        Enhancer enhancer = new Enhancer();  
  
        //设置被代理的类  
        enhancer.setSuperclass(UserService.class);  
  
        //设置处理类(处理类应实现CallBack接口)  
        enhancer.setCallback(new MyCallback(new UserService()));  
  
        //生成的代理类,继承自被代理类(UserService)(所以不需要实现被代理类的接口)  
        UserService proxy = (UserService) enhancer.create();  
        proxy.add();//执行代理类的原来的方法(已经被增强过)  
    }  
}

JDK动态代理与cglib动态代理的区别

  1. JDK只能代理实现了接口的类
  2. 而cglib可以代理未实现任何接口的类
  3. 另外cglib动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final类型的类和方法

Spring默认采用JDK动态代理。判断目标类是否实现接口,如果实现了接口JDK动态代理,否则就使用CGLIB
SpringBoot2.x后默认使用的是CGLIB动态代理

  1. 从速度层面来讲,jdk8+后,二者相差无几。严谨来说的话,jdk动态代理更快。因为jdk只需要生成一个代理类。而cglib需要生成三个代理类
  2. 在jdk1.8前,反射的性能不佳,cglib完胜。但在1.8之后对反射性能做了优化、加入了缓存等。所以相差无几

13.事务ACID

1.ACID的四大特性

  1. A原子性:在一组业务操作下,要么都成功,要么都失败。在一组增删改查的业务中,要么都提交,要么都回滚。
  2. C一致性:事务前后的数据要保证数据的一致性。在一组的查询业务下,必须保证前后关联数据的一致性
  3. I隔离性:在并发情况下,事务之间要互相隔离
  4. D持久性:数据一旦保存,就是持久性的

14.DataSource数据库连接池

用户与数据库的连接可以添加数据库连接池作为中介。
数据库连接池的作用是开一些与数据库的连接源,连接源稳定与数据库连接。用户通过与连接源进行连接从而减少对数据库连接的次数,减少了IO,提高了性能。
同时连接池还有易于扩充和收缩,具有高灵活性的特点

15.事务的传播行为

传播行为 描述
REQUIRED 如果存在当前事务,则加入该事务;如果没有,则新建一个事务。 默认行为
REQUIRES_NEW 始终新建事务,如果存在当前事务,则把当前事务挂起。
SUPPORTS 如果存在事务,则加入事务;如果没有事务,也可以正常执行(非事务执行)。
NOT_SUPPORTED 始终非事务执行,如果存在事务,则挂起当前事务。
MANDATORY 必须在已有事务中运行,如果没有事务,则抛异常。
NEVER 必须在非事务环境下运行,如果存在事务,则抛异常。
NESTED 如果存在事务,则在嵌套事务中执行(可以单独回滚嵌套事务);如果没有事务,则表现为 REQUIRED

16.事务的属性

在 Spring(尤其是 @Transactional)中,事务的核心属性可以概括为 5 个方面。掌握它们,就能覆盖绝大多数业务场景。


一句话总览

事务 = 传播行为 + 隔离级别 + 回滚规则 + 超时 + 是否只读


1️⃣ 传播行为(Propagation)

决定方法被调用时,是否使用已有事务

常见类型:

  • REQUIRED(默认):
    有事务就加入,没有就新建 ✅
  • REQUIRES_NEW
    不管有没有事务,都新建一个(原事务挂起)
  • SUPPORTS
    有事务就用,没有就不用
  • NOT_SUPPORTED
    以非事务方式执行
  • MANDATORY
    必须存在事务,否则抛异常
  • NESTED
    嵌套事务(依赖数据库支持)

👉 重点掌握:REQUIREDREQUIRES_NEW


2️⃣ 隔离级别(Isolation)

决定事务之间的数据可见性,用于解决并发问题

  • READ_UNCOMMITTED:可能脏读(极少使用)
  • READ_COMMITTED:防止脏读(Oracle 默认)
  • REPEATABLE_READ:防止不可重复读(MySQL 默认)
  • SERIALIZABLE:最安全,性能最差

👉 一般不需要手动设置,使用数据库默认即可


3️⃣ 回滚规则(Rollback Rules)

决定发生异常时是否回滚事务

默认行为:

  • RuntimeExceptionError → 回滚
  • 普通 Exception(受检异常)→ 不回滚

可自定义配置:

  • rollbackFor
  • noRollbackFor

👉 这是事务中最容易踩坑的点


4️⃣ 超时时间(Timeout)

限制事务允许执行的最长时间

  • 超过时间 → 自动回滚
  • 防止事务长时间占用资源

示例:

@Transactional(timeout = 5)

(单位:秒)


5️⃣ 是否只读(ReadOnly)

用于纯查询场景

@Transactional(readOnly = true)

作用:

  • 告诉数据库这是只读事务
  • 提升查询性能
  • 在部分数据库中,写操作会失败或被忽略

👉 查询方法强烈建议加


常见实战组合

1️⃣ 普通业务方法

@Transactional

2️⃣ 回滚所有异常(推荐)

@Transactional(rollbackFor = Exception.class)

3️⃣ 查询接口

@Transactional(readOnly = true)

4️⃣ 独立事务(日志、消息、审计)

@Transactional(propagation = Propagation.REQUIRES_NEW)

记忆口诀

传隔回超只
(传播、隔离、回滚、超时、只读)

注:不能在事务方法中捕捉异常,哪怕捕捉了也要主动抛出去异常。不然事务注解就捕捉不到异常了,自然也就起不到相关作用。事务处理机制的本质就是AOP+try-catch捕捉异常并处理

17.事务失效的原因

  1. 保证事务的类,配置为了一个Bean
  2. 事务的方法不能是private
  3. 事务方法把异常捕获了,并且没有抛出去
  4. 动态代理层面失效原因
    1. 要让AOP、事务生效记住一个原则:一定要通过动态代理的对象调用目标方法,不能通过普通对象直接调用
    2. 直接调用本类的方法 解决方法
      1. 新开一个类,把被需要的本类方法放到另一个类里,然后自动装配
      2. 使用AopContext.currentProxy()方法,获取当前 AOP 代理对象。就可以在类内部调用自身的另一个方法(需要先引入AOP依赖)注:
        1. 只有 被 Spring AOP 代理的对象 才能使用 AopContext.currentProxy()
        2. 需要在配置类或 XML 中开启:@EnableAspectJAutoProxy(exposeProxy = true)
          1. @EnableAspectJAutoProxy:开启 Spring 注解式 AOP 支持,创建代理对象,处理切面逻辑
          2. exposeProxy = true:在内部方法调用时,可以通过 AopContext.currentProxy() 获取代理对象,从而触发切面逻辑
        3. 返回的对象后是Object类型,有需要可以强转为你需要的类型,如:(StockService) AopContext.currentProxy()

18.Spring AOT提前优化

一种编译方式的变革,旨在提升启动速度

JIT,just-in-time ,动态(即时)编译,运行时编译
AOT,Ahead Of Time,指运行前编译,是两种程序的编译方式

需要先下载GraalVM。一般SpringBoot3下载Java17对应的版本。

设置环境变量(代替jdk)

变量名
JAVA_HOME D:\GraalVM\graalvm-community-jdk-25.0.0_windows-x64_bin\graalvm-community-openjdk-25+37.1
path %JAVA_HOME%\bin

19.AOT报错的三种解决方案

当AOT遇到不明确的方法时可能会报错,有三种解决方案。(详细去ai)

1.@RegisterReflectionForBinding(XushuService.class)

最简单

2.声明一个静态内部类实现RuntimeHintsRegistrar接口

3.@Reflective

对于反射用到的地方,我们可以直接加该注解。前提是被标注的类应该是一个Bean

20.Spring内部是通过反射来创建对象的,而不是直接new

21.Spring6最核心的技术点是IOC、AOP、声明式事务

22.Bean对象的创建及四种形态

  1. 概念态:@Bean。在容器还没有创建的时候,自己写的@Bean就是概念态,包括@Service等注解
  2. 定义态:底层会把Bean定义的各种属性(如@Lazy等Bean的定义信息)装进BeanDefinition这个对象中。(这是一个接口,有实现类)此步也会创建BeanDefinitionMap<beanName,BeanDefinition>
  3. 纯净态:使用反射的方式,通过BeanDefinition这个对象来得到Bean的实例对象(类似于new 了一个对象。但此时只是创建了一个对象,代码里需要依赖注入的属性还是null)需要经过以下几个步骤能变成成熟态
    • 依赖注入:解析@Autowired
    • 初始化:afterPropertiesSet()
    • 缓存:单例Bean,缓存在Map<BeanName,bean对象>这样一个字典中
  4. 成熟态:最后能通过getBean获取到的对象。getBean("BeanName")可以通过BeanName来拿到Bean对象

23.IOC加载过程

@Component--->new ApplicationContext()--->refresh->invokeBeanFactoryPostProcessors
->finishBeanFactorylnitialization---doGetBean--bean是否已经创建完成-doCreateBean-->
实例化-->属性注入(doGetBean("orderServer'")--初始化--map

posted @ 2026-07-02 17:43  畅畅c  阅读(0)  评论(0)    收藏  举报