Spring Boot 框架相关注解
@Aspect注解的使用?如何定义切面和通知?结合
Spring 框架聊聊
@Aspect注解是 Spring AOP
中定义切面类的核心注解,需要配合@Component注册为
Bean。一个标准的切面类需要同时使用@Aspect和@Component两个注解
Spring AOP 基于代理机制,所以只有通过 Spring 容器调用的方法才能被拦截,这也是为什么我们要确保被拦截的类要注册为 Spring Bean 的原因。
切面定义通过在类上添加@Aspect实现,切点定义使用@Pointcut注解指定拦截规则,比如@Pointcut("execution(* com.example.service.*.*(..))")表示拦截
service 包下所有方法。
谈到 @Aspect 时,你需要明确 Spring AOP 和 AspectJ 的关系:Spring AOP 实际上借鉴了 AspectJ 的注解体系,包括 @Aspect、@Pointcut、@Before 等注解都来自 AspectJ,但底层实现机制不同。Spring AOP 基于动态代理实现,只能拦截 Spring 管理的 Bean 的方法调用,而 AspectJ 是通过字节码织入实现的,功能更强大但也更复杂。在日常开发中,Spring AOP 已经能满足大部分场景需求。
通知类型包括五种:@Before在目标方法执行前运行,常用于参数校验;@After在方法执行后运行,用于资源清理;@AfterReturning在方法正常返回后执行,可获取返回值进行后置处理;@AfterThrowing在方法抛异常时执行,用于异常日志记录;@Around环绕整个方法执行,最为灵活,常用于性能监控和事务控制。
Spring 会在运行时为被拦截的 Bean 创建代理对象,当调用目标方法时实际执行的是代理逻辑,从而实现横切关注点的分离。记住切面只对 Spring 容器管理的 Bean 生效,直接 new 的对象无法被拦截。
对于多个切面可以进行执行顺序控制,当有多个切面作用于同一个方法时,Spring通过@Order注解来控制执行顺序。数值越小,优先级越高。但是对于这些注解并不是优先级高就一定先执行,例如对于@Before通知,Order值小的先执行;对于@After通知,Order值小的后执行
@ComponentScan注解的配置?包扫描的规则和过滤器?结合
Spring 框架聊聊?
@ComponentScan 是 Spring 框架中用于自动扫描和注册 Bean
的核心注解,它告诉 Spring 容器在哪些包路径下寻找标注了
@Component、@Service、@Repository、@Controller
等注解的类。
Spring 容器的自动配置,依赖注入什么的都需要依赖于 Bean
的发现机制,当容器启动时需要知道哪些类应该被管理为
Bean,而@ComponentScan就是告诉容器去哪里找
基本配置很简单,通过 basePackages 或 value
属性指定扫描路径,而且还能配置excludeFilters
用于排除不需要的类等灵活处理的情况。如果不指定路径,默认扫描当前配置类所在包及其子包。你也可以用
basePackageClasses 指定基准类,Spring
会扫描这些类所在的包,这种方式在微服务和重构包名时的情况不容易出错。
包扫描规则遵循递归原则,在Spring容器的 refresh()
方法执行过程中,会有一个专门的阶段处理组件扫描,会递归遍历指定包路径下的所有
class
文件,通过反射机制检查每个类上的注解,符合条件的类会被实例化并注册到 IoC
容器中。
@Configuration和@Component的区别?配置类的特殊性?结合
Spring 框架聊聊?
@Configuration和@Component都是 Spring 中
标识受容器管理的 Bean 的注解
@Component是通用的 Bean
标识注解,而@Configuration是专门为配置类设计的,承载了
Spring 容器初始化、Bean 定义注册、依赖注入的核心逻辑。
@Configuration是@Component的子类注解,它定义
Bean 的创建规则、管理 Bean 之间的依赖关系,是 Spring Java Config
的核心。
@Component标记的类会被 Spring 容器管理,但方法调用遵循普通Java语义。@Configuration标记的类会被CGLIB代理增强,确保@Bean方法之间的调用返回同一个实例,维护Bean的单例特性。@Configuration作为配置类,支持@Import导入其他配置类,支持@PropertySource加载外部配置文件,支持@Enable*系列注解开启 Spring 特定功能;@Component不具备上述内容@Component仅能通过@Autowired/@Resource实现依赖注入,无法直接通过方法调用管理 Bean 依赖。@Configuration支持@Bean方法间的依赖传递,通过方法调用实现 Bean 依赖,无需手动注入。
简单说,@Component用于业务组件,@Configuration专门用于配置Bean的定义和装配,最重要的是区分这个
@Configuration注解的核心在于它会触发Spring的ConfigurationClassPostProcessor后置处理器。Spring容器在解析配置类时,会检测到@Configuration注解,然后使用CGLIB创建一个子类代理,这个代理类会拦截所有@Bean方法的调用。CGLIB代理不是简单的方法拦截,而是保证Bean实例的唯一性。代理的核心作用是确保当一个@Bean方法调用另一个@Bean方法时,返回的是Spring容器中已经存在的Bean实例,而不是重新创建。在
@Component类中定义了多个@Bean方法,并且方法间有调用关系。结果发现每次调用都创建了新的实例,导致数据库连接数异常飙升。后来改成@Configuration注解,问题立即解决。
在实际项目中,一般在三种场景下会选择@Configuration。第一是基础设施配置,比如数据库连接池、Redis客户端这些需要严格单例的组件。第二是第三方框架集成,像Swagger、定时任务框架的配置。第三是复杂的Bean依赖关系配置,特别是Bean之间有相互调用的场景。
@Profile注解的作用是什么?你都如何使用它?多环境配置下如何进行实践来进行环境隔离?
@Profile 注解是 Spring 框架中用于条件化加载
Bean
的一个注解,通过指定激活的环境配置来控制特定组件的实例化。
可以在类级别或方法级别使用它,比如 @Profile("dev") 或
@Profile({"prod", "staging"})。标注在 @Bean 方法上就是精准控制单个
Bean 的加载,标注在配置类上就是控制整个配置类的加载
在多环境配置的实际应用中,我通常会为不同数据源创建专门的配置类,开发环境使用
@Profile("dev") 配置开发用的数据库,生产环境用
@Profile("prod") 配置 MySQL 集群。
激活 Profile 的方式包括在 application.properties 中设置
spring.profiles.active=dev,或通过 JVM 参数
-Dspring.profiles.active=prod 启动应用,这样就能用
@Profile 隔离差异化 Bean
一般情况下,环境隔离需要通过多文件配置策略实现,将
application-dev.yml作为开发环境配置、application-prod.yml作为生产环境配置,等环境特定配置分离,主配置文件只保留公共配置。
对于复杂的微服务架构,可以组合使用多个 Profile,比如
@Profile("prod & !docker")
这样的表达式来处理更精细的环境区分。
Spring
Boot 中都有哪些注入 Bean
的方式?@Autowired、@Resource、@Inject注解的区别?为什么
Spring 官方不推荐使用 @Autowired?
虽然我们常说 注解注入,但从底层机制上,Spring 主要支持以下三种方式来进行 DI:
构造器注入
这是官方推荐的,它通过类的构造函数来明确依赖关系。通过
final修饰,能够保证依赖不为空1
2
3
4
5
6private final UserService userService;
// 4.3+ 单构造器可省略@Autowired
public UserController(UserService userService) {
this.userService = userService;
}通过
setXxx方法注入依赖。它比较灵活,允许依赖在之后被修改或重新注入。但是无法保证依赖在对象使用前已完全初始化,可能导致NPE
1
2
3
4
5
6private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}字段注入
直接在成员变量上注解
1
2
private UserService userService;
而
@Autowired、@Resource、@Inject
这三个注解都能实现自动装配 Bean,但是,
@Autowired、@Resource、@Inject本身是
依赖注入的注解,不是
固定的某一种注入方式,只是日常开发中大家最常用在字段上。
它们之间的区别就是
| 注解 | 所属规范 | 匹配优先级 | 支持的注入形式 | 核心区别 |
|---|---|---|---|---|
@Autowired |
Spring 专属 | 优先类型(byType)→ 再按名称(byName) | 字段 /setter/ 构造器 | 支持required=false、@Qualifier指定 Bean
名 |
@Resource |
JSR-250(Java 标准) | 优先名称(byName)→ 再按类型(byType) | 字段 /setter(不支持构造器) | 可通过name属性指定 Bean 名 |
@Inject |
JSR-330(Java 标准) | 优先类型(byType) | 字段 /setter/ 构造器 | 需导入javax.inject依赖,支持@Named指定
Bean 名 |
详细的查找逻辑是这样的
@Autowired:首先根据 类型 (Type) 查找。
如果找到多个同类型的 Bean,则尝试按 名称 (Name) 匹配。
如果名称也匹配不上,且没有
@Qualifier,则报错。
@Resource:首先根据 名称 (Name) 查找(默认取字段名)。
如果按名称没找到,则回退到按 类型 (Type) 查找。
它的逻辑更符合 Java 原生语义,且与框架解耦。
为什么 Spring 官方不推荐使用 @Autowired 字段注入呢?明明只有它是 Spring 生态中的内容?难道考虑的是兼容性吗?只能说是不止
违反了单一职责原则
使用字段注入太方便了,以至于开发者很容易在一个类里塞入 10 个甚至 20 个依赖,而不会觉得有什么问题。那么就可能出现了类违反单一职责,循环依赖或无用依赖的情况。如果是构造器注入,看到一个拥有 20 个参数的构造函数,开发者会立刻意识到这个类承载了太多职责,需要重构。
无法使用
final修饰符字段注入是无法使用
final修饰符的,因为字段注入是在对象实例化之后通过反射设置的。这破坏了 不可变对象 (Immutable Objects) 的设计模式,可能在高并发等情况下出现问题与 Spring 容器过度耦合
这是最致命的一点。字段注入必须依赖 Spring 的反射机制。如果你想在不启动 Spring 容器的情况下(如纯 JUnit 测试)实例化这个类,你必须手动编写反射代码来注入依赖,否则该字段永远是
null。隐藏了循环依赖风险
如果是构造器注入,存在循环依赖,Spring 在项目启动时就会抛出
BeanCurrentlyInCreationException,提醒你设计有问题。而字段注入因为反射不会在启动时候检查,而是等问题出现才报错。
既然官方不推荐,那为什么我看到的 demo 全是 @Autowired
字段注入?
- 没办法,字段注入太简单了,以至于在实际开发中,它仍然主流
- 但是,我们可以配合 Lombok 的
@RequiredArgsConstructor注解,它能自动生成带final字段的构造函数,这样,使用构造器注入就很方便了
@Qualifier注解的作用?如何解决多个候选
Bean 的问题?结合你的实践聊聊?
首先,我们都知道,@Autowired默认按类型匹配进行注入,@Resource默认按名称匹配,而在实际开发中,如果你的项目可能迁移到其他容器或需要标准化,@Resource更具可移植性,所以说我们用的更多的都是@Resource
当 Spring 容器中存在多个同一类型的 Bean
实例时,依赖注入会触发NoUniqueBeanDefinitionException异常。但在实际开发中,同一类型存在多个
Bean 实例
的场景很常见(比如多数据源、多缓存实现、多支付渠道),例如,在项目中,我需要同时集成
PostgreSQL 和 MongoDB
两种数据源,且都实现了DataSource接口,此时容器中会存在两个DataSource类型的
Bean,直接注入就会冲突
除了@Resource加name属性,我们使用@Qualifier
+ @Autowired也能解决这个问题。
@Qualifier是 Spring 提供的 Bean
名称限定注解,在按类型匹配的基础上,进一步按 Bean
名称筛选,解决多候选 Bean 的歧义问题。
使用方式很直接,在@Autowired旁边添加@Qualifier("beanName")来指定具体的Bean名称。比如你有两个DataSource类型的Bean,一个叫primaryDataSource,另一个叫secondaryDataSource,注入时就用@Qualifier("primaryDataSource")明确告诉Spring要哪个。
如何理解@SpringBootApplication注解,为什么标注了这个注解的类能够作为主启动类?
@SpringBootApplication 是 Spring Boot
核心的组合注解,它整合了 3 个注解。
@ComponentScan做组件扫描,扫描当前包及子包下的@Controller/@Service/@Component等注解的类@EnableAutoConfiguration开启自动配置,自动加载 Spring Boot 内置的默认配置@SpringBootConfiguration也是标记该类为配置类,本质是@Configuration
标注了该注解的类之所以能作为主启动类,是因为它完成了 开启自动配置、扫描组件、标记配置类 三大核心工作,是 Spring Boot 约定大于配置 思想的集中体现。
1 |
|
启动类的核心是 main 方法 +
SpringApplication.run(),但真正让它成为 启动入口 的是
@SpringBootApplication 的三大能力:
@ComponentScan会从启动类所在包开始,递归扫描所有子包下的组件如果你的 Controller 放在启动类的上层包,会扫描不到,我被坑过,需要指定路径
@EnableAutoConfiguration会通过SpringFactoriesLoader加载META-INF/spring.factories文件中的自动配置类,自动配置类通过@Conditional注解实现按需生效,只需配置配置文件yaml中的相关信息,无需手动创建 Bean,就是自动配置的体现。@SpringBootConfiguration让启动类具备配置类的能力,可直接在启动类中通过@Bean定义全局组件
执行SpringApplication.run(),进行的是 Spring Boot
项目启动那一套了
- 加载
@SpringBootApplication注解的配置; - 扫描组件并注册到 Spring 容器;
- 启动自动配置流程;
- 启动内嵌 Tomcat(如果引入 web 依赖);
- 初始化 Spring 上下文,项目启动完成。
Spring
中的 @Cacheable 和 @CacheEvict
注解的作用是什么?在缓存这一块你都用了哪些注解?
这两个注解都属于 Spring Cache 抽象的注解,通过 AOP 代理在方法调用前后自动操作缓存。
@Cacheable—— 读缓存,将运行结果缓存,缓存命中则跳过方法执行逻辑:方法调用前,根据
key去缓存中查找。如果命中,直接返回缓存值,方法体不执行。未命中,执行方法体,将返回值写入缓存,再返回。@CacheEvict—— 写操作后失效缓存当数据发生变更,旧缓存必须被清掉。
beforeInvocation = false是默认,方法成功执行后才删除缓存;设为true则在方法执行前删除。
关于其他常用的缓存相关的注解
@CachePut:总是执行方法体,并把返回值写入缓存,用于更新场景@Caching:用于在一个方法上组合多个缓存注解,当一次操作需要同时触发多种缓存行为时使用。@CacheConfig:类级别的注解,用于抽取该类中所有缓存操作的公共配置,避免在每个方法上重复写cacheNames。@EnableCaching:总开关,不加这个注解,上面所有缓存注解都不会生效
Spring 中的 @PostConstruct 和 @PreDestroy 注解的作用是什么?
@PostConstruct :在 Bean
依赖注入完成后执行初始化逻辑,一般是 Bean
自身的资源初始化用 @PostConstruct
@PreDestroy:在容器销毁 Bean
之前执行清理逻辑
@PostConstruct和@PreDestroy标注的方法必须满足:返回
void、不能有参数
Spring 创建 Bean 并完成所有属性注入之后,按以下顺序触发初始化回调:
@PostConstruct标注的方法InitializingBean接口的afterPropertiesSet()方法- 自定义的
init-method(@Bean(initMethod = "..."))
容器关闭时,按以下顺序触发销毁回调
@PreDestroy标注的方法DisposableBean接口的destroy()方法- 自定义的
destroy-method
为什么推荐用 @PostConstruct
而不是构造方法做初始化? 构造方法执行时依赖尚未注入,无法访问
@Autowired 的 Bean;@PostConstruct
在注入完成后执行,可以安全使用依赖。
@Transactional 注解的实现原理?事务代理的创建过程?结合 Spring 框架聊聊
@Transactional的实现原理核心就是Spring
AOP代理机制加上事务管理器的协作。Spring容器在初始化bean的时候,会扫描带有@Transactional注解的类,然后为这些类创建代理对象,把事务控制从业务代码中完全剥离出来,通过TransactionInterceptor拦截器和PlatformTransactionManager事务管理器的配合,实现了事务的自动开启、提交和回滚。
当你调用带有@Transactional注解的方法时,实际调用的是代理对象,代理会在目标方法执行前开启事务,执行后根据方法执行结果决定提交或回滚事务。
事务代理的创建过程如下,基本上,不少 AOP 的增强都是这种形式
- Spring 在 Bean 完成依赖注入后,会依次调用所有
BeanPostProcessor。其中AbstractAutoProxyCreator的postProcessAfterInitialization是关键入口。 - 当 Spring
发现某个bean类或其方法上有
@Transactional注解时,会先通过TransactionAttributeSource解析事务属性,只有真正需要事务支持的方法才会触发代理创建 - 然后根据是否实现接口或者
proxyTargetClass=true,选择代理方式 - 然后调用
createProxy(...),把事务拦截器织入代理,返回代理对象替换原始 Bean。
对于回滚,默认行为只有 RuntimeException 和
Error 才回滚,受检异常不回滚。
注意自调用陷阱,同一个类中,方法 A 调用方法 B,即使 B 上有
@Transactional,事务也不生效,基本上使用 AOP
代理增强的注解都是如此
@Transactional的核心参数
propagation传播行为:控制事务如何传递
REQUIRED:默认。有事务就加入,没有就新建一个。当前方法不会创建新的事务,而是直接使用调用它的那个方法已经开启的事务。它们会变成同一个事务,共享同一个数据库连接,一起提交或一起回滚。REQUIRES_NEW:总是新建一个事务SUPPORTS:有事务就用,没有就非事务执行。NOT_SUPPORTED:以非事务方式执行,如果当前有事务则挂起。MANDATORY:必须在事务中运行,否则抛异常。NEVER:必须在非事务中运行,否则抛异常。NESTED:如果当前有事务,则嵌套执行。否则和REQUIRED一样。isolation隔离级别:控制事务之间的可见性,主要解决脏读、不可重复读、幻读问题。
DEFAULT:使用底层数据库的默认级别READ_UNCOMMITTED:最低级别,允许脏读。READ_COMMITTED:只能读已提交数据,避免脏读。REPEATABLE_READ:保证同一事务内多次读取结果一致,避免不可重复读。SERIALIZABLE:最高级别,串行化执行,避免所有并发问题。rollbackFor:指定哪些异常触发回滚。例如@Transactional(rollbackFor = Exception.class)让所有异常都回滚。readOnly:默认为false。设置为true时,告诉数据库这是只读操作,可获得性能优化,但不能执行增删改。
@TransactionalEventListener:事务事件监听器的参数如下
phase:决定监听器在事务的哪个阶段被触发。
BEFORE_COMMIT:在事务提交前执行。此时修改数据库会包含在同一个事务中。AFTER_COMMIT:默认值。在事务成功提交后执行。常用于发送通知、缓存更新等不应影响主事务的后续操作。AFTER_ROLLBACK:在事务回滚后执行。可用于记录失败日志或补偿。AFTER_COMPLETION:在事务完成后执行(无论提交还是回滚)。fallbackExecution:默认false。表示如果没有活跃事务,监听器就不执行。如果设为true,即使没有事务,监听器也会像普通@EventListener一样被调用。




