Java 框架面试题(MyBatis / Spring / SpringMVC / SpringBoot / SpringCloud)

3 阅读34分钟

Java 框架面试题(MyBatis / Spring / SpringMVC / SpringBoot / SpringCloud)


一、MyBatis

1. MyBatis 的组件和原理

问题: MyBatis 的核心组件有哪些?它的工作原理是什么?

答案:

MyBatis 的核心组件包括:

组件作用
SqlSessionFactory创建 SqlSession 的工厂,通过配置文件或 Configuration 对象构建
SqlSession面向用户的 API 入口,负责发送 SQL、获取映射结果、管理事务
ConfigurationMyBatis 的全局配置信息,包含映射器、插件、缓存等所有配置
Mapper 接口数据访问接口,通过动态代理生成实现类
MappedStatement一条 SQL 语句的完整封装,包括 SQL、入参映射、结果映射
ExecutorSQL 执行器(Simple/Reuse/Batch),负责实际执行 SQL 和缓存管理
StatementHandler负责操作 Statement 对象,设置参数、执行 SQL
ParameterHandler负责将 Java 参数设置为 PreparedStatement 的入参
ResultSetHandler负责将 ResultSet 结果集映射为 Java 对象
TypeHandlerJava 类型与 JDBC 类型之间的转换器
Plugin / Interceptor插件机制,可拦截四大对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)的方法调用

工作原理:

  1. 读取 mybatis-config.xmlapplication.yml 配置,构建 Configuration 对象
  2. SqlSessionFactoryBuilder 创建 SqlSessionFactory
  3. 通过 SqlSessionFactory 创建 SqlSession
  4. 获取 Mapper 接口的代理对象(JDK 动态代理)
  5. 执行 SQL 时,代理对象调用 Executor
  6. Executor 依次调用 StatementHandlerParameterHandler → 执行 SQL → ResultSetHandler 映射结果
  7. 整个流程中可通过 插件(Interceptor) 对四大对象进行拦截增强

2. MyBatis 的多级缓存

问题: MyBatis 的多级缓存是怎样的?二级缓存和一级缓存有什么区别?

答案:

MyBatis 内置了两级缓存机制:

一级缓存(Local Cache / Session 级缓存):

  • 默认开启,作用域为 SqlSession
  • 在同一个 SqlSession 中相同的查询会直接走缓存
  • 增删改操作会清空该 SqlSession 的一级缓存
  • 不同的 SqlSession 之间缓存互不共享

二级缓存(Namespace 级缓存):

  • 默认关闭,需要在 mapper XML 中配置 <cache/> 或在接口上使用 @CacheNamespace 注解
  • 作用域为 Mapper 的 Namespace,跨 SqlSession 共享
  • 数据查询时,SqlSession 关闭或提交后数据才会写入二级缓存
  • 增删改操作会清空该 Namespace 下的所有二级缓存
  • 实体类必须实现 Serializable 接口(因为二级缓存可能存储到磁盘)

注意: MyBatis 没有"三级缓存"的概念。两级的查询顺序:二级缓存 → 一级缓存 → 数据库。


3. Mybatis 和 MybatisPlus 的区别

问题: MyBatis 和 MyBatisPlus 有什么区别?

答案:

对比项MyBatisMyBatis-Plus(MP)
定位ORM 框架MyBatis 增强工具
CRUD需手写 XML 或注解 SQL内置通用 Mapper/Service,零 SQL 实现 CRUD
条件构造器手写 SQL 拼接条件提供 Wrapper(如 QueryWrapperLambdaQueryWrapper)链式查询
分页需手动写分页 SQL 或插件内置 PaginationInterceptor 分页插件
代码生成内置代码生成器(AutoGenerator),可生成 Entity/Mapper/Service/Controller
逻辑删除需自行实现内置 @TableLogic 注解支持
自动填充内置 MetaObjectHandler,创建时间/更新时间自动填充
乐观锁内置 @Version 注解 + OptimisticLockerPlugin
性能性能分析插件、SQL 执行分析等
兼容性完全兼容 MyBatis,原有代码无需修改

总结: MyBatis-Plus 只做增强不做改变,在 MyBatis 基础上提供了大量开箱即用的功能,减少重复的 CRUD 编写。


4. MyBatis 里 $# 有什么区别?

问题: MyBatis 中 ${}#{} 有什么区别?

答案:

对比项#{}${}
处理方式预编译处理,使用 PreparedStatement 的参数占位符 ?字符串拼接,直接替换到 SQL 中
安全性✅ 防 SQL 注入❌ 存在 SQL 注入风险
参数类型适用于所有类型的参数适用于表名、列名、order by 等不能预编译的场景
底层实现PreparedStatement.setXXX()直接拼接字符串
单引号自动加单引号(字符串类型)不会自动加单引号
<!-- #{} 示例:预编译参数 -->
SELECT * FROM user WHERE id = #{id}
-- 实际执行:SELECT * FROM user WHERE id = ?

-- ${} 示例:字符串替换(注意 SQL 注入风险)
SELECT * FROM user ORDER BY ${columnName}
-- 实际执行:SELECT * FROM user ORDER BY name

最佳实践: 能用 #{} 就用 #{},只有在需要动态传入表名、列名、排序字段时才用 ${}


5. MyBatis 里 ResultMap 和 ResultType 有什么区别?

问题: MyBatis 中 resultMapresultType 有什么区别?

答案:

对比项resultTyperesultMap
映射方式自动映射(字段名与属性名一致)手动定义映射规则
适用场景数据库列名与 Java 属性名一致(或开启驼峰映射)列名与属性名不一致、需要复杂映射
嵌套映射不支持复杂嵌套支持一对一(association)、一对多(collection)嵌套映射
灵活性简单方便高度灵活可控
<!-- resultType:字段名与属性名一致时直接使用 -->
<select id="getUserById" resultType="com.example.User">
    SELECT id, name, age FROM user WHERE id = #{id}
</select>

<!-- resultMap:自定义映射规则 -->
<resultMap id="userResultMap" type="com.example.User">
    <id property="userId" column="id"/>
    <result property="userName" column="name"/>
    <result property="userAge" column="age"/>
    <!-- 一对一关联 -->
    <association property="dept" javaType="com.example.Dept">
        <id property="deptId" column="dept_id"/>
        <result property="deptName" column="dept_name"/>
    </association>
</resultMap>

6. MyBatis 里动态标签有哪些?

问题: MyBatis 的动态 SQL 标签有哪些?

答案:

标签作用
<if>条件判断,满足条件时拼接 SQL 片段
<choose>/<when>/<otherwise>类似 Java 的 switch-case,多条件分支选择
<where>智能处理 WHERE 关键字,自动去除多余的前缀 AND/OR
<set>智能处理 UPDATE 的 SET 子句,自动去除多余的逗号
<trim>通用前后缀处理,可自定义前缀、后缀及去除内容
<foreach>遍历集合,用于 IN 查询、批量插入等场景
<sql>/<include>定义可复用的 SQL 片段,通过 include 引入
<bind>创建变量并绑定到上下文中
<!-- if 标签示例 -->
<select id="queryUser" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null and name != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </where>
</select>

<!-- foreach 标签示例 -->
<select id="getByIds" resultType="User">
    SELECT * FROM user WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

7. 为什么要使用 MyBatisPlus?

问题: 为什么要使用 MyBatisPlus?

答案:

  1. 简化 CRUD:通用 Mapper/Service 封装了单表 CRUD,无需编写 XML,极大减少重复代码
  2. 条件构造器Wrapper 提供链式编程风格,代替手写 SQL 条件拼接,代码更优雅
  3. 内置分页PaginationInnerInterceptor 一键实现物理分页,无需手写分页 SQL
  4. 代码生成器:AutoGenerator 根据数据库表自动生成 Entity、Mapper、Service、Controller 代码
  5. 逻辑删除@TableLogic 注解实现逻辑删除,查询时自动过滤已删除数据
  6. 自动填充:创建时间、修改时间等公共字段自动填充,避免重复编码
  7. 乐观锁@Version 注解轻松实现并发控制
  8. 多租户插件:通过 TenantLineInnerInterceptor 实现多租户数据隔离
  9. 完全兼容:在 MyBatis 基础上增强,原有 MyBatis 代码和配置无需任何修改
  10. Lambda 支持LambdaQueryWrapper 避免字段名硬编码,编译期检查,减少错误

二、Spring

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

问题: Spring 中 Bean 的注入方式有几种?

答案:

注入方式说明示例
构造器注入(推荐)通过构造函数注入,依赖不可变,适合必需依赖@Autowired 放在构造器上
Setter 注入通过 setter 方法注入,适合可选依赖@Autowired 放在 setter 方法上
字段注入直接在属性上注解,使用最简单但 Spring 不推荐@Autowired 放在字段上
@Bean 方法参数注入@Bean 方法中通过参数注入@Bean 方法参数列表自动注入
// 1. 构造器注入(Spring 推荐)
@Service
public class UserService {
    private final UserDao userDao;
    @Autowired
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}

// 2. Setter 注入
@Service
public class UserService {
    private UserDao userDao;
    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

// 3. 字段注入(简洁但不推荐)
@Service
public class UserService {
    @Autowired
    private UserDao userDao;
}

最佳实践: 优先使用构造器注入,保证依赖不可变且方便测试。


9. Spring 中 Bean 的作用域有几种?

问题: Spring 中 Bean 的作用域有几种?

答案:

作用域说明
singleton(默认)整个 IoC 容器中只有一个实例
prototype每次请求都创建一个新实例
request每个 HTTP 请求创建一个实例(仅 Web 应用)
session每个 HTTP Session 创建一个实例(仅 Web 应用)
application每个 ServletContext 创建一个实例(仅 Web 应用)
websocket每个 WebSocket 创建一个实例(仅 Web 应用)

注意: singleton 作用域的 Bean 不会在容器启动时就创建(默认延迟加载),除非设置 lazy-init=false 或使用了 @DependsOn


10. Spring 常用注解有哪些?

问题: Spring 常用注解有哪些?

答案:

Bean 声明注解:

注解作用
@Component通用组件,标注为一个 Spring Bean
@ControllerWeb 层控制器
@Service业务逻辑层
@Repository数据访问层(DAO),支持异常转换
@Configuration配置类,相当于 XML 配置文件
@Bean@Configuration 类中,方法返回的对象注册为 Bean

依赖注入注解:

注解作用
@AutowiredSpring 提供的自动注入(按类型)
@ResourceJSR-250 提供(默认按名称,再按类型)
@Value注入配置文件中的属性值

配置注解:

注解作用
@Configuration标记配置类
@ComponentScan组件扫描路径
@PropertySource加载外部配置文件
@Import导入其他配置类
@Profile根据环境激活不同的配置
@Conditional条件化装配 Bean

其他常用注解:

注解作用
@Lazy延迟加载
@Scope指定 Bean 作用域
@Primary当多个同类型 Bean 存在时,指定优先注入
@PostConstructBean 初始化后执行的方法
@PreDestroyBean 销毁前执行的方法
@Transactional声明式事务

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

问题: @Component@Bean 注解有什么区别?

答案:

对比项@Component@Bean
使用位置标注在标注在方法
适用场景自己编写的类(可以修改源码)第三方库的类(无法修改源码)
控制方式Spring 自动扫描和实例化开发者手动创建和配置 Bean
自定义程度较低,类本身决定较高,可以在方法中进行复杂的初始化逻辑
配合使用通常配合 @ComponentScan通常放在 @Configuration 类中
// @Component:用于自己写的类
@Component
public class MyService { ... }

// @Bean:用于第三方库的类或需要复杂初始化的场景
@Configuration
public class AppConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public DataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        ds.setUsername("root");
        return ds;
    }
}

12. AOP 的实现原理?

问题: AOP 的实现原理是什么?

答案:

AOP(面向切面编程)的底层实现主要依赖动态代理

1. JDK 动态代理(基于接口):

  • 目标类必须实现接口
  • 使用 java.lang.reflect.Proxy 创建代理对象
  • 通过 InvocationHandlerinvoke() 方法拦截方法调用
  • Spring 默认优先使用 JDK 动态代理

2. CGLIB 动态代理(基于继承):

  • 目标类不需要实现接口
  • 通过生成目标类的子类来实现代理
  • 使用 Enhancer 类和 MethodInterceptor 接口
  • Spring Boot 2.x 默认使用 CGLIB 代理(spring.aop.proxy-target-class=true

AOP 核心概念:

概念说明
Aspect(切面)横切关注点的模块化,如日志、事务等
JoinPoint(连接点)程序执行中的一个点,如方法调用
Pointcut(切入点)匹配连接点的表达式,决定哪些方法被增强
Advice(通知)在切入点执行的增强逻辑
Target(目标对象)被代理的原始对象
Weaving(织入)将切面应用到目标对象的过程,Spring 采用运行时织入

13. AOP 主要用在哪些场景中?

问题: AOP 主要用在哪些场景?

答案:

场景说明
日志记录统一记录方法调用的入参、出参、耗时
事务管理@Transactional 的底层就是 AOP 实现
权限校验在方法执行前校验用户权限
性能监控统计方法执行时间,监控慢接口
异常处理统一捕获和处理异常
缓存方法级缓存,查询时先走缓存
限流/熔断接口级别的限流和熔断
数据脱敏返回结果中对敏感信息进行脱敏处理
操作审计记录关键操作的审计日志

14. 项目中 AOP 的使用

问题: AOP 在你们项目中哪里有使用到?怎么用的?

答案:

(以下为常见项目实践示例,可根据实际项目替换)

场景:操作日志记录

// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperLog {
    String value() default "";
    OperType type() default OperType.OTHER;
}

// AOP 切面
@Aspect
@Component
@Slf4j
public class OperLogAspect {

    @Pointcut("@annotation(com.example.annotation.OperLog)")
    public void operLogPointcut() {}

    @Around("operLogPointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 记录开始时间
        long startTime = System.currentTimeMillis();
        
        // 获取方法信息
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        OperLog operLog = method.getAnnotation(OperLog.class);
        
        // 记录入参
        String className = point.getTarget().getClass().getSimpleName();
        String methodName = method.getName();
        Object[] args = point.getArgs();
        log.info("[{}]{}.{} 开始执行,参数:{}", 
                 operLog.value(), className, methodName, args);
        
        // 执行方法
        Object result = point.proceed();
        
        // 记录出参和耗时
        long cost = System.currentTimeMillis() - startTime;
        log.info("[{}]{}.{} 执行完成,耗时:{}ms,结果:{}", 
                 operLog.value(), className, methodName, cost, result);
        
        // 异步保存到数据库
        saveLog(className, methodName, operLog, args, result, cost);
        
        return result;
    }
}

典型使用场景:

  • 通过自定义 @OperLog 注解标注需要记录日志的 Controller 方法
  • AOP 自动记录操作人、操作模块、请求参数、响应结果、执行耗时
  • 日志异步写入数据库,不影响主流程性能

15. Spring 的三级缓存

问题: Spring 的三级缓存是什么?

答案:

Spring 使用三级缓存解决 Bean 的循环依赖问题:

缓存级别名称存储内容作用
一级缓存singletonObjects完整的 Bean 实例(已初始化完毕)存放完全创建好的单例 Bean
二级缓存earlySingletonObjects早期 Bean 引用(已实例化,未初始化)保存提前暴露的 Bean 引用,解决循环依赖
三级缓存singletonFactoriesBean 工厂(ObjectFactory生成早期 Bean 引用的工厂,支持 AOP 代理

获取 Bean 的顺序:

一级缓存 singletonObjects → 二级缓存 earlySingletonObjects → 三级缓存 singletonFactories

创建 Bean 的流程:

  1. 实例化 Bean(调用构造方法)
  2. 三级缓存中放入 ObjectFactory(如果需要 AOP,工厂会返回代理对象)
  3. 属性注入(发现循环依赖时,从三级缓存获取早期引用)
  4. 获取早期引用后放入二级缓存,删除三级缓存
  5. 初始化 Bean(调用 @PostConstructInitializingBean.afterPropertiesSet()
  6. 初始化完成后放入一级缓存,删除二级缓存

16. Spring 如何解决循环依赖

问题: Spring 如何解决循环依赖?

答案:

解决范围: Spring 只能解决单例模式Setter 注入/字段注入 的循环依赖,无法解决构造器注入的循环依赖。

解决原理(以 A → B → A 为例):

1. 创建 A:实例化 A → 将 A 的工厂放入三级缓存
2. A 属性注入 B → 发现需要 B
3. 创建 B:实例化 B → 将 B 的工厂放入三级缓存
4. B 属性注入 A → 从三级缓存获取 A 的工厂 → 生成 A 的早期引用(代理对象)
5. 将 A 的早期引用放入二级缓存,删除三级缓存
6. B 完成初始化 → B 放入一级缓存
7. A 获取到 BA 完成初始化 → A 放入一级缓存

无法解决的情况:

  • 构造器注入循环依赖:实例化时就互相依赖,无法提前暴露引用,直接抛出 BeanCurrentlyInCreationException
  • 原型(prototype)作用域的循环依赖:Spring 不缓存原型 Bean,无法解决

解决方案(构造器循环依赖):

  • 使用 @Lazy 注解延迟加载
  • 重构代码,消除循环依赖

17. Spring 事务如何与线程绑定

问题: Spring 事务是如何与线程绑定的?

答案:

Spring 事务通过 ThreadLocal 实现与线程绑定:

  1. TransactionSynchronizationManager 是核心类,内部使用多个 ThreadLocal 变量保存事务相关资源:

    • resources:保存数据源连接(DataSource → Connection)
    • transactionSynchronizations:保存事务同步回调
    • currentTransactionName:当前事务名称
    • currentTransactionReadOnly:当前事务是否只读
    • currentTransactionIsolationLevel:当前事务隔离级别
  2. 绑定流程:

    开启事务 → 从数据源获取 Connection → 绑定到当前线程的 ThreadLocal
    执行 SQL → 从 ThreadLocal 获取 Connection → 使用同一个 Connection
    提交/回滚事务 → 从 ThreadLocal 获取 Connection → 提交/回滚 → 解除绑定
    
  3. 关键点:

    • 同一线程中的所有数据库操作共用同一个 Connection,保证事务一致性
    • 不同线程各自独立的事务上下文,互不影响
    • 如果需要跨线程传播事务,需要手动传递(如使用 InheritableThreadLocal 或自定义传播机制)

18. Spring 的传播行为

问题: Spring 的事务传播行为有哪些?

答案:

传播行为说明
REQUIRED(默认)有事务就加入,没有就新建
REQUIRES_NEW总是新建事务,挂起当前事务
NESTED有事务就嵌套(savepoint),没有就新建
SUPPORTS有事务就加入,没有就以非事务方式执行
NOT_SUPPORTED以非事务方式执行,挂起当前事务
MANDATORY必须在事务中执行,否则抛出异常
NEVER必须不在事务中执行,否则抛出异常
// 外层方法
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // ...
    methodB(); // B 加入 A 的事务
}

// 内层方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // B 总是新建独立事务,A 的事务被挂起
}

常见使用场景:

  • 日志记录:REQUIRES_NEWNOT_SUPPORTED,保证日志不受业务事务回滚影响
  • 批量操作:NESTED,部分失败可以回滚到 savepoint

三、SpringMVC

19. SpringMVC 执行流程(工作原理)

问题: SpringMVC 的执行流程是什么?

答案:

客户端请求
   ↓
1. DispatcherServlet(前端控制器)
   ↓
2. HandlerMapping(处理器映射器)→ 根据 URL 找到对应的 Handler
   ↓
3. HandlerAdapter(处理器适配器)→ 执行 Handler(Controller 方法)
   ↓
4. Controller 处理业务逻辑,返回 ModelAndView(或数据)
   ↓
5. (如果有拦截器)执行拦截器的 postHandle
   ↓
6. ViewResolver(视图解析器)→ 解析视图名称为具体 View
   ↓
7. View 渲染视图(返回 JSON 则跳过此步)
   ↓
8. DispatcherServlet 返回响应给客户端

核心组件:

组件作用
DispatcherServlet前端控制器,统一接收和分发请求
HandlerMapping根据 URL 查找 Handler
HandlerAdapter执行 Handler,统一适配不同类型的 Handler
Handler具体的 Controller 方法
ViewResolver解析逻辑视图名为具体视图
View负责页面渲染

20. SpringMVC 默认的 JSON 序列化技术是哪个?

问题: SpringMVC 默认的 JSON 序列化技术是哪个?

答案:

  • SpringMVC 默认使用 Jackson 作为 JSON 序列化/反序列化库
  • 核心类:com.fasterxml.jackson.databind.ObjectMapper
  • 当 classpath 中存在 Jackson 的 jar 包时,Spring Boot 会自动配置 MappingJackson2HttpMessageConverter
  • 如果想替换为其他库:
    • Fastjson:配置 FastJsonHttpMessageConverter
    • Gson:配置 GsonHttpMessageConverter

21. RequestBody 和 ResponseBody 区别

问题: @RequestBody@ResponseBody 有什么区别?

答案:

对比项@RequestBody@ResponseBody
作用位置方法参数方法返回值
方向请求 → Controller(接收请求体)Controller → 响应(写入响应体)
功能将请求体(JSON/XML)反序列化为 Java 对象将 Java 对象序列化为 JSON/XML 写入响应
Content-Type需要请求头 Content-Type: application/json响应头 Content-Type: application/json
// @RequestBody:接收前端传来的 JSON 数据
@PostMapping("/user")
public Result createUser(@RequestBody UserDTO userDTO) {
    // userDTO 从请求体中反序列化得到
    userService.save(userDTO);
    return Result.success();
}

// @ResponseBody:返回值直接写入响应体
@GetMapping("/user/{id}")
@ResponseBody
public User getUser(@PathVariable Long id) {
    // 返回的 User 对象会被序列化为 JSON
    return userService.getById(id);
}

// @RestController = @Controller + @ResponseBody

22. SpringAOP 的通知类型有哪些

问题: SpringAOP 的通知类型有哪些?

答案:

通知类型注解说明
前置通知@Before在目标方法执行前执行
后置通知@AfterReturning在目标方法正常返回后执行(有返回值)
异常通知@AfterThrowing在目标方法抛出异常后执行
最终通知@After在目标方法执行后执行(无论正常返回还是异常)
环绕通知@Around包裹整个方法,可控制是否执行、何时执行、执行几次
@Aspect
@Component
public class LogAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint jp) {
        log.info("前置通知:{}", jp.getSignature().getName());
    }

    @Around("execution(* com.example.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("环绕通知-前");
        Object result = pjp.proceed(); // 执行目标方法
        log.info("环绕通知-后");
        return result;
    }

    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void afterReturning(JoinPoint jp, Object result) {
        log.info("后置通知,返回值:{}", result);
    }

    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint jp, Exception ex) {
        log.error("异常通知:{}", ex.getMessage());
    }
}

23. @Controller 和 @RestController 有何区别

问题: @Controller@RestController 有什么区别?

答案:

对比项@Controller@RestController
组成@Controller@Controller + @ResponseBody
返回值处理默认走视图解析器(JSP/Thymeleaf),返回页面直接将返回值序列化为 JSON 写入响应体
适用场景返回视图页面(前后端不分离)返回 JSON 数据(前后端分离 / RESTful API)
// @Controller:返回页面
@Controller
public class PageController {
    @GetMapping("/index")
    public String index() {
        return "index"; // 解析为 index.html 或 index.jsp
    }
}

// @RestController:返回 JSON
@RestController
@RequestMapping("/api")
public class ApiController {
    @GetMapping("/user")
    public User getUser() {
        return new User(1L, "张三"); // 序列化为 {"id":1,"name":"张三"}
    }
}

24. @Autowired 和 @Resource 的区别

问题: @Autowired@Resource 有什么区别?

答案:

对比项@Autowired@Resource
来源Spring 提供JSR-250(Java 标准)
匹配方式默认按类型(byType)匹配默认按名称(byName)匹配
名称匹配配合 @Qualifier 指定名称通过 name 属性指定
依赖对象必须存在(required=true),否则启动报错查找不到时不会立即报错(可通过 nametype 调整)
适用性Spring 生态专用标准注解,可切换为其他 IoC 容器
// @Autowired + @Qualifier
@Autowired
@Qualifier("myDataSource")
private DataSource dataSource;

// @Resource 按名称注入
@Resource(name = "myDataSource")
private DataSource dataSource;

// @Resource 按类型注入
@Resource(type = DataSource.class)
private DataSource dataSource;

25. SpringMVC 常用注解有哪些?

问题: SpringMVC 常用注解有哪些?

答案:

注解作用
@RequestMapping映射 HTTP 请求到 Controller 方法
@GetMapping映射 GET 请求
@PostMapping映射 POST 请求
@PutMapping映射 PUT 请求
@DeleteMapping映射 DELETE 请求
@PatchMapping映射 PATCH 请求
@RestController@Controller + @ResponseBody,返回 JSON
@RequestBody接收请求体中的 JSON 数据
@ResponseBody将返回值写入响应体
@PathVariable获取 URL 路径中的变量
@RequestParam获取请求参数(URL 参数或表单参数)
@RequestHeader获取请求头信息
@CookieValue获取 Cookie 值
@SessionAttribute获取 Session 属性

26. SpringMVC 中接收参数的注解有哪些?

问题: SpringMVC 中接收参数的注解有哪些?

答案:

注解使用位置说明示例
@RequestParam方法参数获取 URL 查询参数或表单参数?name=张三
@PathVariable方法参数获取 URL 路径中的变量/user/{id}
@RequestBody方法参数获取请求体(JSON/XML)请求体 JSON
@RequestHeader方法参数获取请求头信息Accept: application/json
@CookieValue方法参数获取 Cookie 中的值JSESSIONID=xxx
@SessionAttribute方法参数从 Session 中获取属性session 中的 user
@ModelAttribute方法参数将请求参数绑定到对象,也可在方法上返回数据到 Model表单提交绑定
@GetMapping("/users/{id}")
public User getUser(
    @PathVariable Long id,                           // /users/123 → id=123
    @RequestParam(defaultValue = "1") int page,      // ?page=2 → page=2
    @RequestHeader("Authorization") String token,    // 请求头 Authorization
    @CookieValue("sessionId") String sessionId       // Cookie sessionId
) { ... }

四、SpringBoot

27. 项目中 SpringBoot 和 SpringCloud 的版本号

问题: 你们项目中 SpringBoot 和 SpringCloud 的版本号是什么?

答案:

(以下为常见版本组合示例,需根据实际项目填写)

SpringBootSpringCloudSpringCloud Alibaba
2.7.x2021.0.x (Jubilee)2021.0.x
3.0.x2022.0.x (Kilburn)2022.0.x
3.2.x2023.0.x (Leyton)2023.0.x

注意: SpringBoot 和 SpringCloud 版本有严格的兼容对应关系,不能随意搭配。官方提供版本对应表:Spring Cloud Release Notes


28. 为什么要使用 SpringBoot?

问题: 为什么要使用 SpringBoot?

答案:

  1. 自动配置(Auto-Configuration):根据 classpath 中的依赖自动配置 Spring 应用,减少大量 XML 配置
  2. 内嵌容器:内嵌 Tomcat/Jetty/Undertow,无需部署 WAR 包,直接 java -jar 启动
  3. 起步依赖(Starter):提供各种场景的 Starter POM,一键引入所需依赖,解决依赖冲突
  4. 无需 XML:使用 Java 配置 + application.yml/properties,告别繁琐的 XML
  5. 生产就绪特性:内置健康检查、指标监控、外部化配置等
  6. 简化构建:集成 Maven/Gradle 插件,一键打包可执行 jar
  7. 快速开发:结合 Spring Initializr 可快速搭建项目骨架

29. @SpringBootApplication 注解由哪三个注解组成?

问题: @SpringBootApplication 注解是由哪三个注解组成的?

答案:

@SpringBootConfiguration      // 等同于 @Configuration,标记为配置类
@EnableAutoConfiguration       // 启用自动配置机制
@ComponentScan                 // 开启组件扫描(扫描当前包及子包下的组件)
  • @SpringBootConfiguration:本质就是 @Configuration,表示这是一个配置类
  • @EnableAutoConfiguration:根据 classpath 中的依赖自动配置 Spring 应用
  • @ComponentScan:自动扫描主类所在包及其子包下的 @Component 注解的类

30. SpringBoot 启动时做了什么?

问题: SpringBoot 启动时做了什么?

答案:

1. new SpringApplication() 初始化
   ├── 推断应用类型(SERVLET / REACTIVE / NONE)
   ├── 加载初始器(ApplicationContextInitializer)
   ├── 加载监听器(ApplicationListener)
   └── 推断主类(main 方法所在类)

2. run() 方法执行
   ├── 启动计时器(StopWatch)
   ├── 获取监听器,广播 ApplicationStartingEvent
   ├── 准备环境(Environment),广播 ApplicationEnvironmentPreparedEvent
   ├── 打印 Banner
   ├── 创建 ApplicationContext(根据类型创建)
   ├── 准备 ApplicationContext,广播 ApplicationContextInitializedEvent
   ├── 刷新 ApplicationContext(核心步骤)
   │   ├── 注册 BeanDefinition
   │   ├── 实例化单例 Bean
   │   ├── 处理 @Autowired 注入
   │   ├── 执行自动配置(@EnableAutoConfiguration)
   │   └── 启动内嵌 Web 容器(Tomcat 等)
   ├── 刷新后处理,广播 ApplicationStartedEvent
   ├── 执行 Runner(ApplicationRunner / CommandLineRunner)
   └── 广播 ApplicationReadyEvent,启动完成

31. SpringBoot 的 starter 列举至少 5 个常用的

问题: SpringBoot 有哪些常用的 Starter?

答案:

Starter作用
spring-boot-starter-webWeb 开发支持(SpringMVC + 内嵌 Tomcat)
spring-boot-starter-data-redisRedis 集成
spring-boot-starter-data-jpaJPA / Hibernate 数据库操作
spring-boot-starter-jdbcJDBC + 数据源支持
spring-boot-starter-mybatisMyBatis 集成
spring-boot-starter-securitySpring Security 安全框架
spring-boot-starter-validation参数校验(JSR-380)
spring-boot-starter-aopAOP 切面编程
spring-boot-starter-actuator监控与管理端点
spring-boot-starter-test测试支持(JUnit + Mockito)
spring-boot-starter-amqpRabbitMQ 消息队列
spring-boot-starter-mail邮件发送

32. SpringBoot 读取配置文件的方式有几种?

问题: SpringBoot 读取配置文件的方式有几种?

答案:

方式说明示例
@Value注入单个配置值@Value("${server.port}")
@ConfigurationProperties批量绑定配置到 Bean@ConfigurationProperties(prefix = "spring.datasource")
Environment通过环境对象获取env.getProperty("server.port")
@PropertySource加载指定的 properties 文件@PropertySource("classpath:custom.properties")
ConfigurableApplicationContext通过上下文获取环境applicationContext.getEnvironment()
# application.yml
app:
  name: MyApp
  config:
    host: localhost
    port: 8080
// 方式一:@Value
@Value("${app.name}")
private String appName;

// 方式二:@ConfigurationProperties(推荐,支持类型安全和 JSR-303 校验)
@Component
@ConfigurationProperties(prefix = "app.config")
@Validated
public class AppConfig {
    private String host;
    private int port;
    // getter/setter ...
}

// 方式三:Environment
@Autowired
private Environment env;

public void method() {
    String name = env.getProperty("app.name");
}

33. SpringBoot 内置的容器和默认日志框架

问题: SpringBoot 内置的容器是哪个?默认的日志框架是哪个?

答案:

内置容器:

  • 默认:Tomcatspring-boot-starter-web
  • 可切换为:JettyUndertow(排除 Tomcat 依赖,添加对应 Starter)
<!-- 切换为 Undertow -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

默认日志框架:

  • SLF4J + Logback
  • SpringBoot 通过 spring-boot-starter-logging 默认引入
  • SLF4J 是日志门面(接口),Logback 是具体实现
  • 可通过配置切换为 Log4j2

34. SpringBoot 如何接收客户端请求

问题: SpringBoot 如何接收客户端请求?

答案:

SpringBoot 接收请求的方式与 SpringMVC 一致,核心就是通过 DispatcherServlet 分发请求到 Controller:

@RestController
@RequestMapping("/api")
public class UserController {

    // 1. 路径参数
    @GetMapping("/user/{id}")
    public User getById(@PathVariable Long id) { ... }

    // 2. 查询参数
    @GetMapping("/user")
    public User getByName(@RequestParam String name) { ... }

    // 3. 请求体 JSON
    @PostMapping("/user")
    public Result create(@RequestBody UserDTO dto) { ... }

    // 4. 表单数据
    @PostMapping("/login")
    public Result login(@RequestParam String username,
                        @RequestParam String password) { ... }

    // 5. 文件上传
    @PostMapping("/upload")
    public Result upload(@RequestParam("file") MultipartFile file) { ... }

    // 6. 请求头
    @GetMapping("/info")
    public Result info(@RequestHeader("Authorization") String token) { ... }
}

请求处理流程:

客户端 → TomcatDispatcherServletHandlerMappingController → 返回 JSON

35. SpringBoot 如何处理跨域问题

问题: SpringBoot 如何处理跨域问题?

答案:

方式一:全局配置(推荐)

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(3600);
    }
}

方式二:CORS 过滤器

@Configuration
public class CorsFilterConfig {
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOriginPattern("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

方式三:单个接口配置

@CrossOrigin(origins = "*", maxAge = 3600)
@GetMapping("/api/user")
public User getUser() { ... }

五、SpringCloud

36. 为什么用 SpringCloud?优势和缺点?

问题: 为什么用 SpringCloud?优势是什么?缺点是什么?

答案:

优势:

  1. 生态完善:提供了一整套微服务解决方案(注册中心、配置中心、网关、负载均衡、熔断等)
  2. 技术统一:基于 Spring Boot 开发,与 Spring 生态无缝集成
  3. 社区活跃:Spring 官方维护,文档丰富,社区支持好
  4. 选型灵活:各个组件可按需选择,支持 Netflix OSS、Alibaba 等多种实现
  5. 开箱即用:通过 Starter 依赖快速集成各组件
  6. 兼容性强:支持多种注册中心(Eureka、Nacos、Consul 等)和消息队列

缺点:

  1. 学习曲线陡:组件繁多,概念复杂,需要掌握分布式系统的知识
  2. 部署运维复杂:微服务数量多,对运维(Docker/K8s、日志收集、链路追踪)要求高
  3. 调试困难:服务间调用链长,排查问题需要完善的监控体系
  4. 版本迭代快:版本更新频繁,组件淘汰和替换频繁(如 Netflix 部分组件停更)
  5. 性能开销:相比单体应用,微服务间通信增加网络开销
  6. 事务一致性:分布式事务处理复杂(需要 Seata 等额外组件)

37. SpringCloud 有哪些组件?

问题: SpringCloud 有哪些组件?

答案:

功能Netflix 方案Alibaba 方案说明
服务注册与发现EurekaNacos微服务注册与发现
服务调用FeignOpenFeign声明式 HTTP 客户端
负载均衡RibbonLoadBalancer客户端负载均衡
熔断降级HystrixSentinel服务熔断、限流、降级
配置中心Spring Cloud ConfigNacos Config统一配置管理
网关ZuulGatewayAPI 网关,路由、过滤
分布式事务Seata分布式事务解决方案
消息驱动Spring Cloud Stream消息驱动的微服务
链路追踪Sleuth + ZipkinSkyWalking分布式链路追踪
服务限流Sentinel流量控制

38. Eureka 的工作原理?

问题: Eureka 的工作原理是什么?

答案:

核心架构:Eureka Server + Eureka Client

                    ┌──────────────┐
                    │  Eureka Server│
                    │  (注册中心)    │
                    └──────┬───────┘
                           │
            ┌──────────────┼──────────────┐
            │              │              │
     ┌──────┴──────┐ ┌────┴─────┐ ┌──────┴──────┐
     │  Service A  │ │ Service B│ │  Service C  │
     │ (Provider)  │ │(Consumer)│ │ (Provider)  │
     └─────────────┘ └──────────┘ └─────────────┘

工作流程:

  1. 服务注册:服务启动时,向 Eureka Server 发送注册请求(服务名、IP、端口等)
  2. 心跳续约:服务每隔 30 秒向 Eureka Server 发送心跳(Renew),证明自己存活
  3. 服务拉取:客户端从 Eureka Server 获取服务列表,缓存到本地
  4. 服务调用:客户端通过负载均衡(Ribbon)选择一个服务实例发起远程调用
  5. 服务下线:服务正常关闭时,向 Eureka Server 发送下线请求(Cancel)
  6. 自我保护:Eureka Server 如果短时间丢失大量心跳(网络分区),会进入自我保护模式,不再删除注册信息
  7. 剔除服务:如果服务 90 秒未发送心跳(3 个心跳周期),Eureka Server 将其剔除

注意: Eureka 采用 AP 模型(可用性优先),不保证强一致性。Eureka 2.x 已停止开发。


39. Nacos 组件解决了什么问题?

问题: Nacos 组件解决了什么问题?

答案:

Nacos(Dynamic Naming and Configuration Service)是阿里巴巴开源的,主要解决两大问题:

1. 服务注册与发现:

  • 微服务的动态注册和发现
  • 支持服务健康检查(临时实例:心跳模式;永久实例:主动探测模式)
  • 服务分组管理,支持命名空间隔离

2. 动态配置管理:

  • 集中管理所有微服务的配置
  • 配置热更新(修改配置后无需重启服务)
  • 支持配置的版本管理和灰度发布
  • 支持多种配置格式(YAML、Properties、JSON、TEXT)
  • 支持配置监听(配置变更实时推送)

其他特性:

  • 支持命名空间(Namespace)实现多环境隔离(dev/test/prod)
  • 支持服务分组(Group)
  • 支持权重路由
  • 提供控制台 UI,可视化管理服务和配置

40. Nacos 和 Eureka 有什么区别?

问题: Nacos 和 Eureka 有什么区别?

答案:

对比项NacosEureka
CAP 模型支持 AP 和 CP 切换仅 AP
配置中心✅ 内置配置中心❌ 需配合 Spring Cloud Config
健康检查临时实例(心跳)/ 永久实例(主动探测)仅心跳模式
服务分组支持分组和命名空间仅支持 Zone
配置热更新✅ 原生支持❌ 不支持
权重路由✅ 支持权重调整❌ 不支持
管理界面✅ 功能丰富的控制台✅ 基础控制台
社区状态活跃维护(阿里主导)2.x 停止维护,1.x 维护中
一致性协议Raft(CP 模式)/ Distro(AP 模式)复制协议(最终一致性)
中文文档✅ 完善一般

41. 为什么要用 Gateway 组件?

问题: 为什么要用 Gateway 组件?

答案:

  1. 统一入口:所有微服务的统一访问入口,客户端只需知道网关地址
  2. 路由转发:根据请求路径、Header、参数等规则将请求路由到对应的微服务
  3. 统一鉴权:在网关层统一做身份认证和权限校验,避免每个服务重复实现
  4. 限流:对高并发接口进行流量控制,保护后端服务
  5. 熔断降级:结合 Sentinel/Hystrix 实现服务熔断降级
  6. 日志监控:统一记录访问日志、请求耗时等
  7. 跨域处理:在网关层统一处理跨域问题
  8. 灰度发布:支持基于权重的路由,实现灰度发布
  9. 协议转换:支持 WebSocket、gRPC 等多种协议
  10. 隐藏内部结构:对外只暴露网关地址,不暴露内部微服务拓扑

42. Gateway 组件的核心知识

问题: Gateway 组件的核心知识包括哪些内容?

答案:

三大核心概念:

概念说明
Route(路由)路由的基本构建块,包含 ID、目标 URI、Predicate 和 Filter
Predicate(断言)匹配请求的条件(路径、Header、参数、时间等)
Filter(过滤器)对请求/响应进行修改(添加 Header、限流、重试等)

内置 Predicate 工厂:

Predicate说明示例
Path路径匹配Path=/api/user/**
MethodHTTP 方法匹配Method=GET
Header请求头匹配Header=X-Request-Id, \d+
Query查询参数匹配Query=token
Host主机名匹配Host=**.example.com
RemoteAddrIP 地址匹配RemoteAddr=192.168.1.0/24
After/Before/Between时间匹配After=2024-01-01T00:00:00+08:00[Asia/Shanghai]

内置 Filter:

Filter说明
AddRequestHeader添加请求头
AddResponseHeader添加响应头
StripPrefix去除路径前缀
Retry重试
RequestRateLimiter限流(配合 Redis + RedisRateLimiter)
CircuitBreaker熔断器
# Gateway 配置示例
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service          # lb:// 表示负载均衡
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1                # 去除 /api 前缀
            - AddRequestHeader=X-Token, xxx

注意: SpringCloud Gateway 基于 WebFlux + Netty 实现,与 SpringMVC 不兼容,不能在 Web 项目中同时使用。


43. Feign 组件解决了什么问题?

问题: Feign 组件解决了什么问题?

答案:

  1. 简化 HTTP 调用:将微服务间的 HTTP 调用抽象为 Java 接口,像调用本地方法一样调用远程服务
  2. 声明式调用:通过注解定义请求方式、URL、参数等,无需手动拼接 URL 和处理响应
  3. 集成 Ribbon/LoadBalancer:自动实现客户端负载均衡
  4. 集成熔断器:可配合 Hystrix/Sentinel 实现熔断降级
  5. 模板化编码:消除 RESTful API 调用中的重复代码(如 RestTemplate 的手动序列化/反序列化)
// 使用 RestTemplate(繁琐)
@RestController
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;
    
    @GetMapping("/order/{id}")
    public Order getOrder(@PathVariable Long id) {
        User user = restTemplate.getForObject(
            "http://user-service/user/" + id, User.class);
        return new Order(id, user);
    }
}

// 使用 Feign(简洁)
@FeignClient(name = "user-service")
public interface UserClient {
    @GetMapping("/user/{id}")
    User getUser(@PathVariable Long id);
}

@RestController
public class OrderController {
    @Autowired
    private UserClient userClient;
    
    @GetMapping("/order/{id}")
    public Order getOrder(@PathVariable Long id) {
        User user = userClient.getUser(id); // 像调用本地方法一样
        return new Order(id, user);
    }
}

44. Feign 远程调用异常处理及协议

问题: Feign 远程调用接口出现异常应该怎么处理?Feign 是基于什么协议?

答案:

异常处理方式:

方式一:Feign 的 Fallback(降级处理)

// 定义降级类
@Component
public class UserClientFallback implements UserClient {
    @Override
    public User getUser(Long id) {
        return new User(-1L, "服务降级", 0);
    }
}

// 接口指定 fallback
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
    @GetMapping("/user/{id}")
    User getUser(@PathVariable Long id);
}

方式二:FallbackFactory(带异常信息的降级)

@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
    @Override
    public UserClient create(Throwable cause) {
        return new UserClient() {
            @Override
            public User getUser(Long id) {
                log.error("调用用户服务失败,原因:", cause);
                return new User(-1L, "服务降级:" + cause.getMessage(), 0);
            }
        };
    }
}

@FeignClient(name = "user-service", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient { ... }

方式三:全局异常处理

@Configuration
public class FeignConfig {
    @Bean
    public ErrorDecoder errorDecoder() {
        return (methodKey, response) -> {
            // 自定义异常处理逻辑
            String body = Util.toString(response.body().asReader());
            return new RemoteServiceException("远程调用失败: " + body);
        };
    }
}

Feign 基于的协议:

  • Feign 默认基于 HTTP 协议(底层使用 HttpURLConnection
  • 可替换底层实现:Apache HttpClient、OkHttp
  • SpringCloud OpenFeign 默认集成 Spring MVC 注解

45. Feign 的执行流程?使用 Feign 会用到哪些注解?

问题: Feign 的执行流程是怎样的?使用 Feign 会用到哪些注解?

答案:

执行流程:

1. @EnableFeignClients → 扫描标注了 @FeignClient 的接口
2. 为每个 @FeignClient 接口生成 JDK 动态代理对象
3. 调用接口方法 → 代理对象拦截调用
4. 根据 @GetMapping/@PostMapping 等注解构建 HTTP 请求模板
5. 经过 Encoder 将参数编码(如 Jackson 序列化为 JSON)
6. 经过 Interceptor(拦截器)添加 Header 等
7. 通过 LoadBalancer 选择服务实例
8. 通过 Client(HttpURLConnection/OkHttp/HttpClient)发送 HTTP 请求
9. 经过 Decoder 将响应解码为 Java 对象
10. 返回结果(或进入 Fallback 降级逻辑)

常用注解:

注解作用
@EnableFeignClients启动类上开启 Feign 客户端扫描
@FeignClient声明 Feign 客户端(name=服务名,url=直连地址,fallback=降级类)
@GetMapping映射 GET 请求
@PostMapping映射 POST 请求
@PutMapping映射 PUT 请求
@DeleteMapping映射 DELETE 请求
@PathVariable路径参数
@RequestParam查询参数
@RequestBody请求体参数
@RequestHeader请求头参数

46. Feign 是如何使用的?

问题: Feign 是如何使用的?

答案:

步骤一:引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

步骤二:启动类添加注解

@SpringBootApplication
@EnableFeignClients  // 开启 Feign 客户端
public class OrderApplication { ... }

步骤三:定义 Feign 接口

@FeignClient(name = "user-service", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {

    @GetMapping("/user/{id}")
    User getUser(@PathVariable("id") Long id);

    @PostMapping("/user")
    User createUser(@RequestBody UserDTO dto);

    @GetMapping("/user/list")
    List<User> listUsers(@RequestParam("ids") List<Long> ids);
}

步骤四:调用远程服务

@Service
public class OrderService {

    @Autowired
    private UserClient userClient;

    public OrderDTO getOrder(Long orderId) {
        // 像调用本地方法一样调用远程服务
        User user = userClient.getUser(userId);
        return buildOrderDTO(user);
    }
}

步骤五:配置文件(可选)

feign:
  client:
    config:
      default:
        connectTimeout: 5000    # 连接超时
        readTimeout: 10000      # 读取超时
        loggerLevel: full       # 日志级别

47. Feign 的实现原理是什么样的?

问题: Feign 的实现原理是什么?

答案:

Feign 的核心原理就是 JDK 动态代理 + 注解解析 + HTTP 客户端

  1. @EnableFeignClients 触发 FeignClientsRegistrar,扫描所有 @FeignClient 注解的接口,注册为 FactoryBean

  2. FeignClientFactoryBean.getObject() 为每个接口创建代理对象:

    // 内部调用 Feign.builder() 构建
    return Feign.builder()
        .encoder(encoder)          // 参数编码器
        .decoder(decoder)          // 响应解码器
        .contract(contract)        // 注解契约(SpringMVCContract)
        .client(client)            // HTTP 客户端
        .interceptor(interceptor)  // 请求拦截器
        .target(FeignClient.class, url);
    
  3. 动态代理(FeignInvocationHandler

    • 调用接口方法时,代理对象拦截
    • 通过 SynchronousMethodHandler 处理方法调用
  4. 构建请求

    • Contract 解析方法上的 Spring MVC 注解(@GetMapping@PostMapping@PathVariable 等)
    • 构建 RequestTemplate(包含 URL、Method、Headers、Body)
  5. 负载均衡

    • 如果 URI 使用 lb:// 前缀,通过 LoadBalancerClient 选择服务实例
    • 将逻辑服务名替换为实际的 IP:PORT
  6. 发送请求

    • 通过底层的 Client(默认 JDK HttpURLConnection,可切换 OkHttp/HttpClient)发送 HTTP 请求
  7. 解析响应

    • Decoder 将响应体反序列化为方法返回值类型

48. 微服务间互相通信可以怎么实现?

问题: 微服务间互相通信可以怎么实现?

答案:

方式说明适用场景
OpenFeign声明式 HTTP 客户端,像调用本地方法一样调用远程服务同步请求,最常用
RestTemplate直接发起 HTTP 请求简单场景,代码稍繁琐
Dubbo基于 RPC 协议的高性能远程调用高性能、低延迟场景
消息队列(MQ)通过 RabbitMQ/Kafka/RocketMQ 异步通信异步解耦、削峰填谷、事件驱动
gRPCGoogle 的 RPC 框架,基于 HTTP/2 + Protocol Buffers多语言、高性能场景
WebSocket全双工通信实时推送场景
// 方式一:OpenFeign(推荐)
@FeignClient("user-service")
public interface UserClient {
    @GetMapping("/user/{id}")
    User getUser(@PathVariable Long id);
}

// 方式二:RestTemplate + 负载均衡
@Autowired
@LoadBalanced
private RestTemplate restTemplate;

User user = restTemplate.getForObject("http://user-service/user/1", User.class);

// 方式三:消息队列异步通信
@Autowired
private RabbitTemplate rabbitTemplate;

rabbitTemplate.convertAndSend("order.exchange", "order.created", orderDTO);

49. Nacos 是 AP 还是 CP?

问题: Nacos 是 AP 还是 CP?

答案:

Nacos 同时支持 AP 和 CP,可以根据服务类型灵活切换:

实例类型模式一致性协议说明
临时实例(默认)AP 模式Distro 协议通过心跳保活,牺牲一致性保证可用性
永久实例CP 模式Raft 协议通过主动健康检查,保证一致性但可用性稍低
# 注册为临时实例(AP 模式)- 默认
spring:
  cloud:
    nacos:
      discovery:
        ephemeral: true    # 临时实例,AP

# 注册为永久实例(CP 模式)
spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false   # 永久实例,CP

选择建议:

  • AP 模式(临时实例):大多数微服务场景,允许短暂的不一致,优先保证可用性
  • CP 模式(永久实例):对一致性要求高的服务,如配置中心的数据

50. Hystrix 熔断降级触发标准?熔断器有几种状态?

问题: Hystrix 熔断降级的触发标准是什么?熔断器有几种状态?

答案:

熔断触发标准:

在设定的时间窗口(默认 10 秒)内,满足以下任一条件时触发熔断:

条件默认值说明
请求量阈值≥ 20 次时间窗口内请求数达到阈值才计算错误率
错误率阈值≥ 50%失败请求占总请求的比例
异常类型超时、异常、拒绝、短路

注意: 如果请求数不足 20 次,即使全部失败也不会触发熔断。

熔断器三种状态:

         失败率 >= 50%
    ┌──────────────────┐
    │                  ↓
┌───────┐        ┌─────────┐        ┌──────────┐
│  关闭  │───────→│   打开   │───────→│  半打开   │
│(Closed)│        │  (Open) │        │(Half-Open)│
└───────┘        └─────────┘        └──────────┘
    ↑                  │                   │
    │     成功率正常     │    失败率 ≥ 50%    │
    └──────────────────┘←──────────────────┘
                       成功率正常
状态说明
关闭(Closed)正常状态,所有请求正常通过。滑动窗口统计失败率
打开(Open)熔断状态,所有请求直接走降级逻辑(Fallback)。持续 5 秒
半打开(Half-Open)熔断超时后进入半打开状态,放行少量请求试探测。如果成功则恢复关闭;如果继续失败则重新打开

实际项目中,SpringCloud Alibaba 更推荐使用 Sentinel 替代 Hystrix,Sentinel 提供了更丰富的限流、熔断、降级功能和可视化控制台。