面试笔录 - 2026项目面试技巧与面经

Java 后端开发2026年项目面试技巧(DeepSeek总结版)与面经总结

目录指引

采购公共服务系统(中台型系统)

XXL-JOB + 策略模式的组合,能够完美实现调度与业务的解耦,既享受到XXL-JOB的分布式调度能力,又保留了策略模式的灵活扩展性。


一、整体架构设计

deepseek_mermaid_20260626_e67528

二、核心代码实现

1. 任务策略接口(与XXL-JOB解耦)

java
/**
 * 任务策略接口 - 专注于业务逻辑,不关心调度
 */
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. 具体任务策略实现

java
@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. 策略注册中心

java
@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统一调度入口

java
@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. 任务执行监控

java
@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同步)

java
@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的管理界面中,只需要配置一个任务

配置项
任务描述统一任务调度入口
JobHandlerexecuteTask
运行模式BEAN模式
任务参数{taskId},如 tempFileCleanupTask
yaml
# 不同任务的调度配置通过任务参数区分
# 示例:配置多个任务
- 任务: 清理临时文件
  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-JOBXXL-JOB + 策略模式
调度高可用❌ 需自研✅ 原生支持✅ 原生支持
可视化运维❌ 需自研✅ 原生支持✅ 原生支持
任务扩展性✅ 灵活⚠️ 需配置✅ 灵活
代码复用✅ 高复用❌ 重复配置✅ 高复用
业务解耦✅ 解耦⚠️ 耦合度一般✅ 彻底解耦
动态配置✅ 灵活⚠️ 需重启✅ 动态灵活
分片能力❌ 需自研✅ 原生支持✅ 原生支持

核心价值

"通过XXL-JOB + 策略模式的组合,我们实现了调度平台标准化业务逻辑灵活化的完美统一。XXL-JOB负责解决分布式调度、高可用、可视化运维等平台级问题,而策略模式则负责让40+个任务的业务逻辑保持清晰、独立和可扩展。两者各司其职,互不侵入。"

扩展能力

java
// 新增加一个任务只需要三步:

// 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拦截” 的方式,重构零部件询报价系统的导入导出功能。


一、整体架构设计思路

我们的目标是将策略选择逻辑技术参数从业务代码中抽离:

  1. 自定义注解:标记某个导入方法,声明它支持哪些数据量级别。
  2. 配置文件(YAML):定义每种级别下的具体参数(如批次大小、线程数、超时时间)。
  3. AOP切面(核心):拦截导入请求,自动计算数据量,根据配置路由到对应的处理器。

二、核心实现详解

1. 第一步:定义“导入策略”注解

这个注解用来标记 Service 层的导入方法,声明它能处理的数据量上限。

java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ImportStrategy {
    // 策略名称,对应配置文件中的策略Key
    String value() default "default"; 
    // 该策略支持的最大数据量(超过则自动降级或报错)
    long maxRows() default Long.MAX_VALUE; 
}

2. 第二步:在业务方法上使用注解

将你之前“万级、数万级、数十万级”的硬编码逻辑,拆分为三个独立的方法,并用注解标记。

java
@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 中,方便运维动态调整,无需修改代码。

yaml
# 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 注解的方法,自动识别数据量动态路由到对应的导入策略。

java
@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 注解,配置分批大小和异步过期时间。

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExportConfig {
    // 每页查询数量(用于游标分页)
    int fetchSize() default 1000;
    // 是否异步导出(针对超大文件)
    boolean async() default false;
    // 异步任务过期时间(分钟)
    int expireMinutes() default 30;
}

使用示例

java
@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.
Java 虚拟机从入门到精通