八股-Spring
Spring 相关面试题
Spring 框架中的单例 bean 是线程安全的吗?
非线程安全
在Spring框架中,存在一个名为@Scope
的注解,其默认值即为singleton
,代表单例模式。
通常情况下,Spring框架中的Bean大多注入无状态对象,因此不存在线程安全问题。然而,若在Bean中定义了可修改的成员变量,则需要考虑线程安全问题。此时,可以通过使用多例模式或加锁机制来确保线程安全
@Controller
@RequestMapping("/user")
public class UserController {
private int count; // count 有线程安全问题
@Autowired
private UserService userService; // userService 没有线程安全问题,因为无状态
@GetMapping("/getById/{id}")
public User getById(@PathVariable("id") Integer id){
count++;
System.out.println(count);
return userService.getById(id);
}
}
Spring Bean 并无持可变状态(例如 Service 类和 DAO 类),因此在某种程度上可以说 Spring 的单例 Bean 是线程安全的
什么是 AOP
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它通过将横切关注点(cross-cutting concerns)从业务逻辑中分离出来,使得代码更加模块化。AOP 是对 OOP(面向对象编程)的补充,它允许我们定义横跨多个类的公共行为,而不必在每个类中都复制相同的代码
日志记录:记录方法调用、参数和执行时间,无需在每个方法中添加日志代码
@Aspect
@Component
public class LoggingAspect {
@Around("execution(* com.company.service.*.*(..))")
public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("方法 " + joinPoint.getSignature() + " 执行耗时: " + (endTime - startTime) + "ms");
return result;
}
}
事务管理:使用 @Transactional
注解声明式地管理事务,而不是手动编写事务代码
@Service
public class UserService {
@Transactional
public void createUser(User user) {
// 业务逻辑
}
}
使用 AOP 完成了在方法前开启事务,方法结束后提交,方法失败回滚
安全控制:实现权限检查、身份验证等安全功能
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(requiresPermission)")
public void checkPermission(RequiresPermission requiresPermission) {
String[] permissions = requiresPermission.value();
// 验证当前用户是否有权限
}
}
异常处理:统一处理异常,简化业务代码
缓存管理:使用 @Cacheable
, @CachePut
, @CacheEvict
等注解管理缓存操作
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Product getProductById(Long id) {
// 从数据库查询产品
}
}
接口限流:控制接口访问频率,防止恶意调用
参数校验:在方法执行前验证参数的合法性
Spring 事务失效的场景
⭐️异常捕获处理
失效场景:捕获异常但未重新抛出
@Service
public class ProductService {
@Transactional
public void updateStock(Long id, int count) {
try {
// 更新库存
if (count < 0) {
throw new RuntimeException("库存不能为负数");
}
} catch (Exception e) {
log.error("更新库存失败", e);
// 异常被吞没,事务不会回滚
}
}
}
原因:Spring 只有在抛出异常时才会触发事务回滚
解决方案:
- 不要捕获异常,让它自然抛出
- 捕获后重新抛出异常
- 使用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
手动标记回滚
⭐️抛出检查异常
失效场景:抛出的异常类型不会触发回滚
@Service
public class AccountService {
@Transactional
public void transfer(String from, String to, BigDecimal amount) throws Exception {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new Exception("转账金额必须大于0"); // 检查型异常不会触发回滚
}
// 转账逻辑
}
}
原因:Spring 默认只回滚运行时异常和 Error
解决方案:
- 抛出 RuntimeException
- 使用
@Transactional(rollbackFor = Exception.class)
指定回滚的异常类型
⭐️非 public 方法
失效场景:事务方法不是 public
的
@Service
public class UserService {
@Transactional
private void updateUser(User user) { // 事务无效
// 更新用户逻辑
}
}
原因:Spring AOP 默认只代理 public
方法
解决方案:确保带有 @Transactional
的方法是 public
的
⭐️类内部调用问题
失效场景:同一个类中的方法相互调用
@Service
public class OrderService {
public void createOrder(Order order) {
// 一些逻辑
this.saveOrder(order); // 直接调用,事务不生效
}
@Transactional
public void saveOrder(Order order) {
// 保存订单
}
}
原因:Spring AOP 基于代理实现,通过 this
调用方法不会经过代理对象,因此不会启动事务
解决方案:
- 将方法拆分到不同的类中
- 通过依赖注入自己,使用注入的实例调用方法
- 使用
AopContext.currentProxy()
获取当前代理对象来调用
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入自己
public void createOrder(Order order) {
// 使用注入的实例调用
self.saveOrder(order);
}
@Transactional
public void saveOrder(Order order) {
// 保存订单
}
}
事务传播行为设置不当
@Service
public class PaymentService {
@Transactional
public void process() {
// 业务逻辑
recordPayment(); // 这里的事务会被覆盖
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordPayment() {
// 即使外层事务回滚,这个方法仍会提交
}
}
原因:REQUIRES_NEW
会挂起当前事务并创建新事务,导致两个事务相互独立
解决方案:根据业务需求正确选择传播行为,常用的如 REQUIRED
(默认)
数据库不支持事务
失效场景:数据库引擎不支持事务
@Repository
public class LogDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void saveLogs(List<LogEntry> logs) {
// 如果表使用的是MyISAM引擎,事务不会生效
for (LogEntry log : logs) {
jdbcTemplate.update("INSERT INTO system_log VALUES (?, ?, ?)",
log.getId(), log.getMessage(), log.getTime());
}
}
}
原因:如 MyISAM 引擎不支持事务
解决方案:使用支持事务的存储引擎,如 InnoDB
事务管理器配置错误
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource); // 使用JDBC事务管理器
}
}
@Service
public class UserService {
@PersistenceContext
private EntityManager entityManager; // JPA实体管理器
@Transactional // 事务管理器不匹配,事务失效
public void updateUser(User user) {
entityManager.merge(user);
}
}
原因:事务管理器与数据访问技术不匹配
解决方案:确保使用正确的事务管理器,如 JPA 使用 JpaTransactionManager
方法使用 final 或 static 修饰
@Service
public class OrderService {
@Transactional
public final void createOrder(Order order) { // final方法无法被代理
// 创建订单逻辑
}
@Transactional
public static void cancelOrder(Long orderId) { // static方法无法被代理
// 取消订单逻辑
}
}
原因:Spring AOP 无法对 final 或 static 方法进行代理
解决方案:不要对事务方法使用 final 或 static 修饰符
接口上的事务注解未被代理模式支持
失效场景:事务注解放在接口方法上,但使用了 CGLIB 代理模式
public interface UserService {
@Transactional
void createUser(User user); // 接口上的注解在CGLIB模式下会被忽略
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void createUser(User user) {
// 实现逻辑,但没有重复@Transactional注解
}
}
原因:CGLIB 代理基于继承实现,不会检查接口上的注解
解决方案:
- 在实现类的方法上添加
@Transactional
- 使用 JDK 动态代理(基于接口)
(🚩)Bean 的生命周期
BeanDefinition
Spring容器在实例化过程中,会将XML配置中的<bean>
信息封装为Bean Definition对象。Spring依据Bean Definition来构建Bean对象,其中包含众多属性,用以描述Bean
<bean id="userDao"class="com.itheima.dao.impl.UserDaoImpl"lazy-init="true"/>
<bean id="userService"class="com.itheima.service.UserServiceImpl"scope="singleton">
<property name="userDao"ref="userDao"></property>
</bean>
beanClassName: bean的类名
initMethodName: 初始化方法名称
propertyValues: bean的属性值
scope: 作用域
lazyInit: 延迟初始化

Spring Bean 的生命周期
Spring Bean 的生命周期是指 Bean 从创建到销毁的完整过程,这个过程非常复杂,包含了多个阶段和扩展点。理解 Bean 的生命周期对于开发高质量的 Spring 应用至关重要
一、Spring Bean 的完整生命周期
Spring Bean 的生命周期可以分为以下几个主要阶段:
1. 实例化(Instantiation)
- Spring 容器根据 Bean 定义创建 Bean 的实例
- 通常通过反射调用无参构造方法完成
2. 属性赋值(Populate Properties)
- 容器为 Bean 的属性赋值
- 应用依赖注入,设置各种属性值
3. Aware 接口回调阶段
如果 Bean 实现了 Aware 接口族的接口,Spring 会调用这些接口方法
常见的 Aware 接口按执行顺序:
- BeanNameAware
- BeanFactoryAware
- ApplicationContextAware
4. 初始化前(BeanPostProcessor 的前置处理)
- 容器调用 BeanPostProcessor 的
postProcessBeforeInitialization()
方法
5. 初始化(Initialization)
- 如果 Bean 实现了 InitializingBean 接口,调用
afterPropertiesSet()
方法 - 如果 Bean 配置了
init-method
,调用指定的初始化方法 - 如果 Bean 使用了
@PostConstruct
注解,调用标注的方法
6. 初始化后(BeanPostProcessor 的后置处理)
- 容器调用 BeanPostProcessor 的
postProcessAfterInitialization()
方法 - AOP 就是在这个步骤完成的
7. 使用 Bean
- 此时 Bean 已经可以使用了
8. 销毁前处理
- 如果 Bean 实现了 DestructionAwareBeanPostProcessor,则执行相关回调
9. 销毁(Destruction)
- 如果 Bean 实现了 DisposableBean 接口,调用
destroy()
方法 - 如果 Bean 配置了
destroy-method
,调用指定的销毁方法 - 如果 Bean 使用了
@PreDestroy
注解,调用标注的方法
二、生命周期扩展点
1. Aware 接口族
通过实现这些接口,Bean 可以感知到自身的一些属性:
public class MyBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
@Override
public void setBeanName(String name) {
System.out.println("Bean 名称: " + name);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("BeanFactory: " + beanFactory);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("ApplicationContext: " + applicationContext);
}
}
2. 初始化和销毁回调
有三种方式配置初始化和销毁回调:
方式一:InitializingBean 和 DisposableBean 接口
public class MyBean implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean 初始化");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean 销毁");
}
}
方式二:@PostConstruct 和 @PreDestroy 注解
public class MyBean {
@PostConstruct
public void init() {
System.out.println("@PostConstruct 注解初始化");
}
@PreDestroy
public void cleanup() {
System.out.println("@PreDestroy 注解销毁");
}
}
方式三:XML 配置或 @Bean 注解中指定
@Configuration
public class AppConfig {
@Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
public MyBean myBean() {
return new MyBean();
}
}
3. BeanPostProcessor 接口
这是 Spring 提供的强大扩展点,允许在 Bean 初始化前后进行处理:
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor 前置处理: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor 后置处理: " + beanName);
return bean;
}
}
三、完整生命周期执行顺序示例
public class LifecycleBean implements BeanNameAware, BeanFactoryAware,
ApplicationContextAware, InitializingBean,
DisposableBean {
private String name;
// 构造方法
public LifecycleBean() {
System.out.println("1. 实例化:构造方法执行");
}
// setter 注入
public void setName(String name) {
System.out.println("2. 属性赋值:设置属性");
this.name = name;
}
// BeanNameAware 接口
@Override
public void setBeanName(String name) {
System.out.println("3. Aware接口:BeanNameAware.setBeanName()");
}
// BeanFactoryAware 接口
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("4. Aware接口:BeanFactoryAware.setBeanFactory()");
}
// ApplicationContextAware 接口
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("5. Aware接口:ApplicationContextAware.setApplicationContext()");
}
// 前置处理器(由容器调用BeanPostProcessor实现)
// 这里只是表示执行顺序,不是实际的方法
private void postProcessBeforeInitialization() {
System.out.println("6. BeanPostProcessor 前置处理");
}
// @PostConstruct 注解方法
@PostConstruct
public void postConstruct() {
System.out.println("7. 初始化:@PostConstruct 注解方法");
}
// InitializingBean 接口
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("8. 初始化:InitializingBean.afterPropertiesSet()");
}
// 自定义初始化方法
public void initMethod() {
System.out.println("9. 初始化:自定义init-method");
}
// 后置处理器(由容器调用BeanPostProcessor实现)
// 这里只是表示执行顺序,不是实际的方法
private void postProcessAfterInitialization() {
System.out.println("10. BeanPostProcessor 后置处理");
}
// 业务方法
public void use() {
System.out.println("11. 使用 Bean");
}
// @PreDestroy 注解方法
@PreDestroy
public void preDestroy() {
System.out.println("12. 销毁:@PreDestroy 注解方法");
}
// DisposableBean 接口
@Override
public void destroy() throws Exception {
System.out.println("13. 销毁:DisposableBean.destroy()");
}
// 自定义销毁方法
public void destroyMethod() {
System.out.println("14. 销毁:自定义destroy-method");
}
}
四、初始化顺序优先级
在 Spring 中,三种初始化方法的执行顺序为:
-
@PostConstruct
注解方法 -
InitializingBean
接口的afterPropertiesSet()
方法 - 自定义的
init-method
配置方法
五、销毁顺序优先级
三种销毁方法的执行顺序为:
-
@PreDestroy
注解方法 -
DisposableBean
接口的destroy()
方法 - 自定义的
destroy-method
配置方法
六、生命周期的应用场景
初始化阶段:
- 建立数据库连接
- 加载配置文件
- 初始化缓存
- 注册监听器
销毁阶段:
- 关闭数据库连接
- 释放网络资源
- 清理临时文件
- 发送最终通知
Spring 中的循环引用(循环依赖)
循环依赖:循环依赖,即循环引用,指的是两个或两个以上的Bean互相引用对方,最终形成一个闭环。例如,A依赖于B,B又依赖于A
在Spring框架中,循环依赖是被允许存在的。Spring框架通过三级缓存机制已有效解决了大部分的循环依赖问题
- 一级缓存:单例池,缓存已经经历了完整生命周期的Bean对象,即已经初始化完成的Bean实例
- 二级缓存:缓存早期的Bean对象,即生命周期尚未完全走完的Bean实例
- 三级缓存:缓存的是ObjectFactory,代表对象工厂,用于创建特定对象

三级缓存解决循环依赖
三级缓存只能解决初始化过程的循环依赖,不能解决构造函数中的循环依赖
Spring解决循环依赖是通过三级缓存,对应的三级缓存如下所示:
// 单实例对象注册器
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 一级缓存
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
}
缓存名称 | 源码名称 | 作用 |
---|---|---|
一级缓存 | singletonObjects | 单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象 |
二级缓存 | earlySingletonObjects | 缓存早期的bean对象(生命周期还没走完) |
三级缓存 | singletonFactories | 缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的 |
二级缓存不能解决代理对象的循环依赖


构造方法出现了循环依赖怎么解决
@Component
public class A {
private B b;
// 构造方法注入
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
// 构造方法注入
public B(A a) {
this.a = a;
}
}
Spring 通过三级缓存机制可以解决 setter 注入的循环依赖,但对于构造方法注入:
- 实例化和属性注入同时发生(构造方法执行的时候)
- 必须先有构造方法的参数对象,才能实例化当前对象
- 这就导致了 A 需要 B 先创建,B 又需要 A 先创建,形成死锁
改用 setter 方法注入
@Component
public class A {
private B b;
// 使用 setter 注入代替构造方法注入
@Autowired
public void setB(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
@Autowired
public void setA(A a) {
this.a = a;
}
}
使用 @Lazy 注解
@Component
public class A {
private B b;
public A(@Lazy B b) {
this.b = b;
}
}
使用 ApplicationContext 懒加载依赖
@Component
public class A {
private final ApplicationContext context;
public A(ApplicationContext context) {
this.context = context;
}
public void businessMethod() {
B b = context.getBean(B.class);
// 使用 b
}
}
SpringMVC 执行流程
视图阶段(JSP)

1️⃣ 用户发送请求至前端控制器DispatcherServlet
2️⃣ DispatcherServlet接收请求后,调用HandlerMapping(处理器映射器)
3️⃣ HandlerMapping定位到特定处理器,并生成处理器对象及(如有)处理器拦截器,随后将它们一同返回给DispatcherServlet。
4️⃣ DispatcherServlet调用HandlerAdapter(处理器适配器)
5️⃣ HandlerAdapter完成适配后,调用具体的处理器(Handler/Controller)
6️⃣ Controller执行完毕后,返回ModelAndView对象
7️⃣ HandlerAdapter将Controller的执行结果ModelAndView返回给DispatcherServlet
8️⃣ DispatcherServlet将ModelAndView传递给ViewResolver(视图解析器)
9️⃣ ViewResolver解析后,返回具体的View(视图)
🔟 DispatcherServlet根据View进行视图渲染(即将模型数据填充至视图中)
前后端分离阶段(接口开发、异步请求)

1️⃣ 用户发送请求至前端控制器DispatcherServlet
2️⃣ DispatcherServlet
接收请求,调用HandlerMapping
(处理器映射器)
3️⃣ HandlerMapping
定位到特定处理器,生成处理器对象及相应的拦截器(若存在),随后一同返回给DispatcherServlet
4️⃣ DispatcherServlet
调用HandlerAdapter
(处理器适配器)
5️⃣ HandlerAdapter
进行适配后,调用具体的处理器(Handler/Controller)
6️⃣ 在方法上添加了@ResponseBody
7️⃣ 通过HttpMessageConverter
将结果转换为SON并响应
SpringBoot 自动配置原理
在SpringBoot项目的引导类上,存在一个名为
@SpringBootApplication
的注解。该注解封装了以下三个注解:-
@SpringBootConfiguration
-
@EnableAutoConfiguration
-
@ComponentScan
-
其中,
@EnableAutoConfiguration
是实现自动化配置的核心注解。该注解通过@Import
注解导入相应的配置选择器。其内部机制是读取项目及其所引用的jar包的classpath
路径下META-INF/spring.factories
文件中配置的类的全类名。在这些配置类中定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中条件判断涉及如
@ConditionalOnClass
等注解,用于判断是否存在对应的class文件。如果存在,则加载该类,并将该配置类中定义的所有Bean放入Spring容器中使用
SpringBoot 的核心特性之一就是自动配置,它大大简化了 Spring 应用的配置过程
一、自动配置的核心组件
1. @SpringBootApplication 注解
这是启动类上的核心注解,它实际上是一个组合注解,包含了三个关键注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration // 启用自动配置
@ComponentScan // 组件扫描
public @interface SpringBootApplication {
// ...
}
其中 @EnableAutoConfiguration
是实现自动配置的关键
2. @EnableAutoConfiguration 注解
此注解的主要作用是启用 SpringBoot 的自动配置机制,其内部导入了 AutoConfigurationImportSelector
类:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class}) // 导入选择器
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
二、自动配置的实现机制
1. AutoConfigurationImportSelector 的作用
AutoConfigurationImportSelector
实现了 ImportSelector
接口,其关键方法是:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 获取所有候选的自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// ...
return configurations.toArray(new String[0]);
}
此方法返回需要被 Spring 容器导入的类名数组
2. spring.factories 机制
SpringBoot 使用 spring.factories
文件来实现自动配置类的注册。这个文件位于 META-INF 目录下,内容形式为:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.FooAutoConfiguration,\
com.example.BarAutoConfiguration
getCandidateConfigurations()
方法会调用 SpringFactoriesLoader.loadFactoryNames()
来加载所有 jar 包中 META-INF/spring.factories
文件中注册的自动配置类
三、条件注解的应用
自动配置类大量使用了条件注解来决定是否应该创建某个 Bean:
1. 常用条件注解
-
@ConditionalOnClass
:当特定类存在于类路径上时 -
@ConditionalOnMissingClass
:当特定类不存在于类路径上时 -
@ConditionalOnBean
:当特定 Bean 存在时 -
@ConditionalOnMissingBean
:当特定 Bean 不存在时 -
@ConditionalOnProperty
:当特定属性有特定值时 -
@ConditionalOnWebApplication
:当应用程序是 Web 应用时 -
@ConditionalOnResource
:当特定资源存在时
2. 示例分析
以 DataSourceAutoConfiguration
为例:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
// ...
}
这里使用了多个条件注解来确保:
- 类路径上有
DataSource
和EmbeddedDatabaseType
类 - 不存在 R2DBC 连接工厂
- 只有在满足这些条件时,才会创建数据源相关的 Bean
四、自动配置的加载过程
1. 加载所有自动配置类
在 SpringBoot 启动时,会执行以下步骤:
-
SpringFactoriesLoader
读取所有 jar 包中的META-INF/spring.factories
文件 - 获取所有标记为
EnableAutoConfiguration
的配置类 - 排除由
@EnableAutoConfiguration(exclude={...})
指定的类
2. 应用条件注解过滤
- 创建
ConditionEvaluator
来评估条件注解 - 对每个自动配置类应用条件注解
- 只保留满足条件的配置类
3. 配置类的排序处理
- 使用
@AutoConfigureBefore
和@AutoConfigureAfter
注解处理配置类间的顺序 - 使用
@AutoConfigureOrder
注解指定优先级
五、自动配置的执行
当自动配置类被加载到 Spring 容器后:
- Spring 会处理这些配置类中的
@Bean
方法 - 根据条件注解决定是否创建相应的 Bean
- 利用
@ConfigurationProperties
将外部配置绑定到 Bean 的属性
六、自定义自动配置
可以创建自己的自动配置:
- 创建自动配置类:
@Configuration
@ConditionalOnClass(YourLibrary.class)
@EnableConfigurationProperties(YourProperties.class)
public class YourAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public YourService yourService(YourProperties properties) {
return new YourServiceImpl(properties);
}
}
- 创建
META-INF/spring.factories
文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.your.package.YourAutoConfiguration
七、自动配置的优缺点
优点:
- 大幅简化配置
- 遵循"约定优于配置"原则
- 易于扩展和定制
缺点:
- 增加了学习曲线
- 可能产生难以预见的配置冲突
- 过于"魔术般"的自动配置可能降低代码可理解性
Spring 框架常见注解
Spring
注解 | 说明 |
---|---|
@Component、@Controller、@Service、@Repository | 使用在类上用于实例化Bean |
@Autowired | 使用在字段上用于根据类型依赖注入 |
@Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 |
@Scope | 标注Bean的作用范围 |
@Configuration | 指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解 |
@ComponentScan | 用于指定 Spring 在初始化容器时要扫描的包 |
@Bean | 使用在方法上,标注将该方法的返回值存储到Spring容器中 |
@Import | 使用@Import导入的类会被Spring加载到IOC容器中 |
@Aspect、@Before、@After、@Around、@Pointcut | 用于切面编程(AOP) |
SpringMVC
注解 | 说明 |
---|---|
@RequestMapping | 用于映射请求路径,可以定义在类上和方法上。用于类上,则表示类中的所有的方法都是以该地址作为父路径 |
@RequestBody | 注解实现接收http请求的json数据,将json转换为java对象 |
@RequestParam | 指定请求参数的名称 |
@PathVariable | 从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数 |
@ResponseBody | 注解实现将controller方法返回对象转化为json对象响应给客户端 |
@RequestHeader | 获取指定的请求头数据 |
@RestController | @Controller + @ResponseBody |
SpringBoot
注解 | 功能描述 | 适用位置 |
---|---|---|
@SpringBootApplication | 标记 SpringBoot 应用的主类,包含 @EnableAutoConfiguration 、@ComponentScan 和 @SpringBootConfiguration | 类 |
@EnableAutoConfiguration | 启用 SpringBoot 的自动配置机制 | 类 |
@ComponentScan | 指定要扫描的包路径,寻找 Spring 组件 | 类 |
@SpringBootConfiguration | 标记配置类,等效于 @Configuration | 类 |
其他补充(GPT)
配置类注解
注解 | 功能描述 | 适用位置 |
---|---|---|
@Configuration | 标记类为配置类,替代 XML 配置 | 类 |
@Bean | 声明一个方法产生的对象为 Spring 容器管理的 Bean | 方法 |
@Autowired | 自动注入依赖的 Bean | 字段、构造方法、Setter 方法 |
@Qualifier | 当有多个同类型 Bean 时,指定要装配的 Bean | 字段、方法参数 |
@Resource | 按名称注入 Bean,是 JSR-250 规范定义的注解 | 字段、方法 |
@Inject | 按类型注入 Bean,是 JSR-330 规范定义的注解 | 字段、构造方法、方法 |
@Value | 注入属性值或 SpEL 表达式的结果 | 字段、方法参数、构造方法参数 |
@Primary | 标记首选的 Bean,当存在多个同类型 Bean 时优先使用 | 类、方法 |
@Lazy | 指定 Bean 为懒加载模式 | 类、方法、字段、参数 |
@DependsOn | 声明 Bean 初始化前需要先初始化的其他 Bean | 类、方法 |
@Scope | 指定 Bean 的作用域,如 singleton、prototype 等 | 类、方法 |
@Profile | 指定 Bean 在特定环境(profile)下才被注册 | 类、方法 |
Web 开发注解
注解 | 功能描述 | 适用位置 |
---|---|---|
@Controller | 标记一个类为 Spring MVC 控制器 | 类 |
@RestController | 结合 @Controller 和 @ResponseBody ,用于 RESTful API | 类 |
@RequestMapping | 映射 HTTP 请求到控制器方法 | 类、方法 |
@GetMapping | 映射 HTTP GET 请求 | 方法 |
@PostMapping | 映射 HTTP POST 请求 | 方法 |
@PutMapping | 映射 HTTP PUT 请求 | 方法 |
@DeleteMapping | 映射 HTTP DELETE 请求 | 方法 |
@PatchMapping | 映射 HTTP PATCH 请求 | 方法 |
@RequestParam | 绑定请求参数到方法参数 | 方法参数 |
@PathVariable | 绑定 URL 路径变量到方法参数 | 方法参数 |
@RequestBody | 绑定请求体到方法参数 | 方法参数 |
@ResponseBody | 将方法返回值作为响应体返回 | 类、方法 |
@RequestHeader | 绑定请求头到方法参数 | 方法参数 |
@CookieValue | 绑定 Cookie 值到方法参数 | 方法参数 |
@ModelAttribute | 绑定表单数据或请求参数到模型对象 | 方法、方法参数 |
@ExceptionHandler | 处理特定类型的异常 | 方法 |
@ControllerAdvice | 定义全局异常处理、绑定数据等 | 类 |
@RestControllerAdvice | @ControllerAdvice 和 @ResponseBody 的组合 | 类 |
数据访问注解
注解 | 功能描述 | 适用位置 |
---|---|---|
@Repository | 标记类为数据访问层组件 | 类 |
@Transactional | 声明事务边界,可在类或方法级别配置事务属性 | 类、方法 |
@Entity | 标记类为 JPA 实体 | 类 |
@Table | 指定实体对应的数据库表 | 类 |
@Id | 标记字段为主键 | 字段 |
@Column | 指定字段映射到的表列名及属性 | 字段 |
@GeneratedValue | 指定主键的生成策略 | 字段 |
@JoinColumn | 指定外键列 | 字段 |
@OneToMany | 定义一对多关系 | 字段 |
@ManyToOne | 定义多对一关系 | 字段 |
@ManyToMany | 定义多对多关系 | 字段 |
条件注解
注解 | 功能描述 | 适用位置 |
---|---|---|
@Conditional | 基于条件判断是否创建 Bean | 类、方法 |
@ConditionalOnBean | 当指定的 Bean 存在时条件成立 | 类、方法 |
@ConditionalOnMissingBean | 当指定的 Bean 不存在时条件成立 | 类、方法 |
@ConditionalOnClass | 当指定的类存在于类路径时条件成立 | 类、方法 |
@ConditionalOnMissingClass | 当指定的类不存在于类路径时条件成立 | 类、方法 |
@ConditionalOnProperty | 当指定的属性具有指定值时条件成立 | 类、方法 |
@ConditionalOnResource | 当指定的资源存在时条件成立 | 类、方法 |
@ConditionalOnWebApplication | 当应用程序是 Web 应用时条件成立 | 类、方法 |
@ConditionalOnExpression | 当 SpEL 表达式为 true 时条件成立 | 类、方法 |
属性配置注解
注解 | 功能描述 | 适用位置 |
---|---|---|
@ConfigurationProperties | 绑定 properties/yml 文件中的属性到类 | 类 |
@EnableConfigurationProperties | 启用 @ConfigurationProperties 注解的类 | 类 |
@PropertySource | 指定要加载的属性文件 | 类 |
@ImportResource | 导入 XML 配置文件 | 类 |
@Import | 导入其他配置类 | 类 |
其他常用注解
注解 | 功能描述 | 适用位置 |
---|---|---|
@Service | 标记类为服务层组件 | 类 |
@Component | 标记类为 Spring 组件 | 类 |
@ServletComponentScan | 启用 Servlet 组件扫描 | 类 |
@EnableAsync | 启用异步方法调用 | 类 |
@Async | 标记方法为异步执行 | 方法 |
@EnableScheduling | 启用定时任务支持 | 类 |
@Scheduled | 声明方法为定时任务 | 方法 |
@EnableCaching | 启用缓存支持 | 类 |
@Cacheable | 声明方法结果可缓存 | 方法 |
@CachePut | 更新缓存,不影响方法执行 | 方法 |
@CacheEvict | 清除缓存 | 方法 |
@CrossOrigin | 允许跨域访问 | 类、方法 |
@Validated | 启用方法级别的验证 | 类、方法、参数 |
@Valid | 标记对象需要验证 | 方法参数、字段 |
@PostConstruct | 指定初始化方法 | 方法 |
@PreDestroy | 指定销毁方法 | 方法 |
@EventListener | 监听 Spring 事件 | 方法 |
MyBatis 执行流程
1️⃣ 读取MyBatis配置文件:mybatis-config.xml
,加载运行环境和映射文件
2️⃣ 构造会话工厂SqlSessionFactory
3️⃣ 会话工厂创建SqlSession
对象(包含了执行SQL语句的所有方法)
4️⃣ 操作数据库的接口,Executor
执行器,同时负责查询缓存的维护
5️⃣ Executor
接口的执行方法中包含一个MappedStatement
类型的参数,封装了映射信息
6️⃣ 输入参数映射
7️⃣ 输出结果映射
MyBatis 延迟加载及使用
MyBatis 支持延迟加载,但默认没有开启
延迟加载的含义是:仅在需要使用数据时才进行加载,若不需要使用数据则不加载
Mybatis支持 一对一关联对象 以及 一对多关联集合对象 的延迟加载
在Mybatis的配置文件中,可以设置是否启用延迟加载,通过设置lazyLoadingEnabled
属性为true
或false
,默认情况下,延迟加载是关闭的

原理
- 使用CGLIB创建目标对象的代理对象
- 调用目标方法
user.getOrderList()
时,进入拦截器的invoke
方法,发现user.getOrderList()
返回值为null,执行SQL查询以获取order
列表 - 查询结果
order
列表被获取,随后调用user.setOrderList(List<order> orderList)
,接着完成对user.getOrderList()
方法的调用

MyBatis 的一级、二级缓存
本地缓存,基于PerpetualCache
,本质是一个HashMap
一级缓存:作用域为session
级别
二级缓存:作用域为namespace
和mapper
的作用域,不依赖于session
一级缓存
一级缓存:基于PerpetualCache
的HashMap
本地缓存,其存储作用域为Session
。当Session
执行flush()
或close()
操作后,该Session
中的所有Cache
将清空。默认情况下,一级缓存是开启的
// 2. 获取 SqlSession 对象,用它来执行 sql
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 执行 sql
// 3.1 获取 UserMapper 接口的代理对象
UserMapper userMapper1 = sqlSession.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession.getMapper(UserMapper.class);
User user = userMapper1.selectByld(6);
System.out.println(user);
System.out.println("----------------------------");
User user1 = userMapper2.selectByld(6);
System.out.println(user1);
只会执行一次 SQL 查询(前提是同一个 sqlSession)
二级缓存
二级缓存依赖于namespace
和mapper
的作用域而生效,并不依赖于SQL会话。其默认采用PerpetualCache
,使用HashMap
进行存储
二级缓存默认处于关闭状态。
开启方法分为两步:
- 全局配置文件中:
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
- 映射文件中:
使用 <cache/>
标签使当前 mapper 二级缓存生效
// 2. 获取 SqlSession 对象,用它来执行 sql
SqlSession sqlSession1 = sqlSessionFactory.openSession();
// 3. 执行 sql
// 3.1 获取 UserMapper 接口的代理对象
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.selectByld(6);
System.out.println(user1);
sqlSession1.close(); // 关闭资源
SqlSession sqlSession2 = sqlSessionFactory.openSession();
System.out.println("----------------------------");
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.selectByld(6);
System.out.println(user2);
// 4. 关闭资源
sqlSession2.close();
步骤 | 行为 | 是否真正访问数据库 | 结果说明 |
---|---|---|---|
①selectById(6) in sqlSession1 | 第一次查询 | 是(发出 SELECT SQL) | 返回 User{id=6, ...},并把结果写入二级缓存(namespace 级) |
② 关闭 sqlSession1 | 触发缓存刷入 | — | 关闭会将该 SqlSession 中的一级缓存数据同步到对应 Mapper 的二级缓存 |
③ 打印分隔线 | — | — | 控制台输出---------------------------- |
④selectById(6) in sqlSession2 | 第二次查询 | 否(直接命中二级缓存) | 直接从二级缓存拿到同一条User数据,不再向数据库发送 SQL |
⑤ 输出结果 | — | — | 两次 System.out.println(userX) 都打印出同样字段值的User对象 |
注意事项:
- 对于缓存数据更新机制,当某一作用域(一级缓存Session/二级缓存Namespaces)进行新增、修改、删除操作后,默认该作用域下所有select中的缓存将被清除
- 二级缓存所需缓存的数据需实现
Serializable
接口 - 仅当会话提交或关闭后,一级缓存中的数据才会转移至二级缓存中