Spring相关面试题总结

Spring面试题

1.对SpringIOC的理解

1.什么是IOC

​ IOC(Inversion of Control):对象的创建、依赖注入及生命周期管理由Spring容器负责,而非程序员手动控制(如 new 对象)。

​ 核心思想:将控制权交给框架,实现解耦。

2.IOC的实现方式

​ 依赖注入(DI,Dependency Injection):Spring 通过 构造函数注入,Setter注入 或 字段注入自动装配依赖对象。

3.IOC的优势

​ 解耦:对象间依赖由容器管理,降低代码耦合度。

​ 可测试性:依赖可替换(如 Mock 对象)。

​ 同一管理:Bean的生命周期、作用域(Singleton/Prototype)由容器控制。

2.对SpringAOP的理解

1.什么是AOP?

​ AOP(Aspect-Oriented Programming):通过横向切割代码,将通用逻辑(如日志、事务、权限)从业务逻辑中分离,实现解耦和代码复用。

​ 核心思想:在不修改源代码的情况下,动态增强方法功能。

2.AOP的核心概念

术语 说明
切面(Aspect) 封装横切逻辑的模块(如日志切面、事务切面),通常是一个@Aspect类。
连接点(JoinPoint) 程序执行的点(如方法调用、异常抛出),Spring AOP仅支持方法级别。
通知(Advice) 切面在连接点的动作(如@Before@After)。
切入点(Pointcut) 定义哪些连接点会被切面处理(通过表达式匹配,如execution(* com.example.*.*(..)))。
目标对象(Target) 被代理的原始对象(如UserService)。
代理(Proxy) Spring通过JDK动态代理或CGLIB生成增强后的对象。

3.SpringAOP的实现方式

​ 注解配置(主流方式)

java 复制代码
@Aspect
@Component
public class LogAspect {
    // 定义切入点:匹配com.example.service包下所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}

    // 前置通知:在目标方法执行前运行
    @Before("serviceMethods()")
    public void beforeAdvice(JoinPoint jp) {
        System.out.println("方法调用前: " + jp.getSignature().getName());
    }

    // 环绕通知:控制方法执行
    @Around("serviceMethods()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("方法执行前...");
        Object result = pjp.proceed(); // 调用目标方法
        System.out.println("方法执行后...");
        return result;
    }
}

4.Spring AOP的局限

​ 仅支持方法级别的拦截:无法拦截字段访问或构造器调用。

​ 仅作用于Spring管理的Bean:对非容器对象无效。

​ 自调用问题:同一类内方法调用不会触发AOP(需通过代理对象调用)。。

3.说一下SpringMVC的运行流程

1.用户发起请求

​ 用户通过浏览器发送 HTTP 请求,请求到达Web容器(如 Tomcat)。

2.DispatcherServlet 拦截请求

​ 前端控制器 DispatcherServlet 拦截所有请求,作为统一入口。

3.调用 HandlerMapping

​ DispatcherServlet 通过 HandlerMapping 查找请求对应的处理器(Handler)和 拦截器(Interceptor)。

4.执行拦截器(Interceptor)

​ 若配置了拦截器链(如登录校验),按顺序执行 preHandle() 方法。 若任意拦截器返回 false,流程终止。

5.调用 HandlerAdapter

​ HandlerAdapter 适配器调用目标处理器(Controller 方法)。

​ 常见实现:

RequestMappingHandlerAdapter:处理 @Controller 注解方法。

HttpRequestHandlerAdapter:处理 HttpRequestHandler 接口。

6.执行 Controller

​ Controller 方法处理业务逻辑,返回逻辑视图名(String)或 ModelAndView 对象。

7.处理返回值(ViewResolver)

​ 视图解析器(ViewResolver)将逻辑视图名解析为实际视图对象(如JSP、Thymeleaf模板)。

8.渲染视图(View)

​ 视图对象渲染模型数据,生成HTML相应。

​ 若返回 @ResponseBody,直接写入 JSON/XML数据(跳过视图解析)。

9.返回响应

​ 渲染结果通过 DispatcherServlet 返回给用户浏览器。

4.SpringMVC常见的注解有哪些

1.请求映射注解

注解 作用 示例
@RequestMapping 通用请求映射(可指定HTTP方法、路径等)。 @RequestMapping("/user")
@GetMapping 简化版GET请求映射(等价于@RequestMapping(method=RequestMethod.GET))。 @GetMapping("/list")
@PostMapping 简化版POST请求映射。 @PostMapping("/create")
@PutMapping 简化版PUT请求映射。 @PutMapping("/update/{id}")
@DeleteMapping 简化版DELETE请求映射。 @DeleteMapping("/delete/{id}")

2.参数处理注解

注解 作用 示例
@RequestParam 获取URL参数或表单数据(默认必传,可设required=false)。 @RequestParam("name") String username
@PathVariable 获取RESTful路径变量。 @GetMapping("/user/{id}") public User getById(@PathVariable Long id)
@RequestBody 解析请求体为对象(如JSON/XML)。 @PostMapping("/save") public void save(@RequestBody User user)
@RequestHeader 获取HTTP请求头。 @RequestHeader("User-Agent") String userAgent

3.响应处理注解

注解 作用 示例
@ResponseBody 将返回值直接写入HTTP响应体(如返回JSON)。 @ResponseBody @GetMapping("/info")
@RestController 组合注解(@Controller + @ResponseBody),用于REST API。 @RestController @RequestMapping("/api")

4.文件上传

注解 作用 示例
@MultipartFile 处理文件上传(需配合multipart/form-data)。 @PostMapping("/upload") public String upload(@RequestParam("file") MultipartFile file)

5.跨域处理

注解 作用 示例
@CrossOrigin 允许跨域请求(可细化到方法或控制器)。 @CrossOrigin(origins = "https://example.com")

5.谈谈你对SpringBoot的理解以及优点

1.什么是SpringBoot?

​ SpringBoot 是 Spring 生态的快速开发框架,通过约定大于配置的理念,简化了Spring 应用的初始搭建和开发流程。

​ 核心目标:让开发者专注于业务逻辑,而非繁琐的配置。

2.SpringBoot的核心优点

优点 说明 示例
1. 快速启动 内置 Tomcat/Jetty,无需部署 WAR 包,直接运行 main 方法启动应用。 @SpringBootApplication + SpringApplication.run(MyApp.class, args)
2. 自动配置 根据依赖自动配置 Bean(如引入 spring-boot-starter-data-jpa 自动配数据源)。 无需手动配置 DataSourceEntityManager 等。
3. 起步依赖 通过 starter 依赖一键引入相关技术栈(如 spring-boot-starter-web)。 Maven 中只需添加一个依赖,而非多个分散的库。
4. 无 XML 配置 完全基于 Java 注解和配置类,告别繁琐的 XML。 使用 @Configuration 替代 applicationContext.xml
5. 嵌入式服务器 内置 Tomcat/Jetty/Undertow,无需额外安装服务器。 打包为可执行 JAR,直接 java -jar 运行。
6. 生产就绪 提供监控端点(如 /actuator/health)、指标收集、外部化配置等。 集成 Spring Actuator 监控应用状态。
7. 丰富的生态整合 无缝集成 Spring 生态(Spring Data、Spring Security等)及第三方库(Redis、Kafka)。 通过 starter 快速集成 Redis:spring-boot-starter-data-redis

6.SpringBoot读取配置的方式有几种

1.默认配置文件

​ 通过@Value注解进行读取

​ 优先级:最低(被其他配置覆盖)

yml 复制代码
# application.yml
app:
  name: "MyApp"
  timeout: 5000
java 复制代码
@Value("${app.name}")
private String appName;

2.环境变量与命令行参数

​ 优先级:最高(覆盖其他配置)

命令行参数:

bash 复制代码
java -jar app.jar --server.port=8081

环境变量:

bash 复制代码
export APP_NAME="MyApp"
java 复制代码
@Value("${APP_NAME}")
private String appName;

3.@ConfigurationProperties绑定到对象

适用场景:批量读取配置文件并封装为Java对象

yml 复制代码
# application.yml
myapp:
  api:
    url: "https://api.example.com"
    key: "123456"
java 复制代码
@Configuration
@ConfigurationProperties(prefix = "myapp.api")
public class ApiConfig {
    private String url;
    private String key;
    // Getter/Setter
}

// 注入使用
@Autowired
private ApiConfig apiConfig;

4.编程式读取(Environment接口)

适用场景:动态判断配置是否存在或灵活处理

java 复制代码
@Autowired
private Environment env;

public void demo() {
    String dbUrl = env.getProperty("spring.datasource.url");
    boolean debug = env.getProperty("app.debug", Boolean.class, false);
}

7.SpringBoot配置文件有几种类型,区别是什么

(1) .properties 文件

​ 格式:键值对形式,使用 = 或 :分割

​ 特点:简单直观,适合简单配置。 不支持层级结构,重复前缀较多。

properties 复制代码
server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/mydb

(2) .yml/.yaml 文件

​ 格式:基于缩进的层级结构,使用:和换行表示层级

​ 特点:结构化清晰,适合复杂配置。 支持多环境配置(通过---分割)。注意缩进(必须使用空格,不能用Tab)

yml 复制代码
server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb

(3) 多环境配置文件

​ 命令规则:application-{profile}.properties 或 application-{profile}.yml。

​ 示例:

​ application-dev.yml (开发环境)

​ application-prod.yml (生产环境)

​ 激活方式:

​ 命令行参数:--spring.profiles.active==dev

​ 环境变量:export SPRING_PROFILES_ACTIVE=prod

对比项 .properties .yml 多环境配置
格式 扁平键值对(key=value 层级缩进(YAML 语法) 通过 -{profile} 后缀区分
可读性 一般(重复前缀多) 高(结构化清晰) 按环境分离,维护方便
复杂配置支持 强(支持数组、对象等) 独立文件,避免配置混杂
多环境管理 需多个文件 支持单文件多环境(---分隔) 显式激活指定环境
兼容性 所有 Spring 版本 Spring Boot 优先推荐 通用

8.SpringBoot的核心注解是哪个

@SpringBootApplication 是Spring Boot应用的启动入口注解,标注在主启动类上,整合了以下三个关键注解的功能:

java 复制代码
@SpringBootConfiguration  // 标记该类为配置类
@EnableAutoConfiguration  // 启用自动配置
@ComponentScan           // 自动扫描当前包及其子包的组件
子注解 功能说明
@SpringBootConfiguration 继承自 @Configuration,表示该类是一个 Spring 配置类(定义 Bean 的地方)。
@EnableAutoConfiguration 启用 Spring Boot 的自动配置机制(根据依赖自动配置 Bean,如数据源、MVC 等)。
@ComponentScan 自动扫描当前类所在包及其子包中的 @Component@Service@Controller 等。

9.SpringBoot的自动装配原理

1.自动装配的核心机制

​ 自动装配的本质是根据项目依赖和配置,动态注册符合条件的 Bean 到 Spring 容器,无需手动编写 @Bean 配置。

2.实现自动装配的关键步骤

​ 触发条件:@EnableAutoConfiguration

​ @SpringBootApplication 组合了 @EnableAutoConfiguration,该注解通过 @Import 加载自动配置逻辑。

java 复制代码
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration { ... }

3.自动装配的工作流程

复制代码
1.启动阶段:

​ SpringBoot启动时,AutoConfigurationImportSelector 扫描所有 AutoConfiguration.imports 文件。

​ 2.条件过滤:

​ 根据 @Conditional 注解排除不满足条件的配置类(如缺少依赖类、Bean已存在等)。

​ 3.Bean注册:

​ 剩下的自动配置类 通过 @Bean 方法向容器注册组件 (如DataSource等)

​ 4.属性绑定:

​ 通过@ConfigurationProperties 将 application.yml 中的配置注入到Bean。

10.Spring中Bean的注入方式有几种

Spring支持 3 种核心依赖注入方式(按注入途径分类)。

1.构造器注入(Constructor Injection)

​ 方式:通过类的构造方法注入依赖。

​ 特点:

​ 强依赖:适合必须的依赖项。

​ 不可变:依赖字段可设为 final,保证线程安全。

​ 推荐:Spring 官方推荐方式(尤其Spring 4.3+ 可省略 @Autowired)

java 复制代码
@Service
public class UserService {
    private final OrderService orderService;
    
    // Spring 4.3+ 自动识别构造器注入
    public UserService(OrderService orderService) {
        this.orderService = orderService;
    }
}

2.Setter 注入(Setter Injection)

​ 方式:通过Setter 方法注入依赖。

​ 特点:

​ 可选依赖:适合非必须的依赖

​ 灵活性高:可动态重新注入(如测试时 Mock 对象)。

java 复制代码
@Service
public class UserService {
    private OrderService orderService;
    
    @Autowired  // 也可用 @Resource
    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }
}

3.字段注入(Field Injection)

​ 方式:直接通过字段注入依赖。

​ 特点:

​ 简洁但隐蔽:代码少,但隐藏依赖关系,不利于测试和维护。

​ 不推荐:破坏封装性,无法注入 final 字段。

注入方式 优点 缺点 适用场景
构造器注入 不可变、线程安全、显式声明依赖 参数较多时代码略冗长 推荐 强制依赖场景
Setter 注入 灵活、可选依赖 可能因遗漏调用导致 NPE 可选依赖或需动态配置
字段注入 代码简洁 隐藏依赖、难测试 快速原型开发(不推荐生产)

11.Spring中Bean的作用域有几种

1.Singleton(单例,默认)

​ 特点:整个Spring容器中只存在一个 Bean 实例。

​ 适用场景:无状态的Bean(如Service、DAO)。

​ 配置方式:

java 复制代码
@Scope("singleton")  // 或默认不写
@Service
public class UserService { ... }

2.Prototype(原型/多例)

​ 特点:每次请求(getBean() 或 注入)都会创建新实例。

​ 适用场景:有状态的 Bean(如DTO、Session)

​ 配置方式:

java 复制代码
@Scope("prototype")
@Component
public class User { ... }

3.Request(请求域,Web)

​ 特点:每个Http请求创建一个新实例,请求结束后销毁。

​ 适用场景:存储请求相关数据(如用户表单数据)

​ 配置方式:

java 复制代码
@Scope("request")
@Component
public class LoginInfo { ... }

4.Session(会话域,Web)

​ 特点:每个用户会话(Session)创建一个实例,会话过期后销毁。

​ 适用场景:用户登录状态,购物车。

​ 配置方式:

java 复制代码
@Scope("session")
@Component
public class ShoppingCart { ... }

5.Application(全局Web应用域)

​ 特点:整个Web应用共享一个实例,类似ServletContext。

​ 适用场景:全局配置、缓存。

​ 配置方式:

java 复制代码
@Scope("application")
@Component
public class AppConfig { ... }

6.WebSocket(WebSocket会话域)

​ 特点:每个WebSocket 会话一个实例,会话结束后销毁。

​ 适用场景:实时通信(如聊天室)。

12.SpringAOP的通知类型有哪些

SpringAOP提供了 5 种通知类型,用于在目标方法的不同执行阶段插入横切逻辑:

1.@Before(前置通知)

​ 执行时机:目标方法执行前触发。

​ 适用场景:权限校验、日志记录、参数预处理。

java 复制代码
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint jp) {
    System.out.println("方法执行前:" + jp.getSignature().getName());
}

2.@AfterReturning(返回后通知)

​ 执行时机:目标方法正常返回后触发(不抛异常时)。

​ 适用场景:记录返回值、结果处理。

java 复制代码
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void afterReturningAdvice(Object result) {
    System.out.println("方法返回结果:" + result);
}

3.@AfterThrowing(异常通知)

​ 执行时机:目标方法抛出异常后触发。

​ 适用场景:异常捕获、错误日志记录。

java 复制代码
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
public void afterThrowingAdvice(Exception ex) {
    System.out.println("方法抛出异常:" + ex.getMessage());
}

4.@After(最终通知)

​ 执行时机:目标方法执行完毕后触发(无论是否抛出异常,类似finally)

​ 适用场景:资源清理、日志记录。

java 复制代码
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice() {
    System.out.println("方法执行结束(无论成功或失败)");
}

5.Around(环绕通知)

​ 执行时机:包裹目标方法,可控制方法执行前后、返回值、异常等。

​ 适用场景:事务管理、性能监控、方法重试。

java 复制代码
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("方法执行前...");
    Object result = pjp.proceed();  // 调用目标方法
    System.out.println("方法执行后...");
    return result;
}

13.@Autowired 和 @Resource的区别

1.@Autowired使用方式

java 复制代码
@Service
public class UserService {
    // 默认按类型注入
    @Autowired  
    private OrderService orderService;

    // 按名称注入(需配合 @Qualifier)
    @Autowired  
    @Qualifier("userDaoImpl")  
    private UserDao userDao;
}

2.@Resource使用方式

java 复制代码
@Service
public class UserService {
    // 默认按名称注入(字段名需与 Bean 名一致)
    @Resource  
    private OrderService orderService;

    // 显式指定名称
    @Resource(name = "userDaoImpl")  
    private UserDao userDao;
}
对比项 @Autowired (Spring) @Resource (JSR-250, Java标准)
来源 Spring 框架提供 Java 标准注解(javax.annotation包)
默认注入方式 按类型(byType) 按名称(byName),找不到则回退到 byType
名称指定 需配合 @Qualifier 指定 Bean 名称 直接通过 name 属性指定(如 @Resource(name="userService")
适用场景 推荐 Spring 生态使用 需要兼容非 Spring 环境(如 J2EE)
依赖查找顺序 1. 按类型匹配 → 2. 有 @Qualifier 则按名称 1. 按名称匹配 → 2. 找不到则按类型
是否支持构造函数注入 ✔️ 支持(Spring 4.3+ 可省略 @Autowired ❌ 不支持

14.@Component 和 @Bean注解的区别?

1.声明方式

​ @Component 是类注解,通过组件扫描(@ComponentScan)自动注册 Bean。

​ @Bean 是方法注解,需在 @Configuration 类中显示定义Bean。

2.控制权

​ @Component 由 Spring自动实例化。

​ @Bean 由开发者完全控制实例化过程(如条件装配@Conditional)

3.适用对象

​ @Component 适用于自定义类。

​ @Bean 适用于无法修改源码的类(如第三方库的 DataSource)。

对比项 @Component @Bean
作用目标 类级别(标记类为 Spring 组件) 方法级别(在配置类中定义 Bean)
使用场景 自动扫描并注册 Bean(如 @Service, @Repository 手动控制 Bean 的创建逻辑(如第三方库的类)
依赖注入 自动完成(通过 @Autowired 需在方法中显式构造并返回对象
灵活性 适用于标准 Bean 更灵活,可自定义初始化/销毁逻辑
示例 java @Component public class UserService { ... } java @Configuration public class AppConfig { @Bean public DataSource dataSource() { return new HikariDataSource(); } }

15.常见的HTTP响应状态码

HTTP状态码用于表示服务器对请求的处理结果,分为5类(以首位数字区分):

1xx(信息性状态码)

  • 100 Continue:客户端应继续发送请求(用于大文件上传前确认)。
  • 101 Switching Protocols:服务器同意切换协议(如 WebSocket)。

2xx(成功状态码)

  • 200 OK:请求成功(GET/POST 返回数据正常)。
  • 201 Created:资源创建成功(如 POST 新增数据后返回)。
  • 202 Accepted:请求已接受但未处理完成(异步任务)。
  • 204 No Content:请求成功,但无返回内容(如 DELETE 请求)。

3xx(重定向状态码)

  • 301 Moved Permanently:资源永久重定向(SEO 会更新链接)。
  • 302 Found:资源临时重定向(浏览器会缓存原地址)。
  • 304 Not Modified:资源未修改(缓存生效,用于协商缓存)。

4xx(客户端错误)

  • 400 Bad Request:请求语法错误(如参数格式错误)。
  • 401 Unauthorized:未认证(需登录或 Token 无效)。
  • 403 Forbidden:无权限访问(认证成功但权限不足)。
  • 404 Not Found:资源不存在(路径错误或已删除)。
  • 405 Method Not Allowed:请求方法不被允许(如 GET 接口用 POST 调用)。
  • 429 Too Many Requests:请求过于频繁(限流触发)。

5xx(服务器错误)

  • 500 Internal Server Error:服务器内部错误(代码异常)。
  • 502 Bad Gateway:网关/代理服务器收到无效响应(如上游服务崩溃)。
  • 503 Service Unavailable:服务不可用(如系统维护或过载)。
  • 504 Gateway Timeout:网关超时(上游服务未及时响应)。

16.GET和POST的区别

HTTP协议中,GET 和 POST 是最常用的两种请求方法。核心区别如下:

1. 语义与用途

GET POST
获取资源(幂等操作,不应修改数据) 提交数据(非幂等,可能修改数据)
适用于查询、搜索、分页等场景 适用于创建、更新、删除等场景

2. 数据传输方式

GET POST
数据通过 URL 的 Query 参数 传递(如 ?name=foo&age=20 数据通过 请求体(Body) 传递(支持 JSON、Form-Data 等格式)
数据可见(暴露在地址栏,不安全) 数据不可见(相对安全)
URL 长度受限(浏览器通常限制为 2KB~8KB) 数据大小无严格限制(服务器可配置)

3. 缓存与历史记录

GET POST
可被浏览器缓存、保留历史记录 不会被缓存,重复提交可能触发警告(如"确认重新提交表单")
书签或链接可直接访问(含参数) 书签无法保存请求体数据

4. 安全性

GET POST
参数在 URL 中,容易被日志或浏览器历史记录泄露 数据在 Body 中,适合传输敏感信息(但需配合 HTTPS)
CSRF 攻击风险更高(因 URL 可被恶意构造) 相对安全(仍需防护 CSRF)

5. 幂等性与副作用

GET POST
幂等(多次请求结果相同,无副作用) 非幂等(多次请求可能产生不同结果,如重复提交订单)
适用于只读操作 适用于写操作

17.Cookie 和 Session 的区别

1.存储机制

​ Cookie:

​ 数据以键值对形式存储在浏览器中(可通过document.cookie 访问)

​ 每次请求会自动附加到HTTP头的 Cookie 字段发送给服务器。

​ Session:

​ 数据存储在服务端,客户端仅保存一个Session ID(通常通过 Cookie 传递)。

​ 服务器根据Session ID 查询对应的客户数据。

2.安全性对比

​ Cookie:

​ 明文存储,容易被篡改(需配合 HttpOnly、Secure 标志提升安全)。

​ Session:

​ 敏感数据(如用户ID)存在服务端,仅暴露Session ID,更安全。

3.生命周期控制

​ Cookie:

​ 通过 Max-Age 或 Expires 设置有效期(如 Max-Age=3600 表示 1小时后过期)

​ 关闭浏览器后,默认的会话Cookie失效。

​ Session:

​ 服务端可设置超时时间(如 Spring Session 的 server.servlet.session.timeout=30m)。

​ 浏览器关闭后,Session ID 丢失导致会话失效(除非持久化Cookie)。

4.跨域与共享

​ Cookie:

​ 受同源策略限制,跨域需设置 SameSite=None + Secure。

​ Session:

​ 多服务器环境下需集中存储(如 Redis),否则默认无法共享。

对比项 Cookie Session
存储位置 客户端(浏览器) 服务端(服务器内存、数据库、Redis等)
安全性 较低(可被篡改或窃取) 较高(敏感信息存在服务端)
数据大小 单个 Cookie ≤ 4KB,域名下总数有限 理论上无限制(受服务器内存影响)
生命周期 可设置过期时间(Expires/Max-Age 通常依赖会话(浏览器关闭后默认失效,可设置超时)
跨域支持 受同源策略限制 天然支持(因为数据在服务端)
性能影响 每次请求自动携带,增加带宽 需服务端存储和查询,占用服务器资源
典型用途 记住登录状态、跟踪用户行为 保存用户会话信息(如登录凭证、购物车)

18.redirect 和 forward 的区别

1.工作流程

​ redirect(重定向):

​ 浏览器请求 URL-A。

​ 服务器返回302/307 状态码 + Location:/URL-B。

​ 浏览器自动请求 URL-B。

​ 服务器返回 URL-B 的内容。

​ forward(转发):

​ 浏览器请求 URL-A。

​ 服务器在内部将请求转发给 URL-B 处理。

​ 最终返回 URL-B 的结果,但浏览器仍然显示 URL-A。

2.数据共享

​ redirect:

​ 原始请求的 request 数据丢失(因为是两次独立请求)。

​ 可通过 URL 参数 或 Session 传递数据。

​ foward:

​ 共享同一个 request 对象,可直接传递属性。

对比项 redirect(重定向) forward(转发)
工作方式 客户端行为(返回 302/307 状态码,浏览器发起新请求) 服务器内部跳转(同一请求在服务端传递)
URL 变化 会变(显示新地址) 不变(浏览器地址栏不变)
请求次数 2 次(客户端发起两次 HTTP 请求) 1 次(服务端内部处理)
数据传递 不能直接共享原始请求数据(需通过 URL 或 Session) 可共享 requestresponse 对象
性能 较慢(多一次网络往返) 较快(无额外网络开销)
适用场景 跨应用跳转、防止表单重复提交 同一应用内的页面跳转(如 MVC 控制器间)
实现示例 response.sendRedirect("/new-url") request.getRequestDispatcher("/path").forward(request, response)

19.Spring事务失效场景

1.方法访问权限问题

​ 非 public 方法:@Transactional 只能用于 public 方法。

​ 原因:SpringAOP 代理机制限制

java 复制代码
@Transactional
private void updateOrder() { // 事务失效
    // ...
}

2.方法自调用问题

​ 同一个类中方法调用:A方法(无事务)调用B方法(有事务)

​ 解决方案:

​ 使用 AopContext.currentProxy()

​ 将方法拆分到不同类中

java 复制代码
public void process() {
    this.updateOrder(); // 事务失效
}

@Transactional
public void updateOrder() {
    // ...
}

3.异常类型不正确

​ 默认只回滚 RuntimeException 和 Error

​ 检查型异常不会触发回滚

java 复制代码
@Transactional
public void update() throws Exception { // 抛出检查型异常不会回滚
    // ...
}

​ 解决方案:

java 复制代码
@Transactional(rollbackFor = Exception.class)

4.异常被捕获

​ 异常被 catch 后没有重新抛出

java 复制代码
@Transactional
public void update() {
    try {
        // ...
    } catch (Exception e) {
        e.printStackTrace(); // 事务失效
    }
}

5.数据库引擎不支持

​ 必须使用 InnoDB,MyISAM引擎只支持表锁,不支持事务。

6.传播行为设置不当

​ PROPAGATION_NOT_SUPPORTED:挂起当前事务

​ PROPAGATION_NEVER:不能有事务

java 复制代码
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void update() {
    // 无事务运行
}

SpringCloud面试题

1.SpringCloud的核心组件有哪些?

SpringCloud为分布式系统开发提供了一套完整的解决方案,以下是其核心组件及其作用:

1.服务注册于发现

① Eureka (Netflix)

​ 作用:服务注册中心,管理所有微服务的地址信息。

​ 关键特性:

​ 服务注册:微服务启动时向Eureka注册

​ 服务发现:通过服务名调用而非IP地址

​ 心跳检测:定期检查服务健康状态

② Nacos (Alibaba)

​ 优势:同时支持服务注册与配置中心,AP/CP模式可切换

​ 对比Eureka:

​ 提供DNS-F功能(支持权重路由)

​ 集成配置管理

​ 支持K8s集成

2.服务调用

Ribbon (客户端负载均衡)

​ 功能:

​ 基于服务名的负载均衡(轮询、随机、权重等策略)

​ 与RestTemplate/OpenFegin集成

​ 示例:

java 复制代码
@Bean
@LoadBalanced // 开启负载均衡
public RestTemplate restTemplate() {
    return new RestTemplate();
}

OpenFeign (声明式HTTP客户端)

​ 特点:

​ 通过接口+注解定义HTTP请求

​ 内置Ribbon负载均衡

​ 支持熔断降级

​ 示例:

java 复制代码
@FeignClient(name = "order-service")
public interface OrderClient {
    @GetMapping("/orders/{id}")
    Order getOrder(@PathVariable Long id);
}

3.服务容错

① Hystrix (熔断降级)

​ 核心机制:

​ 熔断:当失败率超过阈值时自动切断请求

​ 降级:返回预设的fallback结果

​ 隔离:线程池/信号量隔离资源

② Sentinel (Alibaba替代方案)

​ 优势:

​ 实时监控和控制台

​ 支持流量控制、熔断机制、系统保护

​ 规则可动态配置

4.服务网关

① Zuul (Netflix)

​ 功能:

​ 路由转发:/user-service/** -> 用户服务

​ 过滤器:权限校验、限流

② Spring Cloud Gateway (官方推荐)

​ 优势:

​ 基于WebFlux的非阻塞模型

​ 支持Predicate和Filter链

​ 更好的性能

​ 示例:

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: order-service
        uri: lb://order-service
        predicates:
          - Path=/order/**

5.配置中心

① Spring Cloud Config

​ 架构:

​ 配置存储在Git/SVN等版本库

​ 客户端通过HTTP获取配置

② Nacos Config

​ 优势:

​ 配置动态刷新(无需重启)

​ 与服务发现共用同一组件

​ 示例:

java 复制代码
@RefreshScope // 支持动态刷新
@RestController
public class ConfigController {
    @Value("${app.config}")
    private String config;
}

6.消息驱动

Spring Cloud Stream

​ 作用:统一消息中间件接口(Kafka/RabbitMQ)

7.分布式链路追踪

① Sleuth + Zipkin

​ 功能:

​ 为请求添加唯一Trace ID

​ 记录调用链耗时

② SkyWalking (APM工具)

​ 优势:

​ 可视化拓扑图

​ 支持多种语言探针

2.OpenFeign是如何使用的?

OpenFeign 是 Spring Cloud 提供的声明式 HTTP 客户端,用于简化微服务间的 RESTful 调用。

一、基础配置

​ 1.添加依赖

xml 复制代码
<!-- Spring Cloud OpenFeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

​ 2.启用 Feign 客户端

​ 在启动类添加 @EnableFeignClients 注解:

java 复制代码
@SpringBootApplication
@EnableFeignClients
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

二、基本使用

​ 1.定义Feign客户端接口

java 复制代码
@FeignClient(name = "user-service") // 指定服务名称
public interface UserClient {
    
    @GetMapping("/users/{id}") // 映射服务端点
    User getUserById(@PathVariable("id") Long id);
    
    @PostMapping("/users")
    User createUser(@RequestBody User user);
}

​ 2.注入使用

java 复制代码
@RestController
@RequestMapping("/orders")
public class OrderController {
    
    @Autowired
    private UserClient userClient;
    
    @GetMapping("/{orderId}/user")
    public User getUserByOrder(@PathVariable Long orderId) {
        // 像调用本地方法一样调用远程服务
        return userClient.getUserById(orderId);
    }
}

3.RabbitMQ工作模式有哪些?

RabbitMQ 作为流行的消息中间件,支持多种工作模式以满足不同业务场景需求。

​ 1.简单模式(Simple)

​ 架构:生产者 → 队列 → 消费者

​ 特点:

​ 最简单的消息队列模式

​ 单一生产者、单一队列、单一消费者

​ 消息一旦被消费就从队列种删除

​ 适用场景:单对单的简单消息传递

​ 2.工作队列模式(Work Queue)

​ 架构:

复制代码
生产者 → 队列 → 消费者1
               → 消费者2

​ 特点:

​ 一个队列对应多个消费者

​ 消息被竞争消费(轮询分发)

​ 默认采用公平分发(Fair Dispatch)

​ 适用场景:任务分发、负载均衡

​ 3.发布/订阅模式(Publish/Subscribe)

​ 架构:

复制代码
生产者 → 交换机(Fanout) → 队列1 → 消费者1
                      → 队列2 → 消费者2

​ 特点:

​ 使用 Fanout 类型交换机

​ 消息广播到所有绑定队列

​ 每个消费者获取全量消息

​ 适用场景:日志广播、事件通知

​ 4.路由模式(Routing)

​ 架构:

复制代码
生产者 → 交换机(Direct) → 队列1(路由键error)
                      → 队列2(路由键info/warning)

​ 特点:

​ 使用 Direct 类型交换机

​ 基于路由键(routing key)精确匹配

​ 支持多重条件绑定

​ 适用场景:分类消息处理(如日志级别区分)

​ 5.主题模式(Topics)

​ 架构:

复制代码
生产者 → 交换机(Topic) → 队列1(user.*)
                     → 队列2(*.order)

​ 特点:

​ 使用 Topic 类型交换机

​ 支持通配符匹配路由键:* 匹配一个单词,# 匹配零或多个单词

​ 灵活性最高的路由方式

​ 适用场景:多维度消息过滤(如地理位置、设备类型)

​ 6.RPC模式(Remote Procedure Call)

​ 架构:

复制代码
客户端 → 请求队列 → 服务端
   ↑               ↓
   └── 回调队列 ←──┘

​ 特点:

​ 实现远程过程调用

​ 需要关联ID(corrlationId)匹配请求响应

​ 使用临时回调队列

4.RabbitMQ的体系结构有哪些内容?

RabbitMQ 是一个基于 AMQP(Advanced Message Queuing Protocol)的开源消息代理系统。

​ 1.核心组件

组件 说明
Producer 消息生产者,通过Exchange发送消息
Consumer 消息消费者,从Queue接收消息
Exchange 消息路由中心,决定消息如何分发到Queue
Queue 存储消息的缓冲区,FIFO结构
Binding Exchange与Queue之间的绑定规则
Connection TCP长连接(建议复用)
Channel 虚拟连接(复用TCP连接的轻量级通道)
Virtual Host 虚拟隔离环境(类似MySQL的database)

​ 2.Exchange 类型与路由机制

类型 路由规则 典型应用场景
Direct 精确匹配routing_key 点对点精准投递
Fanout 广播到所有绑定队列(忽略routing_key 发布/订阅模式
Topic 通配符匹配(*匹配单个词,#匹配多个词) 多维度消息分类
Headers 根据消息headers属性匹配(忽略routing_key 复杂条件路由

​ 3.消息生命周期

​ 1.生产者发布消息 -> Exchange

​ 2.Exchange根据类型和Binding规则 -> 路由到Queue

​ 3.Queue存储消息(持久化消息会写入磁盘)

​ 4.消费者订阅Queue -> 获取消息

​ 5.消息确认(ACK) -> 从Queue删除

5.什么场景适合使用RabbitMQ?

RabbitMQ 作为一款功能强大的消息中间件,适合以下核心场景:

​ 1.应用解耦:

​ 当系统模块间需要松耦合通信时,通过消息队列隔离生产者和消费者。

​ 优势:

​ 库存服务升级不影响订单服务。

​ 避免直接HTTP调用导致的级联故障

java 复制代码
// 订单服务(生产者)
orderService.createOrder() {
    saveOrderToDB();
    rabbitTemplate.convertAndSend("order.created", orderId); // 发送消息
}

// 库存服务(消费者)
@RabbitListener(queues = "order.created")
public void deductStock(String orderId) {
    inventoryService.deduct(orderId);
}

​ 2.异步处理

​ 将非核心流程异步化,提升主流程响应速度。

​ 典型案例:

​ 用户注册后异步发送邮件/短信

​ 支付成功后的积分计算

​ 3.流量削峰

​ 场景描述:应对突发流量,保护下游系统不被压垮。

​ 典型案例:

​ 秒杀系统:将瞬间请求转为队列消费。

​ 日志收集:高峰期的日志写入

​ 实现方案:

java 复制代码
// 配置队列最大积压量
@Bean
public Queue spikeQueue() {
    return new Queue("spike.queue", true, false, false, 
        Map.of("x-max-length", 10000)); // 最大1万条消息
}

​ 4.定时/延迟任务

​ 场景描述:替代轮询数据库,实现精准延时控制。

​ 典型案例:

​ 订单30分钟未支付自动取消。

​ 预约提醒通知

java 复制代码
// 发送延迟消息(RabbitMQ插件实现)
rabbitTemplate.convertAndSend(
    "delayed.exchange",
    "order.cancel",
    orderId,
    message -> {
        message.getMessageProperties().setDelay(30 * 60 * 1000); // 30分钟
        return message;
    });

​ 5.分布式事务最终一致性

​ 场景描述:替代刚性事务,通过消息队列实现柔性事务。

​ 典型案例:跨系统数据一致性(如订单+库存+积分)

​ Saga模式实现:

​ 1.订单服务创建订单(本地事务)

​ 2.发送OrderCreated 事件

​ 3.库存服务扣减库存(若失败则发送补偿事件)

6.RabbitMQ的运行流程

RabbitMQ的运行流程设计多个组件之间的协作,包括生产者、交换机、队列和消费者。

​ 1.建立连接(Connection)

​ 描述:客户端(生产者或消费者)与 RabbitMQ 服务器建立 TCP 连接。

​ 步骤:

​ 客户端通过指定 RabbitMQ 服务器的地址和端口,建立 TCP 连接。

​ 客户端进行身份验证(如用户名和密码)

​ 连接成功后,客户端可以创建信道(Channel)进行后续操作。

​ 2.创建信道(Channel)

​ 描述:在连接的基础上,客户端创建虚拟的信道用于执行具体的操作。

​ 步骤:

​ 客户端在已建立的连接上创建信道。

​ 信道是轻量级的,可以在一个连接上创建多个信道,支持多线程操作。

​ 3.声明交换机(Declare Exchange)

​ 描述:生产者或消费者声明交换机,指定交换机的类型和属性。

​ 步骤:

​ 客户端通过信道声明交换机,指定交换机的名称、类型(如 Direct、Fanout、Topic)和属性。

​ 如果交换机已存在且属性匹配,则直接使用;否则创建新的交换机。

​ 4.声明队列(Declare Queue)

​ 描述:消费者声明队列,指定队列的名称和属性。

​ 步骤:

​ 客户端通过信道声明队列,指定队列的名称和属性(如持久化、排他性、自动删除)。

​ 如果队列已存在且属性匹配,则直接使用;否则创建新的队列。

​ 5.绑定队列到交换机(Bind Queue to Exchange)

​ 描述:消费者将队列绑定到交换机,并指定绑定规则(如路由键)

​ 步骤:

​ 客户端通过信道将队列绑定到交换机,指定绑定规则(如路由键或模式匹配)。

​ 绑定后,交换机根据规则将消息路由到队列。

​ 6.发送消息(Publish Message)

​ 描述:生产者将消息发送到交换机

​ 步骤:

​ 生产者通过信道将消息发送到指定的交换机。

​ 消息包含消息体和属性(如路由键、持久化标志)。

​ 交换机根据路由规则将消息路由到一个或多个队列。

​ 7.接收消息(Consume Message)

​ 描述:消费者从队列种获取消息并进行处理。

​ 步骤:

​ 消费者通过信道订阅队列,开始接收消息。

​ RabbitMQ 将队列中的消息推送给消费者。

​ 消费者处理消息内容,并根据处理结果发送确认(ACK)或拒绝(NACK)。

​ 8.确认消息(Acknowledage Message)

​ 描述:消费者处理完消息后,向RabbitMQ发送确认,表示消息已成功处理。

​ 步骤:

​ 消费者处理完消息后,通过信道发送确认(ACK)给 RabbitMQ。

​ RabbitMQ 收到确认后,将消息从队列中移除。

​ 如果消费者发送拒绝(NACK)或未发送确认,RabbitMQ 可能会将消息重新入队或丢弃。

​ 9.关闭信道和连接(Close Channel and Connection

​ 描述:客户端在完成操作后,关闭信道和连接。

​ 步骤:

​ 客户端关闭信道,释放资源。

​ 客户端关闭连接,断开与 RabbitMQ 服务器的 TCP 连接。

7.RabbitMQ的消息可靠性如何保证?

RabbitMQ 通过多层次的机制确保消息可靠性传递。

1.生产者确认机制(Publisher Confirm)

​ 作用:确保消息从生产者到达 Broker

​ 三种确认模式:

模式 触发时机 性能 可靠性
单条同步确认 每发一条等待Broker确认 最高
批量同步确认 累积多条后统一确认
异步确认 通过回调通知

2.消息持久化

​ 注意事项:

​ 进设置消息持久化无效,必须同时持久化队列

​ 持久化会降低性能(约10倍吞吐量下降)

3.消费者手动ACk

操作 命令 效果
ACK basicAck 确认处理成功,消息从队列删除
NACK basicNack 处理失败,可设置是否重新入队

4.高可用架构

​ (1) 镜像队列(Mirrored Queues)

​ 特性:

​ 队列数据跨节点复制,主节点故障自动切换,需配合集群使用。

​ (2) 集群部署

​ 数据分布:元数据全节点同步,队列数据仅存于创建节点(除非配置镜像队列)。

5.死信队列(DLX)

​ 触发条件:

​ 消息被消费者 NACK 且不重新入队。

​ 消息 TTL 过期。

​ 队列到达最大长度。

8.RabbitMQ的消息幂等性如何保证?

在分布式系统中,消息重复消费是常见问题,RabbitMQ 本身不提供内置的幂等性保障,需要通过业务逻辑或技术手段实现。

1.幂等性核心原则

​ 定义:同一消息被消费多次与消费一次的效果相同

​ 常见需幂等场景:订单支付处理,库存扣减,账户余额变更,数据同步操作。

2.消息唯一标识(Message ID)

​ 描述:为每条消息分配一个唯一标识(如 UUID),消费者在处理消息时记录已处理的消息 ID,避免重复处理。

​ 实现:

​ 生产者为每条消息设置唯一 ID。

​ 消费者在处理消息时检查消息 ID 是否已经处理过。

3.数据库唯一约束

​ 描述:利用数据库的唯一约束(如唯一索引)来防止重复处理。

​ 实现:

​ 在数据库中为消息 ID 或 业务唯一标识(如订单号)创建唯一索引。

​ 消费者在处理消息时,将消息 ID 或业务唯一标识插入数据库。如果插入失败(唯一约束冲突),则说明消息已处理过。

4.乐观锁(Optimistic Locking)

​ 描述:在业务逻辑中使用乐观锁机制,确保同一笔业务操作不会被重复执行。

​ 实现:

​ 在数据库中为业务数据添加版本号字段。

​ 消费者在处理消息时,检查数据的版本号是否匹配。如果版本号不匹配,则说明数据已被更新,忽略当前消息。

5.幂等性设计

​ 描述:从业务逻辑层面设计幂等性操作,确保多次执行同一操作不会产生副作用。

​ 实现:

​ 查询操作:查询操作天然幂等,多次查询不会更改系统作用。

​ 更新操作:设计更新为覆盖式更新,而不是累加式更新。

​ 插入操作:使用唯一约束来避免重复插入。

6.Redis 分布式锁

​ 描述:利用 Redis 的分布式锁机制,确保同一笔业务操作在同一时间只能被一个消费者处理。

​ 实现:消费者在处理消息前,尝试获取 Redis 锁。

7.RabbitMQ 的消费者确认机制

​ 描述:通过 RabbitMQ 的消费者确认机制 (ACK/NACK),确保消息被正确处理后才从队列中移除,避免消息重复投递。

​ 实现:消费者在处理完消息后手动发送 ACK。

9.RabbitMQ如何实现延迟队列?

RabbitMQ 本身没有直接的延迟队列功能,但可以通过其他方法实现延迟消息投递。

1.RabbitMQ 官方插件(推荐)

​ 使用 rabbitmq_delayed_message_exchange 插件

​ 优点:原生支持,可靠性高。 消息直接进入延迟Exchange,无需额外队列。

​ 限制:需安装插件(生产环境需测试兼容性)

2.TTL + 死信队列(传统方案)

​ 实现原理:

​ 1.消息设置 TTL (Time To Live)

​ 2.过期后通过死信 Exchange 路由到目标队列

​ 优点:无需插件,兼容所有 RabbitMQ 版本。

​ 缺点:消息堆积问题,队列头部消息会阻塞后续消息过期。不精确延迟,只有队列头部消息的TTL会被检查。

3.外部调度 + 定时任务

​ 实现架构:

复制代码
[数据库] ← 定时任务 → [RabbitMQ]

​ 实现步骤:

​ 消息存入MySQL并记录投递时间,定时任务扫描到期消息,投递到RabbitMQ实际队列。

​ 优点:支持任意延迟时间(天/月级),可结合业务状态灵活控制。

​ 缺点:依赖外部存储,定时任务有处理延迟。

方案 延迟精度 最大延迟 复杂度 适用场景
官方插件 高(毫秒级) 数小时 秒级/分钟级延迟
TTL+死信队列 低(队列阻塞) 数天 简单延迟需求
外部调度 依赖扫描间隔 无限制 长延迟(小时/天级)
相关推荐
战族狼魂3 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
xyliiiiiL4 小时前
ZGC初步了解
java·jvm·算法
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
hycccccch4 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
bobz9655 小时前
k8s 怎么提供虚拟机更好
后端
zhuyixiangyyds5 小时前
day21和day22学习Pandas库
笔记·学习·pandas
bobz9655 小时前
nova compute 如何创建 ovs 端口
后端
天天向上杰5 小时前
面基JavaEE银行金融业务逻辑层处理金融数据类型BigDecimal
java·bigdecimal
请来次降维打击!!!6 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
用键盘当武器的秋刀鱼6 小时前
springBoot统一响应类型3.5.1版本
java·spring boot·后端