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 自动配数据源)。 |
无需手动配置 DataSource 、EntityManager 等。 |
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) | 可共享 request 和 response 对象 |
性能 | 较慢(多一次网络往返) | 较快(无额外网络开销) |
适用场景 | 跨应用跳转、防止表单重复提交 | 同一应用内的页面跳转(如 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+死信队列 | 低(队列阻塞) | 数天 | 中 | 简单延迟需求 |
外部调度 | 依赖扫描间隔 | 无限制 | 高 | 长延迟(小时/天级) |