Java 后端开发2026年项目面试技巧(DeepSeek总结版)与面经总结
目录指引:
采购公共服务系统(中台型系统)
XXL-JOB + 策略模式的组合,能够完美实现调度与业务的解耦,既享受到XXL-JOB的分布式调度能力,又保留了策略模式的灵活扩展性。
一、整体架构设计

二、核心代码实现
1. 任务策略接口(与XXL-JOB解耦)
/**
* 任务策略接口 - 专注于业务逻辑,不关心调度
*/
public interface ITaskStrategy {
/**
* 任务唯一标识
*/
String getTaskId();
/**
* 任务名称
*/
String getTaskName();
/**
* 任务分组
*/
String getTaskGroup();
/**
* 执行任务
* @param context 任务上下文,包含参数、执行环境等
* @return 执行结果
*/
TaskResult execute(TaskContext context);
/**
* 任务执行前的校验
*/
default boolean validate(TaskContext context) {
return true;
}
/**
* 任务超时时间(毫秒),默认30分钟
*/
default long getTimeout() {
return 30 * 60 * 1000L;
}
/**
* 是否允许并发执行
*/
default boolean allowConcurrent() {
return false;
}
/**
* 任务描述
*/
default String getDescription() {
return "";
}
}
/**
* 任务执行结果
*/
@Data
@Builder
public class TaskResult {
private boolean success;
private String taskId;
private String taskName;
private String message;
private Object data;
private long executionTime;
private Map<String, Object> metrics;
public static TaskResult success(String message) {
return TaskResult.builder()
.success(true)
.message(message)
.build();
}
public static TaskResult failure(String message) {
return TaskResult.builder()
.success(false)
.message(message)
.build();
}
}2. 具体任务策略实现
@Component
public class TempFileCleanupTask implements ITaskStrategy {
@Value("${task.temp-file.retention-days:7}")
private int retentionDays;
@Override
public String getTaskId() {
return "tempFileCleanupTask";
}
@Override
public String getTaskName() {
return "清理临时文件";
}
@Override
public String getTaskGroup() {
return "MAINTENANCE";
}
@Override
public TaskResult execute(TaskContext context) {
log.info("开始清理临时文件,保留天数: {}", retentionDays);
try {
File tempDir = new File("/tmp/procurement");
if (!tempDir.exists()) {
return TaskResult.success("临时目录不存在,无需清理");
}
int deletedCount = deleteOldFiles(tempDir, retentionDays);
return TaskResult.builder()
.success(true)
.message(String.format("成功清理 %d 个临时文件", deletedCount))
.metrics(Map.of("deletedCount", deletedCount))
.build();
} catch (Exception e) {
log.error("清理临时文件失败", e);
return TaskResult.failure("清理失败: " + e.getMessage());
}
}
}
@Component
public class SupplierDataSyncTask implements ITaskStrategy {
@Override
public String getTaskId() {
return "supplierDataSyncTask";
}
@Override
public String getTaskName() {
return "同步供应商数据";
}
@Override
public String getTaskGroup() {
return "SYNC";
}
@Override
public long getTimeout() {
return 60 * 60 * 1000L; // 1小时超时
}
@Override
public TaskResult execute(TaskContext context) {
log.info("开始同步供应商数据...");
// 获取分片参数(用于分布式分片执行)
Integer shardIndex = context.getIntParam("shardIndex", 0);
Integer shardTotal = context.getIntParam("shardTotal", 1);
try {
// 只同步当前分片的数据
int syncedCount = syncFromExternalSystem(shardIndex, shardTotal);
return TaskResult.builder()
.success(true)
.message(String.format("同步完成,共 %d 条", syncedCount))
.metrics(Map.of(
"syncedCount", syncedCount,
"shardIndex", shardIndex,
"shardTotal", shardTotal
))
.build();
} catch (Exception e) {
log.error("同步供应商数据失败", e);
return TaskResult.failure("同步失败: " + e.getMessage());
}
}
}
// 其他38+个任务策略类似...3. 策略注册中心
@Component
public class TaskStrategyRegistry {
private final Map<String, ITaskStrategy> strategyMap = new ConcurrentHashMap<>();
private final Map<String, TaskMetadata> metadataMap = new ConcurrentHashMap<>();
@Autowired
private List<ITaskStrategy> strategies;
@PostConstruct
public void initialize() {
log.info("开始初始化任务策略注册中心...");
strategies.forEach(strategy -> {
String taskId = strategy.getTaskId();
strategyMap.put(taskId, strategy);
// 构建任务元数据
TaskMetadata metadata = TaskMetadata.builder()
.taskId(taskId)
.taskName(strategy.getTaskName())
.taskGroup(strategy.getTaskGroup())
.description(strategy.getDescription())
.timeout(strategy.getTimeout())
.allowConcurrent(strategy.allowConcurrent())
.build();
metadataMap.put(taskId, metadata);
log.info("注册任务策略: {} - {}", taskId, strategy.getTaskName());
});
log.info("任务策略注册完成,共注册 {} 个任务", strategyMap.size());
}
public ITaskStrategy getStrategy(String taskId) {
ITaskStrategy strategy = strategyMap.get(taskId);
if (strategy == null) {
throw new TaskNotFoundException("未找到任务: " + taskId);
}
return strategy;
}
public List<TaskMetadata> getAllMetadata() {
return new ArrayList<>(metadataMap.values());
}
public boolean isTaskExist(String taskId) {
return strategyMap.containsKey(taskId);
}
}4. XXL-JOB统一调度入口
@Component
@Slf4j
public class UnifiedTaskJobHandler {
@Autowired
private TaskStrategyRegistry strategyRegistry;
@Autowired
private TaskExecutionMonitor monitor;
@Autowired
private TaskAlertService alertService;
/**
* 统一任务执行入口 - XXL-JOB调用此方法
*/
@XxlJob("executeTask")
public void executeTask() throws Exception {
// 从XXL-JOB上下文中获取参数
String taskId = XxlJobHelper.getJobParam();
log.info("接收到任务调度指令: taskId={}", taskId);
if (StringUtils.isBlank(taskId)) {
XxlJobHelper.handleFail("任务ID不能为空");
return;
}
// 获取任务策略
ITaskStrategy strategy = strategyRegistry.getStrategy(taskId);
// 构建任务上下文
TaskContext context = buildTaskContext();
// 执行任务并监控
long startTime = System.currentTimeMillis();
TaskResult result = null;
try {
// 执行前置校验
if (!strategy.validate(context)) {
XxlJobHelper.handleFail("任务前置校验失败");
return;
}
// 执行任务(带超时控制)
result = executeWithTimeout(strategy, context);
// 记录执行结果
long duration = System.currentTimeMillis() - startTime;
monitor.recordExecution(taskId, result, duration);
// 处理结果
if (result.isSuccess()) {
log.info("任务执行成功: {}, 耗时: {}ms", taskId, duration);
XxlJobHelper.handleSuccess("执行成功: " + result.getMessage());
} else {
log.error("任务执行失败: {}, 原因: {}", taskId, result.getMessage());
XxlJobHelper.handleFail("执行失败: " + result.getMessage());
// 发送告警
alertService.sendTaskFailureAlert(taskId, result, duration);
}
} catch (TimeoutException e) {
log.error("任务执行超时: {}", taskId, e);
XxlJobHelper.handleFail("执行超时");
alertService.sendTaskTimeoutAlert(taskId, strategy.getTimeout());
} catch (Exception e) {
log.error("任务执行异常: {}", taskId, e);
XxlJobHelper.handleFail("执行异常: " + e.getMessage());
alertService.sendTaskExceptionAlert(taskId, e);
}
}
/**
* 带超时控制的任务执行
*/
private TaskResult executeWithTimeout(ITaskStrategy strategy, TaskContext context)
throws TimeoutException, Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<TaskResult> future = executor.submit(() -> strategy.execute(context));
try {
return future.get(strategy.getTimeout(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
throw e;
} finally {
executor.shutdownNow();
}
}
/**
* 构建任务上下文
*/
private TaskContext buildTaskContext() {
TaskContext context = new TaskContext();
// 从XXL-JOB获取分片参数
int shardIndex = XxlJobHelper.getShardIndex();
int shardTotal = XxlJobHelper.getShardTotal();
context.setParam("shardIndex", shardIndex);
context.setParam("shardTotal", shardTotal);
// 获取自定义参数(JSON格式)
String jobParam = XxlJobHelper.getJobParam();
if (StringUtils.isNotBlank(jobParam)) {
// 支持JSON格式的自定义参数
try {
Map<String, Object> params = JsonUtils.parseMap(jobParam);
params.forEach(context::setParam);
} catch (Exception e) {
log.warn("解析任务参数失败,使用原始参数: {}", jobParam);
context.setParam("rawParam", jobParam);
}
}
// 设置执行环境信息
context.setParam("executorHost", getLocalHost());
context.setParam("executorPort", getLocalPort());
context.setParam("executionTime", LocalDateTime.now().toString());
return context;
}
}5. 任务执行监控
@Component
@Slf4j
public class TaskExecutionMonitor {
@Autowired
private MeterRegistry meterRegistry;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 执行中的任务缓存
private final Set<String> runningTasks = ConcurrentHashMap.newKeySet();
public void recordExecution(String taskId, TaskResult result, long duration) {
// 更新运行状态
runningTasks.remove(taskId);
// 记录指标
Timer.Sample sample = Timer.start();
Timer.builder("task.execution.duration")
.tags("taskId", taskId, "success", String.valueOf(result.isSuccess()))
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
// 记录计数
Counter.builder("task.execution.count")
.tags("taskId", taskId, "success", String.valueOf(result.isSuccess()))
.register(meterRegistry)
.increment();
// 存储执行历史到Redis(用于最近执行状态查询)
String historyKey = "task:history:" + taskId;
redisTemplate.opsForList().leftPush(historyKey, ExecutionRecord.of(taskId, result, duration));
redisTemplate.opsForList().trim(historyKey, 0, 99); // 只保留最近100条
// 记录日志
log.info("任务执行记录: taskId={}, success={}, duration={}ms, message={}",
taskId, result.isSuccess(), duration, result.getMessage());
}
public boolean isTaskRunning(String taskId) {
return runningTasks.contains(taskId);
}
public void markTaskStarted(String taskId) {
runningTasks.add(taskId);
}
}6. 任务管理端点(用于XXL-JOB同步)
@RestController
@RequestMapping("/api/task-management")
public class TaskManagementController {
@Autowired
private TaskStrategyRegistry registry;
@Autowired
private TaskExecutionMonitor monitor;
/**
* 获取所有任务信息 - 用于同步到XXL-JOB
*/
@GetMapping("/tasks")
public ApiResponse<List<TaskMetadata>> getAllTasks() {
return ApiResponse.success(registry.getAllMetadata());
}
/**
* 获取任务执行状态
*/
@GetMapping("/tasks/{taskId}/status")
public ApiResponse<TaskStatus> getTaskStatus(@PathVariable String taskId) {
boolean isRunning = monitor.isTaskRunning(taskId);
List<ExecutionRecord> recentExecutions = monitor.getRecentExecutions(taskId, 10);
TaskStatus status = TaskStatus.builder()
.taskId(taskId)
.isRunning(isRunning)
.recentExecutions(recentExecutions)
.build();
return ApiResponse.success(status);
}
/**
* 手动触发任务执行
*/
@PostMapping("/tasks/{taskId}/trigger")
public ApiResponse<Void> triggerTask(@PathVariable String taskId) {
// 通过XXL-JOB的API触发任务
xxlJobClient.triggerTask(taskId);
return ApiResponse.success("任务已触发");
}
}三、XXL-JOB控制台配置
在XXL-JOB的管理界面中,只需要配置一个任务:
| 配置项 | 值 |
|---|---|
| 任务描述 | 统一任务调度入口 |
| JobHandler | executeTask |
| 运行模式 | BEAN模式 |
| 任务参数 | {taskId},如 tempFileCleanupTask |
# 不同任务的调度配置通过任务参数区分
# 示例:配置多个任务
- 任务: 清理临时文件
JobHandler: executeTask
任务参数: tempFileCleanupTask
Cron: 0 0 2 * * ?
- 任务: 同步供应商数据
JobHandler: executeTask
任务参数: supplierDataSyncTask
Cron: 0 0 1 * * ?
- 任务: 供应商评估超时检查
JobHandler: executeTask
任务参数: assessmentTimeoutCheckTask
Cron: 0 */5 * * * ?
# ... 其他39+个任务,都在XXL-JOB中配置为使用统一的JobHandler四、架构优势总结
| 对比维度 | 纯策略模式 | 纯XXL-JOB | XXL-JOB + 策略模式 |
|---|---|---|---|
| 调度高可用 | ❌ 需自研 | ✅ 原生支持 | ✅ 原生支持 |
| 可视化运维 | ❌ 需自研 | ✅ 原生支持 | ✅ 原生支持 |
| 任务扩展性 | ✅ 灵活 | ⚠️ 需配置 | ✅ 灵活 |
| 代码复用 | ✅ 高复用 | ❌ 重复配置 | ✅ 高复用 |
| 业务解耦 | ✅ 解耦 | ⚠️ 耦合度一般 | ✅ 彻底解耦 |
| 动态配置 | ✅ 灵活 | ⚠️ 需重启 | ✅ 动态灵活 |
| 分片能力 | ❌ 需自研 | ✅ 原生支持 | ✅ 原生支持 |
核心价值
"通过XXL-JOB + 策略模式的组合,我们实现了调度平台标准化与业务逻辑灵活化的完美统一。XXL-JOB负责解决分布式调度、高可用、可视化运维等平台级问题,而策略模式则负责让40+个任务的业务逻辑保持清晰、独立和可扩展。两者各司其职,互不侵入。"
扩展能力
// 新增加一个任务只需要三步:
// 1. 实现ITaskStrategy接口
@Component
public class NewBusinessTask implements ITaskStrategy {
@Override
public String getTaskId() { return "newBusinessTask"; }
@Override
public TaskResult execute(TaskContext context) {
// 业务逻辑
return TaskResult.success("执行完成");
}
}
// 2. 无需修改任何已有代码,策略注册中心会自动扫描注册
// 3. 在XXL-JOB控制台配置调度,指向统一入口即可:
// JobHandler: executeTask, 参数: newBusinessTask这就是**"平台级调度能力 + 业务级策略灵活性"**的最佳实践!
零部件询报价寻源系统(交易型系统)
下面我为你拆解如何通过 “注解标记 + YAML配置驱动 + AOP拦截” 的方式,重构零部件询报价系统的导入导出功能。
一、整体架构设计思路
我们的目标是将策略选择逻辑和技术参数从业务代码中抽离:
- 自定义注解:标记某个导入方法,声明它支持哪些数据量级别。
- 配置文件(YAML):定义每种级别下的具体参数(如批次大小、线程数、超时时间)。
- AOP切面(核心):拦截导入请求,自动计算数据量,根据配置路由到对应的处理器。
二、核心实现详解
1. 第一步:定义“导入策略”注解
这个注解用来标记 Service 层的导入方法,声明它能处理的数据量上限。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ImportStrategy {
// 策略名称,对应配置文件中的策略Key
String value() default "default";
// 该策略支持的最大数据量(超过则自动降级或报错)
long maxRows() default Long.MAX_VALUE;
}2. 第二步:在业务方法上使用注解
将你之前“万级、数万级、数十万级”的硬编码逻辑,拆分为三个独立的方法,并用注解标记。
@Service
public class PartDataImportService {
// 策略1:万级以下(常规导入)
@ImportStrategy(value = "level1", maxRows = 10000)
public ImportResult importLevel1(List<PartData> list) {
// 使用 MyBatis 批量插入 + 简单事务
return doBatchInsert(list, 1000);
}
// 策略2:数万级(高性能导入)
@ImportStrategy(value = "level2", maxRows = 100000)
public ImportResult importLevel2(List<PartData> list) {
// 使用 JDBC Batch + 手动事务控制
return doJdbcBatchInsert(list, 5000);
}
// 策略3:数十万级以上(分布式并行导入)
@ImportStrategy(value = "level3", maxRows = Long.MAX_VALUE)
public ImportResult importLevel3(List<PartData> list) {
// 使用 CompletableFuture 并行 + 分片处理
return doParallelImport(list);
}
}3. 第三步:外部化配置(配置文件驱动)
将具体的性能参数(如批次大小、线程池配置)抽离到 application.yml 中,方便运维动态调整,无需修改代码。
# application-import.yml
procurement:
import:
strategies:
level1:
description: 万级数据导入
batch-size: 1000
thread-pool: core: 2
transaction-mode: BATCH
level2:
description: 数万级数据导入
batch-size: 5000
thread-pool: core: 4
transaction-mode: MANUAL
level3:
description: 十万级以上数据导入
batch-size: 10000
thread-pool: core: 8
transaction-mode: PARALLEL
shard-size: 5000 # 分片大小4. 第四步:核心增强 - AOP自动路由与适配
利用 Spring AOP 拦截所有带 @ImportStrategy 注解的方法,自动识别数据量并动态路由到对应的导入策略。
@Aspect
@Component
public class ImportStrategyAspect {
@Autowired
private ImportConfigProperties configProperties; // 读取YAML配置
@Around("@annotation(importStrategy)")
public Object routeImport(ProceedingJoinPoint joinPoint, ImportStrategy importStrategy) throws Throwable {
Object[] args = joinPoint.getArgs();
List<PartData> dataList = extractDataList(args); // 提取导入数据
if (CollectionUtils.isEmpty(dataList)) {
return ImportResult.empty();
}
int dataSize = dataList.size();
log.info("检测到导入数据量: {} 条,开始智能路由", dataSize);
// 1. 获取所有候选策略方法
Method[] methods = joinPoint.getTarget().getClass().getMethods();
Method targetMethod = null;
ImportStrategy targetStrategy = null;
for (Method method : methods) {
if (method.isAnnotationPresent(ImportStrategy.class)) {
ImportStrategy strategy = method.getAnnotation(ImportStrategy.class);
// 2. 核心逻辑:根据数据量匹配 maxRows 最小的那个策略
if (dataSize <= strategy.maxRows()) {
if (targetStrategy == null || strategy.maxRows() < targetStrategy.maxRows()) {
targetStrategy = strategy;
targetMethod = method;
}
}
}
}
if (targetMethod == null) {
throw new IllegalArgumentException("未找到匹配数据量(" + dataSize + ")的导入策略");
}
// 3. 从配置文件获取该策略的详细参数
ImportStrategyConfig config = configProperties.getStrategies().get(targetStrategy.value());
// 4. 动态注入参数(构建新的上下文)
log.info("匹配到策略: {}, 配置参数: {}", targetStrategy.value(), config);
// 5. 执行目标方法(反射调用)
return targetMethod.invoke(joinPoint.getTarget(), args);
}
}5. 第五步:导出功能的“注解 + 配置”优化
对于大数据量导出(尤其是防止 OOM),我们可以定义一个 @ExportConfig 注解,配置分批大小和异步过期时间。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExportConfig {
// 每页查询数量(用于游标分页)
int fetchSize() default 1000;
// 是否异步导出(针对超大文件)
boolean async() default false;
// 异步任务过期时间(分钟)
int expireMinutes() default 30;
}使用示例:
@Service
public class QuotationExportService {
// 同步导出(小数据量)
@ExportConfig(fetchSize = 500, async = false)
public void exportSmallData(HttpServletResponse response) { ... }
// 异步导出(大数据量,防止超时和OOM)
@ExportConfig(fetchSize = 2000, async = true, expireMinutes = 60)
public String exportLargeData(ExportRequest request) {
// 1. 创建异步任务ID
// 2. 使用游标查询 + EasyExcel 分批写入临时文件
// 3. 返回下载链接
return "http://xxx/download/task-123.xlsx";
}
}三、优化后的效果对比
| 对比维度 | 优化前(硬编码) | 优化后(注解 + 配置) |
|---|---|---|
| 策略选择逻辑 | 写在业务代码里(if-else判断数量) | 由 AOP 自动路由,业务代码只关注具体逻辑 |
| 参数调整 | 修改代码、编译、重启 | 直接修改 YAML 配置文件,重启或支持热刷新即可 |
| 代码可读性 | 一个方法动辄几百行 | 每个策略独立方法,职责单一(SRP原则) |
| 扩展性 | 新增策略需要改动主流程 | 新增策略只需新增方法+注解,完全符合开闭原则 |
| 测试难度 | 测试大方法需构造各种数据量 | 各个策略方法可独立测试,Mock 简单 |
四、面试回答话术(直接可用)
当面试官问到:“你的导入导出优化具体是怎么做的?”
“针对导入导出功能的优化,我采用了 ‘声明式编程’ 的设计思想,结合 注解与外部配置文件 进行了架构升级。
在代码层面,我自定义了
@ImportStrategy注解,将原来if-else判断数据量的硬编码逻辑剥离,改为由 AOP 切面自动路由。比如,当数据量低于1万时,AOP会自动路由到标注了level1的方法;当超过10万时,自动路由到并行处理的level3方法。这样让核心业务逻辑非常干净,遵循了开闭原则。在运维层面,我将批次大小、线程池核心数、事务模式等性能参数,全部抽离到了
application.yml配置文件中。这使得我们无需修改代码,仅通过调整配置就能针对不同规模的服务器环境(如开发/测试/生产环境)进行性能调优,非常灵活。这套方案落地后,新增一种导入策略只需新增一个方法并打上注解,开发效率提升了 50% 以上,彻底解决了之前因策略调整而频繁修改核心类的痛点。”
To Be Continued.

