Spring Boot + Spring Cloud Alibaba + MyBatis-Plus——这三个框架构成了90%的Java后端项目的骨架。面试官要听的不是"我会用",而是"我知道企业里怎么用"
1. Spring Boot — 企业级开发的基石
1.1 企业中Spring Boot的典型项目结构
一个标准的企业级多模块Maven项目结构:
project-name/
├── project-name-api/ # 对外暴露的DTO/接口
│ └── src/main/java/.../dto/ # 数据传输对象
├── project-name-common/ # 通用工具类与常量
│ └── src/main/java/.../util/ # 工具类
├── project-name-dao/ # 数据访问层
│ └── src/main/java/.../mapper/ # MyBatis Mapper
│ └── src/main/resources/mapper/ # XML映射文件
├── project-name-service/ # 业务逻辑层
│ └── src/main/java/.../service/
│ └── src/main/java/.../service/impl/
├── project-name-web/ # 控制层(启动入口)
│ └── src/main/java/.../controller/
│ └── src/main/java/.../config/ # 配置类
│ └── src/main/resources/
│ ├── application.yml
│ ├── application-dev.yml
│ ├── application-prod.yml
│ └── bootstrap.yml # Nacos配置
└── pom.xml企业实践:多模块的核心目的是职责分离和依赖控制——dao层只依赖common,service层依赖dao,web层依赖service,形成单向依赖环。
1.2 自动配置原理 (面试高频)
// Spring Boot 自动配置的核心注解
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}自动配置原理三步拆解:
@SpringBootApplication
├── @EnableAutoConfiguration ← 开启自动配置
│ └── @Import(AutoConfigurationImportSelector.class)
│ └── SpringFactoriesLoader.loadFactoryNames()
│ └── META-INF/spring.factories
│ └── (读取所有xxxAutoConfiguration类)
├── @ComponentScan ← 组件扫描
└── @SpringBootConfiguration ← 标记配置类核心流程:
AutoConfigurationImportSelector从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 2.7+) 或spring.factories中读取所有 AutoConfiguration 类名- 逐类解析
@Conditional条件注解链 —— 条件不满足就跳过 - 匹配条件的配置类被加载,其中的
@Bean方法被解析并注入容器
关键条件注解:
| 注解 | 作用 | 示例场景 |
|---|---|---|
@ConditionalOnClass | 类路径存在某类时生效 | RedisAutoConfiguration 依赖 RedisOperations |
@ConditionalOnMissingBean | 容器中没有某Bean时生效 | 用户自定义 DataSource 则跳过默认配置 |
@ConditionalOnProperty | 配置项匹配时生效 | spring.redis.host 存在时配置Redis |
@ConditionalOnWebApplication | Web环境下生效 | WebMvcAutoConfiguration |
@ConditionalOnExpression | SpEL表达式为true时生效 | 复杂组合条件 |
面试深问:如果问"如何自定义Starter",回答要点——(1) 创建
xxx-spring-boot-starter模块 (2) 编写XxxAutoConfiguration(3) 配置spring.factories(4) 提供spring-configuration-metadata.json做IDE提示。
1.3 常用Starter清单与选型建议
| 分类 | Starter | 用途 | 企业使用率 |
|---|---|---|---|
| Web | spring-boot-starter-web | 传统Servlet Web (Tomcat) | ★★★★★ |
| 响应式 | spring-boot-starter-webflux | 响应式Web (Netty) | ★★ |
| 数据 | spring-boot-starter-data-redis | Redis操作 (Lettuce) | ★★★★★ |
| 数据 | mybatis-spring-boot-starter | MyBatis整合 | ★★★★★ |
| 数据 | spring-boot-starter-data-jpa | JPA整合 | ★★★ |
| 数据 | shardingsphere-jdbc-core-spring-boot-starter | 分库分表 | ★★★ |
| 安全 | spring-boot-starter-security | 认证授权 | ★★★★ |
| 消息 | spring-boot-starter-amqp | RabbitMQ | ★★★ |
| 消息 | rocketmq-spring-boot-starter | RocketMQ | ★★★★ |
| 监控 | spring-boot-starter-actuator | 端点监控 | ★★★★★ |
| 测试 | spring-boot-starter-test | 单元测试 | ★★★★★ |
| 云 | spring-cloud-starter-alibaba-nacos-discovery | Nacos注册 | ★★★★★ |
| 云 | spring-cloud-starter-alibaba-sentinel | 流量控制 | ★★★★★ |
| 云 | spring-cloud-starter-gateway | 网关 | ★★★★ |
| 云 | spring-cloud-starter-openfeign | HTTP客户端 | ★★★★★ |
| 无状态 | spring-boot-starter-oauth2-resource-server | JWT/OAuth | ★★★★ |
选型建议:
- Starter版本必须跟随Spring Boot版本,使用
spring-boot-starter-parent或spring-boot-dependenciesBOM统一管理 - 避免引入不必要的Starter,降低启动时间和依赖冲突风险
- 企业内部可以封装
company-spring-boot-starter统一日志、监控、异常处理
1.4 企业级配置管理
多环境Profile
# application.yml — 公共配置
spring:
profiles:
active: @profile.active@ # Maven打包时动态替换
application:
name: order-service
server:
tomcat:
max-threads: 200 # 公共默认值
min-spare-threads: 20
---
# application-dev.yml (开发环境)
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:mysql://localhost:3306/order?useSSL=false
redis:
host: localhost
---
# application-prod.yml (生产环境)
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:mysql://prod-master:3306/order?useSSL=true
hikari:
maximum-pool-size: 20 # 生产环境连接池调大
redis:
host: redis-prod-cluster
timeout: 3000ms企业实践:敏感配置(数据库密码、密钥)不放在Git仓库中,而是使用Nacos配置中心或Vault管理,本地配置只保留非敏感项。
Nacos配置中心
# bootstrap.yml — 优先于application.yml加载
spring:
cloud:
nacos:
config:
server-addr: 192.168.1.100:8848
namespace: prod # 命名空间隔离环境
group: DEFAULT_GROUP
file-extension: yaml
refresh-enabled: true # 配置热更新
discovery:
server-addr: 192.168.1.100:8848// 配置热更新:两种方式
@RefreshScope // 方式一:注解标记,配置变化时Bean刷新
public class DynamicConfig {
@Value("${order.timeout:5000}")
private Integer orderTimeout;
}
// 方式二:@ConfigurationProperties 自动刷新(推荐)
@ConfigurationProperties(prefix = "order")
public class OrderProperties {
private Integer timeout;
private Integer retryCount;
}1.5 内嵌Tomcat调优
# application.yml — Tomcat调优配置
server:
tomcat:
# 连接器线程池 —— 处理请求的工作线程
max-threads: 200 # 默认200,按CPU核心数*2~4调整
min-spare-threads: 20 # 最小空闲线程数
max-connections: 10000 # 最大连接数,默认10000
accept-count: 1000 # 等待队列长度
connection-timeout: 5000 # 连接超时ms
# HTTP压缩
compression: on
compression-min-response-size: 1024 # 超过1KB启用压缩
# 优雅关闭 —— 生产环境必配
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s// 代码方式配置
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
return factory -> {
factory.addConnectorCustomizers(connector -> {
connector.setProperty("maxSwallowSize", "-1"); // 不限制吞数据大小
connector.setProperty("relaxedQueryChars", "[]{}"); // URL特殊字符
});
factory.addContextCustomizers(context -> {
context.setSessionTimeout(30, TimeUnit.MINUTES);
});
};
}面试点:调优的核心是观测 -> 调整 -> 再观测,不要盲目改参数。启动时观察
/actuator/metrics/tomcat.threads.busy线程忙碌程度,找到水位线再调整。
2. Spring Cloud Alibaba — 微服务全家桶
2.1 企业微服务全景架构图
┌─────────────────────┐
│ Nacos │
│ 注册中心+配置中心 │
└──────────┬──────────┘
│
┌───────────────────────┼───────────────────────┐
│ 注册/发现 │ 配置管理 │
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────────┐
│ Spring Cloud Gateway │
│ 路由转发 │ 鉴权(Sentinel) │ 限流 │ 跨域 │ 请求日志 │
└──────┬───────────────────────────────────────────────────────────────┘
│
│ OpenFeign 调用
▼
┌──────────────────────────────────────────────────────────────────────┐
│ 微服务集群 (Service Cluster) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 用户服务│◄─┤ 订单服务│◄─┤ 库存服务│◄─┤ 支付服务│ │ 通知服务│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │ │
│ │ │ Seata AT │ │ │ │
│ │ │ (分布式事务)│ │ │ │
│ └────────────┴────────────┴────────────┴──────────┘ │
│ │
│ 同步调用: OpenFeign + Sentinel 熔断降级 │
│ 异步通信: RocketMQ (事务消息) │
└──────────────────────────────────────────────────────────────────────┘2.2 Nacos — 注册中心与配置中心二合一
Nacos 是阿里巴巴开源的一站式服务发现与配置管理平台,企业中最常用的功能点:
注册中心能力
# application.yml 服务注册配置
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.1.100:8848
namespace: prod # 环境隔离
group: ORDER_GROUP # 服务分组
cluster-name: HZ # 集群名(同城机房)
weight: 1.0 # 负载均衡权重
ephemeral: true # 临时实例(默认true)
heart-beat-interval: 5000 # 心跳间隔msAP vs CP模式选择:
| 模式 | 一致性模型 | 特点 | 适用场景 |
|---|---|---|---|
| AP (临时实例) | 最终一致性 | 心跳检测,不可用时直接剔除 | 大部分业务场景(默认),可用性优先 |
| CP (永久实例) | 强一致性 | 使用Raft协议,需半数以上节点确认 | 银行、交易核心,一致性优先 |
选型建议:90%的企业选择AP模式。CP模式在集群半数节点宕机时不可用,对生产环境风险较大。
配置中心实战
# Nacos中推送的配置 Data ID: order-service-prod.yaml
order:
timeout: 3000
auto-cancel-minutes: 30
max-retry-count: 3
spring:
datasource:
url: jdbc:mysql://prod-db:3306/order?rewriteBatchedStatements=true// 动态刷新配置
@Component
@RefreshScope
@ConfigurationProperties(prefix = "order")
public class OrderConfig {
private Integer timeout;
private Integer autoCancelMinutes;
private Integer maxRetryCount;
// getter/setter... 配置变更时自动注入新值
}企业级配置最佳实践:
- 命名空间隔离:dev / test / prod 三个命名空间,配置完全隔离
- 配置分组管理:按业务域分组(ORDER_GROUP / USER_GROUP / PAY_GROUP)
- 灰度配置:先发布到灰度机器验证,再全量推送
- 配置变更审计:记录每次配置变更的操作人和变更内容
2.3 Sentinel — 流量控制框架
Sentinel 是阿里巴巴开源的流量防卫兵,企业中使用频率极高:
核心功能速查
| 功能 | 说明 | 配置方式 |
|---|---|---|
| 流控 (FlowRule) | QPS/线程数限流 | 直接/关联/链路模式 |
| 熔断 (DegradeRule) | 慢调用/异常比例/异常数熔断 | 熔断后降级方法 |
| 热点 (ParamFlowRule) | 热点参数限流 | 针对特定参数值限流 |
| 系统规则 (SystemRule) | 系统负载/CPU保护 | Load/RT/入口QPS |
| 授权规则 (AuthorityRule) | 黑白名单 | 根据调用来源控制 |
// 企业中最常用的Sentinel配置方式:注解 + Nacos持久化
// 1. 资源定义与流控
@Service
public class OrderService {
@SentinelResource(
value = "createOrder",
blockHandler = "createOrderBlockHandler", // 触发了流控 -> 走这里
fallback = "createOrderFallback" // 触发了熔断 -> 走这里
)
public OrderVO createOrder(OrderDTO orderDTO) {
// 业务逻辑
if (someCondition) {
throw new RuntimeException("业务异常");
}
return doCreate(orderDTO);
}
// 流控降级 —— 返回友好的提示信息
public OrderVO createOrderBlockHandler(OrderDTO orderDTO, BlockException ex) {
log.warn("createOrder 被限流: {}", ex.getMessage());
return OrderVO.failed("系统繁忙,请稍后重试");
}
// 熔断降级 —— 返回兜底数据
public OrderVO createOrderFallback(OrderDTO orderDTO, Throwable t) {
log.error("createOrder 熔断降级: {}", t.getMessage());
return OrderVO.failed("服务异常,请稍后重试");
}
}// 2. 规则持久化到Nacos(避免重启丢失规则)
@Configuration
public class SentinelConfig {
@PostConstruct
public void initSentinelRules() {
// 流控规则持久化
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource =
new NacosDataSource<>(
Arrays.asList("192.168.1.100:8848"),
"DEFAULT_GROUP",
"sentinel-flow-rules",
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
);
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
// 熔断规则持久化
ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource =
new NacosDataSource<>(
Arrays.asList("192.168.1.100:8848"),
"DEFAULT_GROUP",
"sentinel-degrade-rules",
source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {})
);
DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
}
}企业常问的Sentinel场景题:
| 场景 | 解决方案 |
|---|---|
| 秒杀流量瞬间暴涨10倍 | QPS流控 + 排队等待模式,削峰填谷 |
| 第三方支付接口变慢 | 熔断降级(慢调用比例>50%则熔断),返回"支付处理中" |
| 热点商品频繁被刷 | 热点参数限流,针对商品ID的QPS设为100 |
| 数据库连接池打满 | 线程数限流 + 系统Load保护 |
面试重点:Sentinel与Hystrix的核心区别——(1) Sentinel支持实时监控和动态规则 (2) 支持热点限流 (3) 支持规则持久化 (4) 线程隔离是信号量而非线程池 (5) 微服务零侵入
2.4 Gateway — 路由/鉴权/限流
# application.yml — 企业级Gateway配置
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service # 负载均衡
predicates:
- Path=/api/order/** # 路径匹配
- Header=X-Request-Source, mobile # Header匹配
filters:
- StripPrefix=1 # 去掉/api前缀
- name: RequestRateLimiter # 限流
args:
key-resolver: "#{@userKeyResolver}"
redis-rate-limiter:
replenishRate: 100 # 每秒令牌数
burstCapacity: 200 # 令牌桶容量
- name: Retry # 重试
args:
retries: 3
statuses: BAD_GATEWAY
methods: GET
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
# 全局过滤器配置
default-filters:
- AddResponseHeader=X-Response-Time, localtime
- DedupeResponseHeader=Access-Control-Allow-Origin// 全局鉴权过滤器 —— 企业必配
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 白名单路径跳过鉴权
List<String> whitelist = Arrays.asList("/api/user/login", "/api/user/register");
if (whitelist.contains(path)) {
return chain.filter(exchange);
}
// 从Header中提取Token
String token = request.getHeaders().getFirst("Authorization");
if (StringUtils.isBlank(token) || !token.startsWith("Bearer ")) {
return unauthorized(exchange, "Missing token");
}
// 校验JWT(缓存校验结果减少重复解析)
try {
Claims claims = JwtUtil.parseToken(token.substring(7));
// 将用户信息放入请求头,传递给下游服务
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-User-Id", claims.get("userId").toString())
.header("X-User-Name", URLEncoder.encode(claims.get("userName").toString(), "UTF-8"))
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());
} catch (Exception e) {
return unauthorized(exchange, "Invalid token");
}
}
@Override
public int getOrder() {
return -100; // 高优先级
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String msg) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse().bufferFactory()
.wrap(("{\"code\":401,\"msg\":\"" + msg + "\"}").getBytes()))
);
}
}面试关键:Gateway的核心组件——Route(路由)、Predicate(匹配条件)、Filter(过滤处理)。Gateway是基于WebFlux响应式编程模型,使用Netty而非Tomcat,因此不能直接获取HttpServletRequest。
2.5 OpenFeign — 声明式HTTP调用
// 企业级OpenFeign配置
// 1. FeignClient声明
@FeignClient(
name = "inventory-service",
path = "/api/inventory",
configuration = InventoryFeignConfig.class,
fallbackFactory = InventoryFallbackFactory.class // 降级工厂
)
public interface InventoryFeignClient {
@PostMapping("/deduct")
Result<Void> deductStock(@Valid @RequestBody StockDeductDTO dto);
@GetMapping("/query/{skuId}")
Result<StockVO> queryStock(@PathVariable("skuId") Long skuId);
}
// 2. 统一降级处理 —— 返回兜底数据 + 记录日志
@Component
public class InventoryFallbackFactory implements FallbackFactory<InventoryFeignClient> {
@Override
public InventoryFeignClient create(Throwable cause) {
log.error("inventory-service 调用失败: {}", cause.getMessage());
return new InventoryFeignClient() {
@Override
public Result<Void> deductStock(StockDeductDTO dto) {
return Result.error("库存服务不可用");
}
@Override
public Result<StockVO> queryStock(Long skuId) {
return Result.success(new StockVO(skuId, 0)); // 返回安全值
}
};
}
}
// 3. 配置类 —— 超时/拦截器/重试
@Configuration
public class InventoryFeignConfig {
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
// 传递TraceId用于链路追踪
requestTemplate.header("X-Trace-Id", MDC.get("traceId"));
// 传递认证信息
requestTemplate.header("X-User-Id", UserContext.getUserId());
};
}
@Bean
public Retryer retryer() {
return new Retryer.Default(100, 1000, 3); // 100ms间隔, 最多3次
}
}# application.yml — Feign全局配置
spring:
cloud:
openfeign:
client:
config:
default:
connect-timeout: 5000 # 连接超时
read-timeout: 10000 # 读取超时
logger-level: BASIC # BASIC/HEADERS/FULL
compression:
request:
enabled: true
mime-types: application/json
min-request-size: 2048
response:
enabled: true性能警告:Feign底层是JDK HttpURLConnection或Apache HttpClient,生产环境建议替换为 OkHttp 或 Apache HttpClient,并设置连接池——JDK默认实现不支持连接池,高并发下性能极差。
2.6 Seata — 分布式事务
// AT模式 —— 最常用,对业务侵入最小
@GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class)
public OrderVO createOrder(OrderDTO orderDTO) {
// 1. 创建订单(本地事务)
orderMapper.insert(order);
// 2. 扣减库存(远程调用:inventory-service)
inventoryFeign.deductStock(new StockDeductDTO(order.getSkuId(), order.getCount()));
// 3. 扣减账户余额(远程调用:account-service)
accountFeign.deductBalance(new BalanceDeductDTO(order.getUserId(), order.getAmount()));
// 任意一步失败,Seata自动回滚所有参与者
return OrderVO.success(order);
}三种模式对比:
| 模式 | 性能 | 一致性 | 侵入性 | 适用场景 |
|---|---|---|---|---|
| AT | 较高 | 最终一致 | 低(自动回滚SQL) | 大部分业务场景(推荐) |
| TCC | 高 | 最终一致 | 高(需实现Try/Confirm/Cancel) | 高性能要求的核心链路 |
| Saga | 高 | 最终一致 | 中(需实现补偿逻辑) | 长事务、异步场景 |
Seata AT模式原理:
AT模式执行流程
┌─────────┐ ┌─────────┐ ┌─────────┐
│ TM │ │ TC │ │ RM │
│ (事务发起)│────▶│ (协调器)│────▶│ (资源管理)│
└────┬────┘ └─────────┘ └────┬────┘
│ │
│ 1. 开启全局事务 │
│ 2. 注册分支事务 │
│ 3. 执行业务SQL │
│ + 解析SQL生成UNDO_LOG │
│ 4. 提交/回滚 │
└────────────────────────────────┘
关键机制:
- 前置镜像: 在执行业务SQL前,记录修改前的数据快照
- UNDO_LOG表: 保存前后镜像,用于失败回滚
- 全局锁: 防止脏读脏写,事务结束后释放2.7 Dubbo vs Spring Cloud 选型
很多企业内部会同时使用两者,了解它们的异同是面试加分项:
| 维度 | Dubbo | Spring Cloud | 两者结合使用 |
|---|---|---|---|
| 通信协议 | Dubbo协议 (Netty, TCP长连接) | HTTP (RestTemplate/Feign) | 内部RPC用Dubbo,对外开放用Spring Cloud |
| 性能 | 高(二进制协议,序列化快) | 较低(HTTP+JSON) | Dubbo处理内部高并发调用 |
| 治理 | 自带服务治理(路由、限流、降级) | 需整合Sentinel/Hystrix | Dubbo + Sentinel 完美搭配 |
| 生态 | 主要用于服务间RPC | 完整微服务体系 | 互补使用,取长补短 |
| 学习曲线 | 较低,配置简单 | 组件较多,学习门槛稍高 | 团队根据能力选择 |
| 互联网大厂 | 阿里/京东/美团等大量使用 | 中小型企业为主 | 大厂内部Dubbo为主 + 网关层Spring Cloud |
企业真实场景:很多企业用 Dubbo做内部服务间RPC调用(高性能),用 Spring Cloud Gateway做API网关(生态丰富),服务注册全部走 Nacos,流量控制统一用 Sentinel——这就是"Dubbo + Spring Cloud Alibaba 混合架构"。
3. 持久层框架 — MyBatis-Plus & JPA
3.1 MyBatis-Plus在企业中的实际用法
MyBatis-Plus(简称MP)是MyBatis的增强工具,国内企业渗透率极高:
BaseMapper 通用CRUD
// 1. 定义Mapper —— 继承BaseMapper即获得基础CRUD
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
// 无需写任何代码,自动拥有 insert/deleteById/updateById/selectById 等方法
}
// 2. 使用示例
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
public Order getById(Long id) {
return orderMapper.selectById(id); // 单表查询
}
public Page<Order> pageQuery(int page, int size) {
Page<Order> pageObj = new Page<>(page, size);
return orderMapper.selectPage(pageObj, null);
}
public boolean save(Order order) {
// 自动填充 createTime, updateTime (配合 @TableField)
return orderMapper.insert(order) > 0;
}
}条件构造器 (Wrapper)
// QueryWrapper —— 最常用,支持链式条件构建
public List<Order> queryOrders(Long userId, Integer status, String keyword) {
LambdaQueryWrapper<Order> wrapper = Wrappers.<Order>lambdaQuery()
.eq(Order::getUserId, userId)
.eq(status != null, Order::getStatus, status) // 条件判断
.like(StringUtils.isNotBlank(keyword), Order::getOrderNo, keyword) // 模糊查询
.orderByDesc(Order::getCreateTime)
.last("limit 10");
return orderMapper.selectList(wrapper);
}
// UpdateWrapper —— 更新特定字段(避免全字段更新)
public void updateStatus(String orderNo, Integer status) {
LambdaUpdateWrapper<Order> wrapper = Wrappers.<Order>lambdaUpdate()
.set(Order::getStatus, status)
.set(Order::getUpdateTime, new Date())
.eq(Order::getOrderNo, orderNo);
orderMapper.update(null, wrapper);
}分页插件
@Configuration
@MapperScan("com.company.*.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件(必配)
PaginationInnerInterceptor pagination = new PaginationInnerInterceptor(DbType.MYSQL);
pagination.setMaxLimit(1000L); // 单页最大条数保护
pagination.setOverflow(true); // 溢出后自动回到第一页
interceptor.addInnerInterceptor(pagination);
// 乐观锁插件(写操作需要用)
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}逻辑删除与自动填充
// 1. 逻辑删除 —— 企业标配,避免物理删除
@TableName("order")
public class Order {
@TableId(type = IdType.ASSIGN_ID) // 分布式ID
private Long id;
@TableLogic(value = "0", delval = "1") // 逻辑删除
private Integer deleted;
@TableField(fill = FieldFill.INSERT) // 自动填充
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
// 2. 自动填充处理器
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}3.2 MyBatis vs JPA 选型
| 维度 | MyBatis / MyBatis-Plus | Spring Data JPA (Hibernate) |
|---|---|---|
| SQL控制 | 完全手动编写SQL,精准控制 | 自动生成SQL,控制力弱 |
| 学习曲线 | 较低(会SQL就会用) | 较高(需要理解ORM映射机制) |
| 复杂查询 | 动态SQL灵活强大 | JPQL/Criteria API较复杂 |
| 性能 | 高,SQL可控 | 容易产生N+1问题,需要调优 |
| 迁移成本 | SQL与具体数据库耦合 | 数据库无关性好 |
| 国内企业 | 80%以上使用 | 10%-15%使用 |
| 面试热度 | ★★★★★ | ★★★ |
国内企业为什么偏好MyBatis-Plus?
- SQL可控:DBA可以直接审查和优化SQL,不像JPA自动生成难掌控
- 复杂业务场景:国内企业业务逻辑复杂,多表关联、子查询频繁,MyBatis的动态SQL更灵活
- 团队习惯:Java开发者基本都熟悉SQL,JPA的学习曲线相对陡峭
- 与ShardingSphere整合:分库分表场景下MyBatis-Plus + ShardingSphere-JDBC是国内标准搭配
3.3 复杂SQL管理
// XML映射文件 —— 复杂SQL建议写在XML中而非注解
@Mapper
public interface OrderExtMapper {
// 统计某时间段内的销售排行
List<SalesRankVO> getSalesRanking(@Param("startDate") String startDate,
@Param("endDate") String endDate,
@Param("limit") Integer limit);
}<!-- OrderExtMapper.xml —— 企业级复杂SQL示例 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.company.order.mapper.OrderExtMapper">
<!-- 基础结果映射 -->
<resultMap id="OrderDetailMap" type="com.company.order.entity.OrderDetail">
<id property="id" column="id"/>
<result property="orderNo" column="order_no"/>
<result property="totalAmount" column="total_amount"/>
<association property="user" javaType="com.company.user.entity.User"
select="com.company.user.mapper.UserMapper.selectById"
column="user_id"/>
<collection property="items" ofType="com.company.order.entity.OrderItem"
select="com.company.order.mapper.OrderItemMapper.selectByOrderId"
column="id"/>
</resultMap>
<!-- 动态SQL + 时间窗口统计 -->
<select id="getSalesRanking" resultType="com.company.order.vo.SalesRankVO">
SELECT
oi.sku_id,
sku_name,
SUM(oi.quantity) AS total_sales,
SUM(oi.amount) AS total_amount
FROM order_item oi
INNER JOIN `order` o ON oi.order_id = o.id
<where>
o.`status` = 2 <!-- 已支付状态 -->
<if test="startDate != null and startDate != ''">
AND o.pay_time >= #{startDate}
</if>
<if test="endDate != null and endDate != ''">
AND o.pay_time <![CDATA[ <= ]]> #{endDate}
</if>
AND o.deleted = 0
</where>
GROUP BY oi.sku_id, sku_name
ORDER BY total_sales DESC
<if test="limit != null">
LIMIT #{limit}
</if>
</select>
</mapper>3.4 与ShardingSphere整合
# application.yml —— ShardingSphere + MyBatis-Plus 分库分表配置
spring:
shardingsphere:
datasource:
names: ds0, ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.10:3306/order_0?useSSL=false
username: root
password: ${DB_PASSWORD}
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.11:3306/order_1?useSSL=false
username: root
password: ${DB_PASSWORD}
rules:
sharding:
tables:
order:
actual-data-nodes: ds$->{0..1}.order_$->{0..3}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db_hash # 按user_id分库
table-strategy:
standard:
sharding-column: id
sharding-algorithm-name: table_mod # 按id取模分表
key-generate-strategy:
column: id
key-generator-name: snowflake # 雪花算法分布式ID
sharding-algorithms:
db_hash:
type: HASH_MOD
props:
sharding-count: 2 # 2个库
table_mod:
type: MOD
props:
sharding-count: 4 # 每库4张表
key-generators:
snowflake:
type: SNOWFLAKE # 雪花算法
props:
sql-show: true # 打印SQL(开发环境启用)
check-table-metadata-enabled: true # 检查表元数据一致性企业实践:ShardingSphere + MyBatis-Plus 是国内分库分表的标准组合。核心原则——(1) 分片键选择要均衡,避免热点 (2) 尽量避免跨分片查询 (3) 不适合做跨分片Join和事务,需要应用层处理。
4. 网关与认证授权
4.1 Spring Cloud Gateway 企业级配置详解
# Gateway核心路由配置(已在2.4节展示基础配置)
# 这里补充企业常用的高级特性
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
- Method=GET,POST,PUT,DELETE # 允许的HTTP方法
- Header=X-Request-ID, \d+ # Header匹配正则
- Query=source, app. # 查询参数匹配
- RemoteAddr=192.168.1.0/24 # IP白名单
- Weight=group1, 80 # 灰度权重
filters:
- name: CircuitBreaker # 断路器
args:
name: userServiceCB
fallbackUri: forward:/fallback/user
- AddRequestHeader=X-Api-Version, v2
- PrefixPath=/api/v2
- name: SecureHeaders # 安全头
args:
disable: x-frame-options4.2 JWT + OAuth2.0 + RBAC 权限模型
企业中典型的权限模型是一个三层结构:
三层权限模型
┌─────────────────────────────────────────────────────┐
│ 用户 (User) │
│ ┌──────┴──────┐ │
│ │ admin │ user1 user2 user3 │
│ └──────┬──────┘ │
│ │ 多对多 │
│ ┌──────▼──────┐ │
│ │ 角色 (Role)│ │
│ │ 管理员 │ 运营 │ 客服 │ 财务 │
│ └──────┬──────┘ │
│ │ 多对多 │
│ ┌──────▼──────┐ │
│ │ 权限 (Permission) │
│ │ 订单:查询 │ 订单:创建 │ 订单:退款 │ 用户:管理 │
│ └─────────────┘ │
└─────────────────────────────────────────────────────┘JWT Token结构:
# JWT = Header.Payload.Signature
# Header
{
"alg": "RS256", // 非对称加密
"typ": "JWT"
}
# Payload
{
"sub": "user123", // 用户ID
"userName": "张三",
"roles": ["admin", "operator"],
"permissions": ["order:create", "order:query", "order:refund"],
"exp": 1750000000, // 过期时间
"iat": 1700000000, // 签发时间
"jti": "<uuid>" // Token唯一ID(用于黑名单)
}
# Signature
RSASHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), privateKey)4.3 Spring Security 企业配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级别权限控制
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 1. 关闭CSRF(前后端分离)
.csrf().disable()
// 2. 无状态Session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 3. 请求鉴权
.authorizeRequests()
.antMatchers("/api/user/login", "/api/user/register").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
// 4. 异常处理
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}");
})
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":403,\"msg\":\"无权限\"}");
})
// 5. JWT过滤器
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
}4.4 Sa-Token 轻量级权限框架
Sa-Token 在中小企业中使用广泛,以其简洁易用著称:
// Sa-Token 极简API
@RestController
@RequestMapping("/api")
public class UserController {
// 登录 —— 一行代码
@PostMapping("/login")
public Result<String> login(@RequestBody LoginDTO dto) {
User user = userService.login(dto.getUsername(), dto.getPassword());
StpUtil.login(user.getId()); // 登录,生成Token
StpUtil.getSession().set("userInfo", user); // Session中存用户信息
return Result.success(StpUtil.getTokenValue()); // 返回Token
}
// 鉴权 —— 注解方式
@SaCheckLogin // 必须登录
@SaCheckRole("admin") // 必须admin角色
@SaCheckPermission("order:delete") // 必须有指定权限
@PostMapping("/order/delete")
public Result<Void> deleteOrder(Long orderId) {
orderService.delete(orderId);
return Result.success();
}
}Sa-Token vs Spring Security 对比:
| 维度 | Sa-Token | Spring Security |
|---|---|---|
| 上手难度 | 低(15分钟上手) | 高(配置复杂) |
| 功能覆盖 | 核心认证授权 | 完整安全框架 |
| OAuth2支持 | 需插件 | 原生支持 |
| 社区活跃度 | 国内活跃 | 全球主流 |
| 企业使用 | 中小企业为主 | 中大型企业为主 |
选型建议:如果没有复杂的OAuth2 SSO需求,Sa-Token是个很好的轻量选择。大厂通常用Spring Security + 自研认证中心。
5. 定时任务与异步处理
5.1 XXL-JOB — 分布式任务调度
XXL-JOB是目前国内企业使用最广泛的分布式调度平台:
XXL-JOB核心特性:
| 特性 | 说明 | 企业使用场景 |
|---|---|---|
| 分片广播 | 任务在每台机器上各执行一次 | 数据对账、批量处理 |
| 动态分片 | 按分片参数执行不同数据 | 海量数据并行处理 |
| 故障转移 | 调度失败后自动转移到其他机器 | 核心定时任务高可用 |
| 失败重试 | 支持配置重试次数 | 补偿任务 |
| 任务依赖 | 父子任务顺序执行 | 数据ETL流程 |
| 日志查看 | 在线查看执行日志 | 调试线上问题 |
// XXL-JOB 任务示例 —— 分片广播处理海量数据
@Component
public class BatchOrderHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String param) throws Exception {
// 获取分片信息
int shardIndex = XxlJobHelper.getShardIndex(); // 当前分片序号 (0,1,2...)
int shardTotal = XxlJobHelper.getShardTotal(); // 总分片数
// 按分片查询待处理订单:当前节点只处理自己分片的数据
List<Order> orders = orderMapper.selectPendingOrders(
shardIndex, shardTotal, 1000
);
XxlJobHelper.log("当前节点: {}/{},待处理订单: {}", shardIndex, shardTotal, orders.size());
for (Order order : orders) {
try {
processOrder(order);
} catch (Exception e) {
XxlJobHelper.log("处理订单 {} 失败: {}", order.getOrderNo(), e.getMessage());
// 记录失败,后续通过补偿任务处理
orderMapper.insertFailedLog(order.getId(), e.getMessage());
}
}
return ReturnT.SUCCESS;
}
}XXL-JOB调度中心配置:
# xxl-job 执行器配置
xxl:
job:
admin:
addresses: http://192.168.1.100:8080/xxl-job-admin
executor:
appname: order-job-executor
ip:
port: 9999
logpath: /data/applogs/xxl-job/jobhandler
logretentiondays: 30
accessToken: ${ACCESS_TOKEN}5.2 Spring @Async + 线程池配置
@Configuration
@EnableAsync // 开启异步支持
public class AsyncConfig implements AsyncConfigurer {
@Override
@Bean("taskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心参数 —— 需根据业务类型调整
executor.setCorePoolSize(10); // 核心线程数
executor.setMaxPoolSize(50); // 最大线程数
executor.setQueueCapacity(200); // 等待队列
executor.setKeepAliveSeconds(60); // 空闲线程存活时间
executor.setThreadNamePrefix("async-biz-"); // 线程名前缀
// 拒绝策略 —— 企业推荐CallerRunsPolicy(由调用线程执行)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 优雅关闭
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(30);
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
log.error("异步任务执行异常: {}#{}", method.getDeclaringClass().getSimpleName(),
method.getName(), ex);
// 发送告警通知
alertService.sendAlert("异步任务失败", method.getName(), ex.getMessage());
};
}
}
// 使用示例
@Service
public class OrderServiceImpl implements OrderService {
@Async("taskExecutor") // 指定线程池
@Override
public CompletableFuture<Void> asyncProcessAfterPay(Order order) {
// 异步执行:发送短信、更新统计、推送消息......失败不影响主流程
smsService.sendPaySuccessSms(order.getPhone(), order.getOrderNo());
statisticsService.updateSalesStatistic(order);
pushService.sendPaySuccessPush(order.getUserId(), order.getOrderNo());
return CompletableFuture.completedFuture(null);
}
}面试高频坑点:
@Async的两个陷阱 —— (1) 同一个类中的方法互相调用时@Async不生效(走的是代理对象,this调用跳过代理)(2) 默认使用 SimpleAsyncTaskExecutor(每次创建新线程),必须自定义线程池。
5.3 Quartz vs Elastic-Job vs XXL-JOB 对比
| 维度 | Quartz | Elastic-Job | XXL-JOB |
|---|---|---|---|
| 定位 | 单机/集群定时任务库 | 分布式调度框架 | 分布式调度平台 |
| 分布式 | 支持集群(数据库锁) | 原生分布式(Zookeeper协调) | 原生分布式(数据库协调) |
| 管理界面 | 无(需自研) | 简易管理界面 | 功能完善的管理界面 |
| 分片 | 不支持 | 支持(任务分片) | 支持(分片广播+动态分片) |
| 动态配置 | 不支持 | 支持 | 支持(支持暂停/触发/修改) |
| 运维友好度 | 低 | 中 | 高(有完善的日志和监控) |
| 国内企业 | 逐渐淘汰 | 部分在用 | 主流选择 |
企业真实情况:90%的新项目直接用XXL-JOB,老的Quartz任务也在分批迁移到XXL-JOB。原因很简单——XXL-JOB有完整的Web管理界面、支持在线日志查看、支持动态修改执行计划、调度中心宕机不影响已启动的任务。
6. 面试高频追问
这些是面试官在聊完技术细节后,会追着问的"为什么"——回答质量决定了是PASS还是Strong Hire:
Q1:为什么选择Spring Cloud Alibaba而不是Spring Cloud Netflix?
回答要点:
- Netflix组件进入维护期:Eureka 2.0已停止开发,Hystrix进入维护模式,Zuul社区活跃度持续下降
- 功能密度更高:Nacos = Eureka + Spring Cloud Config + Bus 三个组件,减少运维复杂度
- Sentinel vs Hystrix:Sentinel支持实时监控、热点限流、规则持久化、微服务零侵入,Hystrix不支持
- 社区活跃度:Alibaba组件在国内更新频繁,中文文档友好,遇到问题容易找到解决方案
Q2:你们为什么选MyBatis-Plus而不是JPA?
回答要点:
- 团队惯性:现有团队成员都熟悉SQL,MyBatis上手更快,JPA的HQL和Criteria API学习成本较高
- SQL可控:DBA可以直接review和优化MyBatis的SQL语句,JPA自动生成的SQL复杂场景下难以掌控
- 与ShardingSphere的兼容性:分库分表场景下,MyBatis-Plus + ShardingSphere-JDBC是经过验证的标准方案
- 动态SQL能力:MyBatis的动态SQL(if/where/foreach)在处理复杂查询时比JPA更直观灵活
Q3:你们用的哪种分布式事务方案?为什么选Seata AT?
回答要点:
- 业务场景匹配:我们的核心链路(下单 -> 扣库存 -> 扣余额)需要强一致性,AT模式正好满足
- 侵入性最低:AT模式对业务代码零侵入,只需加一个
@GlobalTransactional注解,相比TCC需要手写Try/Confirm/Cancel,开发效率更高 - 性能可接受:AT模式的性能损失主要在前后镜像记录和全局锁上,经过压测单次AT事务耗时<50ms,满足业务要求
- 不做TCC的原因:我们的业务场景没有高性能强要求,TCC的Confirm/Cancel逻辑需要大量额外的业务补偿代码,维护成本高
Q4:Redis缓存常见三大问题,你们怎么解决的?
回答要点:
- 缓存穿透:布隆过滤器 + 空值缓存(过期时间5分钟),两种方案并行,布隆过滤器拦截绝大部分不合理请求
- 缓存击穿:互斥锁(Redisson分布式锁) + 热点数据不过期,热点数据采用后台定时刷新而不是等到过期再加载
- 缓存雪崩:缓存过期时间加随机值(基础值 +/- 随机1-10分钟) + 多级缓存(本地Caffeine + Redis) + 限流降级
Q5:如果让你设计一个微服务网关,你会怎么设计?
回答要点:
- 路由转发:配置中心(Nacos)+ 动态路由刷新,支持灰度发布
- 鉴权体系:JWT Token校验(缓存公钥,减少RSA解密次数)+ 白名单机制
- 限流熔断:集成Sentinel做网关流控,支持按API/用户/来源IP分组限流
- 统一异常处理:全局异常处理 + 统一返回格式
- 可观测性:全链路TraceId透传 + 请求响应日志(ES存储) + 请求耗时监控
- 高可用:集群部署 + 健康检查 + 与Sentinel Dashboard集成监控
Q6:微服务拆分的原则是什么?
回答要点:
- 业务域划分:按DDD限界上下文划分,用户域、订单域、支付域、库存域各自独立
- 数据独立:每个服务拥有自己的数据库,不共享库
- 通信原则:同步调用(OpenFeign)解决查询类场景,异步消息(MQ)解决最终一致性场景
- 拆分粒度控制:一开始不要拆太细,建议3-5个服务起步,随着业务复杂度逐步拆分
- 避免拆分的场景:强关联的业务(同一个事务中更新多个表)、频繁交互的模块(网络开销超过收益)
Q7:Gateway和Nginx的区别,能替代吗?
| 维度 | Nginx | Spring Cloud Gateway |
|---|---|---|
| 定位 | Web服务器/反向代理/负载均衡 | API网关 |
| 协议 | HTTP/HTTPS/TCP/UDP | HTTP/HTTPS/WebSocket |
| 动态路由 | 配置换需要reload | 支持动态刷新(集成Nacos) |
| 鉴权集成 | 需要lua脚本 | 原生支持Spring Security |
| 限流 | ngx_http_limit_req_module | 集成Sentinel/Redis RateLimiter |
| 服务发现 | 需配合consul/upstream动态更新 | 原生支持Nacos/Eureka |
企业实践:Nginx和Gateway并非替代关系,而是分层使用——Nginx负责接入层(SSL卸载、CDN、静态资源),Gateway负责业务层(路由、鉴权、限流、灰度)。
Q8:Sentinel@SentinelResource注解的BlockHandler和Fallback有什么区别?
| 维度 | blockHandler | fallback |
|---|---|---|
| 触发条件 | 流量控制 / 限流触发 | 熔断降级 / 业务异常 |
| 异常类型 | BlockException (系统级) | Throwable (业务异常) |
| 返回值 | 返回限流提示 | 返回兜底数据 |
| 典型场景 | "系统繁忙,请稍后重试" | "服务异常,展示缓存数据" |
Q9:你们如何保证缓存和数据库的一致?
企业常用方案(Cache Aside Pattern + Canal最终一致性):
读流程:
1. 查询缓存 -> 命中直接返回
2. 未命中 -> 查询数据库 -> 写入缓存 -> 返回
写流程(延迟双删):
1. 删除缓存
2. 更新数据库
3. 延迟500ms再次删除缓存(应对并发读写)
最终一致性(Canal方案):
1. Canal监听MySQL binlog变更
2. 解析变更数据
3. 发送MQ消息
4. 消费者更新Redis缓存面试注意:强一致性方案(分布式锁+双写)会导致性能大幅下降,绝大多数企业接受最终一致性。真正要求强一致性的数据就不应该放缓存。
Q10:讲一下XxlJob调度器和执行器的通信机制
XXL-JOB通信模型 (调度中心 => 执行器)
调度中心 (xxl-job-admin)
│
│ 触发调度 ├── 注册回调接口 (Post请求)
│
▼
执行器 (xxl-job-executor)
│
│ 接收调度 ├── 启动任务线程
│ 执行日志 ├── 写入日志文件
│ 回调结果 ├── Post请求通知调度中心
│
▼
调度中心更新任务状态
核心机制:
- 调度中心和执行器解耦,调度中心不持有执行器连接
- 回调机制是推模式(执行器主动回调),避免调度中心轮询
- 内置分布式锁防止同一任务被多次调度
- 调度失败会触发故障转移,选择另一台执行器重试核心要点:面试官问"为什么"比问"是什么"更能看出技术深度。每一次选型背后都应该有一个明确的理由——可能是性能、可能是团队、可能是生态、可能是维护成本。没有理由的选型 = 没有思考。