Spring
Spring:IOC、AOP、事务、Bean 生命周期、循环依赖、常见注解。
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(比如Tomcat和DispatcherServlet)。@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.properties或application.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)
将创建和管理对象的控制权,从我们的代码手上,转移(反转)给一个外部的“容器”
- 实例化 Bean (创建对象)
- Spring 容器通过扫描配置(比如
@Component注解)只得到了一个类名字符串(例如"com.example.OrderService")。 - 反射。它会执行类似
Class.forName("...").getDeclaredConstructor().newInstance()的代码来创建对象。这个过程完全不需要我们手动new OrderService()。
- 依赖注入 (注入属性)
- 当 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的定义与注册
- 扫描Bean定义
ComponentScan扫描指定包路径下的所有类,扫描Bean会 - 解析成
BeanDefinition(比如类名、作用域、构造函数参数、属性、初始化方法等) 图纸 - 注册
BeanDefinition将BeanDefinition注册到一个内部的注册表Map - 如这个图纸是个单例(Singleton)且非懒加载(lazy-init=false),Spring 就会通过 Java 的反射机制,调用这个类的构造方法 。进入下面的Bean的生命周期。
Spring的Bean的生命周期
-
实例化
-
依赖注入
- Aware接口 BeanNameAware BeanFactoryAware 方法执行啦!
-
初始化前 BeanPostProcessor before 方法
- InitializingBean 接口的方法执行啦!
-
初始化
- 初始化后 BeanPostProcessor after 方法
-
使用 Bean
- DisposableBean 接口的方法执行啦!
-
销毁 Bean
-
实例化
-
依赖注入
- 依赖注入之后
Aware(意识到的)接口BeanNameAwareBeanFactoryAwareApplicationContextAware在初始化之前 被初始化时能够感知并获取到其运行环境,也就是 Spring IoC 容器自身的一些核心资源。想知道自己的名字、获取其他Bean等
- 依赖注入之后
-
初始化
BeanPostProcessor的后置处理前置接口InitializingBean接口 这种方式会使代码与 Spring 框架产生耦合。BeanPostProcessor的后置处理后置接口 AOP在这里实现
-
Bean可用
-
销毁
DisposableBean接口: 如果 Bean 实现了DisposableBean接口,destroy()方法会被调用。这同样会造成与 Spring 框架的耦合。(销毁的顺序按照创建的顺序) 手动释放资源
Spring中的循环引用问题
- 循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的 bean 互相持有对方,最终形成闭环。比如 A 依赖于 B,B 依赖于 A
- 循环依赖在 spring 中是允许存在,spring 框架依据三级缓存已经解决了大部分的循环依赖① 一级缓存
singletonObjects:单例池,存放已经完全初始化好的 Bean 实例。② 二级缓存earlySingletonObjects:缓存早期的 bean 对象(生命周期还没走完) 代理对象时候用 存放代理对象③ 三级缓存singletonFactories:缓q存的是 ObjectFactory,表示对象工厂,用来创建某个对象的
构造器注入无法解决循环依赖,使用 @Lazy 注解
SpringBoot项目启动流程
- SpringApplication.run( ); 主要做了两件事
- 创建一个
SpringApplication实例 这个实例会做很多准备工作,比如推断应用类型(是否是 Web 应用)、加载所有可用的初始化器(Initializers)和监听器(Listeners) - 调用run方法 真正开始启动
- 创建一个
- 蓝图 -
@SpringBootApplication注解**@Configuration**:**@EnableAutoConfiguration**(最核心的“魔法”): 开启自动化配置。**@ComponentScan**: 它会扫描当前启动类所在的包及其所有子包。所有被@Component、@Service、@Repository、@RestController等注解标记的类,都会被自动发现并注册为 Bean。
- 执行 - 创建并刷新
ApplicationContext- 创建
ApplicationContext这就是 Spring 的“IoC 容器 - 准备上下文 (Prepare Context): 将
application.properties或application.yml文件中定义的配置加载到 Spring 的环境(Environment)中。 执行所有的初始化器(Initializers)。 - 刷新上下文 (Refresh Context):组件扫描 - 自动配置 - 实例化 - 依赖注入 - 初始化
- 创建
- 启动内嵌Tomcat服务器Bean
- 执行run方法。
HTTP请求到达Spring经过了哪些组件
- 公司大门与安检
- Tomacat 将网络字节流解析成 Java 能理解对象
- Filter过滤器链 核心鉴权
- 前台接待(SpringMVC调度)
- DispatcherServelet 前端控制器
- HanderMapping
- 部门走廊与助理
- Interceptor
- HttpMessageConverter 拆包 如拆成
@RequestBody
- 员工干活(业务逻辑层)
- Controller -> Service -> DAO
- 原路打包返回
- HttpMessageConverter 装包
- Interceptor
- 原路发走
SLF4J规范
- 规范性: 使用
@Slf4j和占位符{},杜绝System.out和+拼接。 - 有效性: 遇到
catch异常时,确保log.error包含了堆栈信息(把e传进去)。 - 安全性: 时刻警惕,不要把用户的密码或敏感信息打到日志里。
| 级别 | 含义 | 适用场景 | 生产环境开关 |
|---|---|---|---|
| ERROR | 错误 | 影响业务正常运行的故障。需要人工介入处理。 例如:数据库连接断开、空指针异常、核心业务逻辑失败。 | 开启 |
| WARN | 警告 | 不影响系统继续运行,但存在潜在风险,或业务出现了预期内的异常路径。 例如:接口参数校验失败、重试操作、缓存丢失触发回源。 | 开启 |
| INFO | 信息 | 关键的业务节点或系统生命周期事件。用于体现由于系统运行状况。 例如:系统启动/关闭、定时任务开始/结束、关键状态变更(订单支付成功)。 | 开启 (默认级别) |
| DEBUG | 调试 | 开发人员关注的细节,用于排查问题。 例如:SQL 语句、完整的入参出参、算法中间步骤。 | 关闭 (仅开发/测试开启) |
| TRACE | 追踪 | 极度详细的运行轨迹。。 | 关闭 |