面试题二:业务篇
一:如果给你一个业务,谈谈你会如何实现:
作为Java开发工程师,我会通过以下结构化流程实现业务需求,结合主流技术栈确保高效、可维护和可扩展性:
1️⃣ 需求分析与拆解
- 明确核心逻辑:与产品经理对齐业务场景(如电商下单、数据报表等),识别关键实体(订单、用户)和流程边界
- 非功能性需求:并发量(QPS)、数据一致性要求、响应时间(如<200ms)
- 输出:UML时序图 + 接口契约(Swagger)
2️⃣ 技术选型分层设计
graph LR
A[接入层] --> B[业务层]
B --> C[数据层]
- 接入层:SpringMVC(RESTful API)或Spring WebFlux(高并发场景)
- 业务层:
- 领域模型:DDD战术设计(Entity/Aggregate/Repository)
- 事务控制:
@Transactional
+ 传播机制(如REQUIRED)
- 数据层:
- ORM:MyBatis-Plus(简化CRUD) + 动态数据源(读写分离)
- 缓存:Redis缓存热点数据(注解
@Cacheable
) - 消息队列:RocketMQ解耦异步操作(如订单状态通知)
3️⃣ 关键代码实现
示例:订单支付流程
@Service
public class OrderServiceImpl {// 使用策略模式处理支付渠道@Autowiredprivate Map<String, PaymentStrategy> paymentStrategies;@Transactional(rollbackFor = Exception.class)public void payOrder(String orderId, String paymentType) {Order order = orderRepository.selectById(orderId);PaymentStrategy strategy = paymentStrategies.get(paymentType);strategy.pay(order); // 抽象支付行为orderRepository.updateStatus(orderId, OrderStatus.PAID);rocketMQTemplate.send("order_topic", new OrderPaidEvent(orderId)); // 领域事件}
}
技术亮点:
- 策略模式避免
if-else
分支 - 事务注解保障数据一致性
- MQ事件驱动解耦
4️⃣ 防御性编程与监控
- 校验:使用Hibernate Validator验证参数(如
@NotBlank
) - 异常处理:全局异常处理器(
@ControllerAdvice
)返回标准化错误码 - 日志:SLF4J + ELK日志采集(关键链路MDC追踪)
- 监控:Prometheus + Grafana监控JVM与接口性能
5️⃣ 持续交付优化
- 单元测试:JUnit5 + Mockito(核心逻辑覆盖率>80%)
- 集成测试:Testcontainers模拟数据库/中间件
- CI/CD:Jenkins流水线自动构建Docker镜像
- 压测:JMeter全链路压测(提前发现瓶颈)
💡 可维护性建议
- 模块化:Maven多模块拆分(API/Service/Dao独立)
- 配置中心:Nacos动态管理配置(如线程池参数)
- 代码规范:Checkstyle + Git Hooks强制代码风格
✨ 核心原则:
“先设计后编码,重抽象轻耦合” —— 通过分层和模式设计提升系统弹性,避免过度设计。每个方法保持单一职责(不超过50行),善用Java Stream API简化集合操作(如list.stream().filter(...).map(...)
),让代码成为可读的文档!
二:项目中异常是如何处理的?
作为Java项目中的异常处理实践者,我会通过分层策略+全局管控+语义化封装的方式,实现可追溯、可维护、用户友好的异常处理流程。以下是具体实现方案:
一、先明确:异常的分类与处理边界
在项目中,我会先将异常分为三类,并制定不同的处理策略:
- 系统异常(如
NullPointerException
、OutOfMemoryError
):由程序逻辑错误或资源耗尽引起,需通过全局处理器捕获,返回500级错误并记录详细日志。 - 业务异常(如
UserNotFoundException
、OrderStatusException
):由业务规则违反引起(如用户不存在、订单已支付),需自定义异常,返回400级错误并给出具体业务提示。 - 外部异常(如
IOException
、HttpClientErrorException
):由外部系统调用失败引起(如文件读取失败、接口超时),需局部捕获(如重试、降级),或转换为业务异常抛出。
二、核心实现:全局异常处理器(@ControllerAdvice+@ExceptionHandler)
全局异常处理器是项目中异常处理的中枢,用于集中捕获所有未处理的异常,避免在每个Controller
中重复写try-catch
。实现步骤如下:
1. 定义全局异常处理类
使用@RestControllerAdvice
注解(组合了@ControllerAdvice
和@ResponseBody
),标记此类为全局异常处理器:
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {// 处理所有未捕获的异常(系统异常)@ExceptionHandler(Exception.class)public Result<?> handleGlobalException(HttpServletRequest request, Exception e) {log.error("系统异常:请求URL={}, 方法={}, 参数={}", request.getRequestURL(), request.getMethod(), request.getParameterMap(), e);return Result.fail("500", "服务器内部错误,请联系管理员");}// 处理自定义业务异常(如用户不存在)@ExceptionHandler(BusinessException.class)public Result<?> handleBusinessException(BusinessException e) {log.warn("业务异常:{}", e.getMessage()); // 警告级别,不记录堆栈(避免日志冗余)return Result.fail(e.getCode(), e.getMessage());}// 处理参数校验异常(如@NotBlank注解触发的BindException)@ExceptionHandler(BindException.class)public Result<?> handleBindException(BindException e) {String errorMsg = e.getBindingResult().getFieldErrors().stream().map(fieldError -> fieldError.getField() + ":" + fieldError.getDefaultMessage()).collect(Collectors.joining(";"));log.warn("参数校验失败:{}", errorMsg);return Result.fail("400", "参数错误:" + errorMsg);}
}
三、语义化:自定义业务异常
为了让异常更贴合业务场景,我会定义自定义业务异常,将业务逻辑错误封装为特定的异常类。例如:
// 基础业务异常类(所有业务异常的父类)
public class BusinessException extends RuntimeException {private String code; // 业务错误码(如"USER_NOT_FOUND")private String message; // 错误消息public BusinessException(String code, String message) {super(message);this.code = code;this.message = message;}// getter方法
}// 具体业务异常(用户不存在)
public class UserNotFoundException extends BusinessException {public UserNotFoundException(String userId) {super("USER_NOT_FOUND", "用户不存在,ID:" + userId);}
}// 具体业务异常(订单已支付)
public class OrderPaidException extends BusinessException {public OrderPaidException(String orderId) {super("ORDER_PAID", "订单已支付,ID:" + orderId);}
}
使用场景:在业务层判断到不符合规则的情况时,抛出自定义异常:
// 业务层示例(用户服务)
public User getUserById(String userId) {return userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException(userId)); // 抛出用户不存在异常
}
四、可追溯:日志记录与上下文保留
异常日志是排查问题的关键,我会通过SLF4J+Logback记录以下信息:
- 基础信息:异常类名、堆栈轨迹(
log.error
会自动记录)。 - 上下文信息:请求URL、HTTP方法、参数、用户ID(通过
MDC
(映射诊断上下文)传递)。 - 业务信息:如订单ID、商品ID等,方便定位具体业务场景。
示例:在拦截器或过滤器中,将用户ID放入MDC
:
public class UserInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从Token中解析用户ID(假设使用JWT)String userId = JwtUtil.getUserIdFromToken(request.getHeader("Authorization"));MDC.put("userId", userId); // 将用户ID放入MDCreturn true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {MDC.clear(); // 清理MDC,避免线程复用导致的脏数据}
}
日志配置(logback.xml
):
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - userId: %X{userId} - %msg%n</pattern></encoder>
</appender>
效果:日志中会包含用户ID,方便排查特定用户的问题:
2025-09-22 08:30:19.123 [http-nio-8080-exec-1] WARN com.example.service.UserService - userId: 123 - 用户不存在,ID:456
五、友好性:统一错误响应格式
为了让前端能快速处理错误,我会定义统一的错误响应格式,例如:
// 统一返回结果类
public class Result<T> {private String code; // 错误码(200:成功;400:参数错误;500:服务器错误;业务错误码:如"USER_NOT_FOUND")private String message; // 错误消息private T data; // 响应数据(成功时返回)// 私有构造函数(通过静态方法创建对象)private Result(String code, String message, T data) {this.code = code;this.message = message;this.data = data;}// 成功响应(带数据)public static <T> Result<T> success(T data) {return new Result<>("200", "成功", data);}// 成功响应(无数据)public static Result<?> success() {return new Result<>("200", "成功", null);}// 失败响应(带错误码和消息)public static Result<?> fail(String code, String message) {return new Result<>(code, message, null);}// getter方法
}
效果:前端收到的错误响应会是这样的JSON:
{"code": "USER_NOT_FOUND","message": "用户不存在,ID:456","data": null
}
六、最佳实践:避免踩坑
- 不要吞异常:
catch
异常后,要么处理(如重试、降级),要么抛出(让全局处理器处理),不要空catch
(catch (Exception e) {}
)。 - 不要滥用try-catch:只在需要的地方捕获异常(如资源关闭、外部接口调用),其他地方让全局处理器处理。例如:
// 正确:用try-with-resources处理资源(自动关闭) public String readFile(String filePath) throws IOException {try (FileReader fr = new FileReader(filePath);BufferedReader br = new BufferedReader(fr)) {return br.lines().collect(Collectors.joining("\n"));}// 不需要catch IOException,让上层处理(或全局处理器处理) }
- 区分业务异常与系统异常:业务异常(如用户不存在)返回400级错误,系统异常(如数据库连接失败)返回500级错误,避免前端将业务错误当作系统错误处理。
- 异常消息要具体:包含上下文信息(如用户ID、订单ID),方便排查问题。例如:
throw new UserNotFoundException("用户不存在,ID:" + userId)
,而不是throw new UserNotFoundException("用户不存在")
。
总结
项目中的异常处理核心逻辑是:分类处理+集中管控+语义化封装+友好响应。通过全局异常处理器减少重复代码,用自定义异常提高业务可读性,用日志记录保证可追溯性,用统一响应提升用户体验。最终目标是让异常处理既高效又可维护,让开发人员能快速定位问题,让用户能得到清晰的错误提示。✨