note · 7,908

Spring

Spring:IOC、AOP、事务、Bean 生命周期、循环依赖、常见注解。

Spring笔记

Spring

Spring SpringBoot SpringMVC

  • Spring
    • 核心理念:控制反转(IOC) 依赖注入(DI)
    • 面向切面编程 (AOP)
    • 庞大完善的生态系统
  • SpringBoot 解决配置繁琐的问题
    • 自动配置 (Auto-Configuration)
    • 内嵌服务器
    • 约定优于配置
  • SpringMVC Model-View-Controller
    • 专门处理 Web 相关的逻辑。

单例Bean是线程安全的吗

特性线程安全吗?解释最佳实践
无状态(Stateless)的单例Bean。没有可变的共享成员变量,因此不会出现数据冲突。这是推荐的设计方式,适用于绝大多数业务场景(如Service, Repository)。
有状态(Stateful)的单例Bean拥有可变的共享成员变量,多线程并发访问时会产生竞态条件。应极力避免。如果必须要有状态,请考虑改变作用域或使用ThreadLocal等技术。

Spring常见注解

Spring Boot的核心思想是 “约定优于配置”,而注解是实现这一目标的关键。

  • 核心与启动类注解 @SpringBootApplication
    • @SpringBootConfiguration: 作用和 @Configuration 一样,表明这个类是一个Java配置类。
    • @EnableAutoConfiguration: 这是Spring Boot自动配置的魔法所在。它会根据项目中引入的依赖(比如 spring-boot-starter-web),自动配置相关的Bean(比如 TomcatDispatcherServlet)。
    • @ComponentScan: 告诉Spring从哪个包开始扫描组件(如 @Component, @Service 等)。默认是从当前类所在的包及其子包开始。
  • Controller层
    • @RestController: 这是一个组合注解,相当于 @Controller@ResponseBody 的结合。它表明这个类中的所有方法返回的都是JSON或XML等数据,而不是视图(View)名。
    • @RequestMapping("/path"): 这是最通用的请求映射注解,可以用于类和方法上,指定处理请求的URL路径。@GetMapping, @PostMapping, @PutMapping, @DeleteMapping: 这些是 @RequestMapping 的细化版本,分别对应HTTP的GET, POST, PUT, DELETE方法,让代码意图更清晰。
    • @PathVariable: 用于获取URL路径中的动态参数,比如 /users/{id} 中的 id
    • @RequestParam: 用于获取查询参数(Query Parameters),即URL中 ? 后面的参数,比如 /users?name=zhangsan
    • @RequestBody: 用于将HTTP请求的Body(通常是JSON格式)反序列化为Java对象。
  • Service层
    • @Service: 通常用在业务逻辑层(Service层),表示这是一个服务类。它在功能上和 @Component 类似,但更具语义化。
    • @Repository: 用于数据访问层(DAO/Repository层),同样是为了语义化,并且它能让Spring的异常转换处理器捕获并处理平台无关的数据库异常。
    • @Component: 这是一个通用的组件注解,是 @Service@Repository 的父注解。当一个Bean不方便归类到上述两者时,可以使用 @Component
    • @Autowired: 这是Spring最核心的注解之一,用于实现依赖注入(DI)。Spring会自动寻找匹配类型的Bean,并注入到标记的字段、构造方法或Setter方法上。
  • 配置与Bean管理注解
    • @Configuration: 声明当前类是一个配置类,它会包含一个或多个用 @Bean 注解的方法。
    • @Bean: 用在方法上,声明这 个方法的返回值是一个由Spring容器管理的Bean。常用于创建第三方库中的对象实例。
    • @Value("${property.name}"): 用于从 application.propertiesapplication.yml 配置文件中读取值,并注入到类的字段中。
    • @ConfigurationProperties(prefix = "prefix.name"): 这是一个更强大的配置注入方式,可以将配置文件中以特定前缀开头的一组属性,批量绑定到一个Java对象(POJO)的字段上,非常适合处理复杂的配置结构。
    • @ConditionalOn...: 这是一组条件注解,比如 @ConditionalOnClass(当类路径下有指定的类时)或 @ConditionalOnProperty(当配置文件中有指定的属性时),它们是实现Spring Boot自动配置的关键,能够根据不同的环境和条件来决定是否创建某个Bean。

补充一些其他常用注解

- `@Aspect`: 用于声明一个切面,结合 `@Pointcut`, `@Before`, `@After` 等注解可以实现AOP(面向切面编程),常用于日志记录、事务管理等。
- `@Transactional`: 用在方法或类上,声明该方法或该类下的所有方法需要进行事务管理。
- `@Entity`: 这是JPA(Java Persistence API)的注解,用于声明一个类是实体类,对应数据库中的一张表。

AOP - 面向切面编程

  • 动态代理实现
    • JDK动态代理 利用反射机制
    • CGLIB 代理 通过修改字节码技术
  • 默认策略
    • 如果目标 Bean 实现了至少一个接口,Spring 默认会使用 JDK 动态代理
    • 如果目标 Bean 没有实现任何接口,Spring 就会自动切换到使用 CGLIB 代理

@Componetn @Aspect 给切面类

@PointCut("@annotation(com.itheima.anotation.Log)")
private void pointcut(){
}
@Around("pointcut")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
 
}

常见AOP使用场景

  • 记录操作日志
  • 缓存处理
  • Spring中内置的事务处理

IOC Inversion of Control

ioc是一种思想 依靠DI(Dependency Injection)实现

IoC 的核心目的:解耦(Decoupling)

将创建和管理对象的控制权,从我们的代码手上,转移(反转)给一个外部的“容器”

  1. 实例化 Bean (创建对象)
  • Spring 容器通过扫描配置(比如 @Component 注解)只得到了一个类名字符串(例如 "com.example.OrderService")。
  • 反射。它会执行类似 Class.forName("...").getDeclaredConstructor().newInstance() 的代码来创建对象。这个过程完全不需要我们手动 new OrderService()
  1. 依赖注入 (注入属性)
  • 当 Spring 创建了一个 OrderService 实例后,发现它内部有一个用 @Autowired 标记的 orderRepository 字段,此时这个字段还是 null
  • Spring 需要把 OrderRepository 的实例注入进来。如果是私有字段(private),常规方法是无法访问的。
  • 此时,Spring 再次利用反射,执行类似 field.setAccessible(true) 来获取访问权限,然后调用 field.set(orderServiceInstance, orderRepositoryInstance) 来强行将依赖对象设置进去。无论是字段注入、Setter注入还是构造器注入,底层都离不开反射来查找和调用相应的方法或字段。

Spring当中的依赖注入方式

Spring 主要有三种依赖注入方式:字段注入、Setter 注入和构造器注入。

在早期的业务代码中,大家为了图方便,经常使用字段注入(**@Autowired**加在属性上),但这会导致类与 Spring 容器强耦合,且无法声明为 final

目前我和业界规范一样,首选构造器注入。这也是 Spring 官方目前强烈推荐的方式。它可以保证依赖不可变(配合 final)、确保 Bean 实例化时依赖绝对不为空,并且非常利于编写脱离 Spring 容器的纯粹单元测试。

如果觉得写构造方法太啰嗦,我通常会在项目中配合 Lombok 的 @RequiredArgsConstructor 注解来使用,这样既享受了构造器注入的安全和规范,又保持了代码的极度简洁。

至于 Setter 注入,我一般只在有‘可选依赖’或者需要处理‘循环依赖’的特殊场景下才会使用。

Autowired Resource

核心区别在于它们查找和匹配 Bean 的默认策略不同

  • **@Autowired**:默认按类型 (byType) 查找。 Spring框架
  • **@Resource**:默认按名称 (byName) 查找。 Java提供

1. 构造器注入 (Constructor-Based Injection) 最推荐

@Autowired
public MyService(OtherService otherService, AnotherService anotherService) {
    this.otherService = otherService;
    this.anotherService = anotherService;
}

2. Setter 注入 (Setter-Based Injection)

@Autowired
public void setOtherService(OtherService otherService) {
    this.otherService = otherService;
}

3. 字段注入 (Field-Based Injection) 最不推荐

@Autowired
private OtherService otherService;

Spring中的事务是如何实现的

调用方法时,代理对象先触发“前置通知”开启事务,再执行业务逻辑;若正常结束则“后置通知”提交事务,若抛异常则“异常通知”回滚事务。

@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    try {
        // 开启事务
        // 执行业务代码
        Object proceed = joinPoint.proceed();
        // 提交事务
        return proceed;
    } catch (Exception e) {
        e.printStackTrace();
        // 回滚事务
    }
}

Spring中事务失效的场景有哪些

  • 非Public方法 事务方法不用public修饰会导致事物失效
  • 异常捕获处理
    • 事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉。
    • 解决:把异常再抛出去 thow new RuntimeException()
  • 抛出检查异常
    • Spring 默认只会回滚非检查异常(RuntimeException)
    • 解决:Transaction(rollbackFor=Exception class)
  • 事务方法内部调用(Self-Invocation,最常见的坑)
    • 当一个类中,一个没有事务注解的方法调用了事务注解的方法(如 methodB)时,事务会失效。
    • 拆分到另一个 Bean 中(最推荐)
    • 注入自己的代理对象

Bean的定义与注册

  1. 扫描Bean定义 ComponentScan 扫描指定包路径下的所有类,扫描Bean会
  2. 解析成 BeanDefinition (比如类名、作用域、构造函数参数、属性、初始化方法等) 图纸
  3. 注册BeanDefinitionBeanDefinition注册到一个内部的注册表 Map
  4. 如这个图纸是个单例(Singleton)且非懒加载(lazy-init=false),Spring 就会通过 Java 的反射机制,调用这个类的构造方法 。进入下面的Bean的生命周期。

Spring的Bean的生命周期

  1. 实例化

  2. 依赖注入

    1. Aware接口 BeanNameAware BeanFactoryAware 方法执行啦!
  3. 初始化前 BeanPostProcessor before 方法

    1. InitializingBean 接口的方法执行啦!
  4. 初始化

    1. 初始化后 BeanPostProcessor after 方法
  5. 使用 Bean

    1. DisposableBean 接口的方法执行啦!
  6. 销毁 Bean

  7. 实例化

  8. 依赖注入

    1. 依赖注入之后Aware(意识到的)接口 BeanNameAware BeanFactoryAware ApplicationContextAware 在初始化之前 被初始化时能够感知并获取到其运行环境,也就是 Spring IoC 容器自身的一些核心资源。想知道自己的名字、获取其他Bean等
  9. 初始化

    1. BeanPostProcessor的后置处理前置接口
    2. InitializingBean 接口 这种方式会使代码与 Spring 框架产生耦合。
    3. BeanPostProcessor 的后置处理后置接口 AOP在这里实现
  10. Bean可用

  11. 销毁

    1. DisposableBean接口: 如果 Bean 实现了 DisposableBean 接口,destroy() 方法会被调用。这同样会造成与 Spring 框架的耦合。(销毁的顺序按照创建的顺序) 手动释放资源

Spring中的循环引用问题

  • 循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的 bean 互相持有对方,最终形成闭环。比如 A 依赖于 B,B 依赖于 A
  • 循环依赖在 spring 中是允许存在,spring 框架依据三级缓存已经解决了大部分的循环依赖① 一级缓存singletonObjects:单例池,存放已经完全初始化好的 Bean 实例。② 二级缓存earlySingletonObjects:缓存早期的 bean 对象(生命周期还没走完) 代理对象时候用 存放代理对象③ 三级缓存singletonFactories:缓q存的是 ObjectFactory,表示对象工厂,用来创建某个对象的

构造器注入无法解决循环依赖,使用 @Lazy 注解

SpringBoot项目启动流程

  1. SpringApplication.run( ); 主要做了两件事
    1. 创建一个SpringApplication实例 这个实例会做很多准备工作,比如推断应用类型(是否是 Web 应用)、加载所有可用的初始化器(Initializers)和监听器(Listeners)
    2. 调用run方法 真正开始启动
  2. 蓝图 - @SpringBootApplication 注解
    1. **@Configuration**
    2. **@EnableAutoConfiguration**最核心的“魔法”): 开启自动化配置。
    3. **@ComponentScan**: 它会扫描当前启动类所在的包及其所有子包。所有被 @Component@Service@Repository@RestController 等注解标记的类,都会被自动发现并注册为 Bean。
  3. 执行 - 创建并刷新 ApplicationContext
    1. 创建 ApplicationContext 这就是 Spring 的“IoC 容器
    2. 准备上下文 (Prepare Context):application.propertiesapplication.yml 文件中定义的配置加载到 Spring 的环境(Environment)中。 执行所有的初始化器(Initializers)。
    3. 刷新上下文 (Refresh Context):组件扫描 - 自动配置 - 实例化 - 依赖注入 - 初始化
  4. 启动内嵌Tomcat服务器Bean
  5. 执行run方法。

HTTP请求到达Spring经过了哪些组件

  1. 公司大门与安检
    1. Tomacat 将网络字节流解析成 Java 能理解对象
    2. Filter过滤器链 核心鉴权
  2. 前台接待(SpringMVC调度)
    1. DispatcherServelet 前端控制器
    2. HanderMapping
  3. 部门走廊与助理
    1. Interceptor
    2. HttpMessageConverter 拆包 如拆成@RequestBody
  4. 员工干活(业务逻辑层)
    1. Controller -> Service -> DAO
  5. 原路打包返回
    1. HttpMessageConverter 装包
    2. Interceptor
    3. 原路发走

SLF4J规范

  • 规范性: 使用 @Slf4j 和占位符 {},杜绝 System.out+拼接。
  • 有效性: 遇到 catch 异常时,确保 log.error 包含了堆栈信息(把 e 传进去)。
  • 安全性: 时刻警惕,不要把用户的密码或敏感信息打到日志里。
级别含义适用场景生产环境开关
ERROR错误影响业务正常运行的故障。需要人工介入处理。
例如:数据库连接断开、空指针异常、核心业务逻辑失败。
开启
WARN警告不影响系统继续运行,但存在潜在风险,或业务出现了预期内的异常路径。
例如:接口参数校验失败、重试操作、缓存丢失触发回源。
开启
INFO信息关键的业务节点或系统生命周期事件。用于体现由于系统运行状况。
例如:系统启动/关闭、定时任务开始/结束、关键状态变更(订单支付成功)。
开启 (默认级别)
DEBUG调试开发人员关注的细节,用于排查问题
例如:SQL 语句、完整的入参出参、算法中间步骤。
关闭 (仅开发/测试开启)
TRACE追踪极度详细的运行轨迹。。关闭