java的架构思想

在 Java 开发(尤其是 Spring/Spring Boot 生态)中,分层架构、依赖注入、配置文件是基础工程设计思想,而日志、异常、拦截器、AOP、事务是保障系统稳定性、可维护性和数据一致性的核心技术。本文将从概念作用、核心原理、使用场景三个维度逐一拆解,结合实际开发案例让你理解这些技术的本质和应用。

一、基础工程设计:分层、依赖注入、配置文件

这三者是 Java 后端项目架构规范工程化管理的核心,解决了代码耦合、硬编码、扩展性差的问题。

1. 分层架构:解耦代码,职责单一

分层架构是将系统按功能职责拆分为不同层级,每层只做自己的事,符合单一职责原则。Java 后端最经典的是四层架构(也有三层,合并 Service 和 DAO),Spring Boot 项目中最常用:

层级核心职责核心组件 / 技术常见包名
表现层(Controller)接收前端请求、返回响应,参数校验 / 请求分发@RestController、@RequestMappingcom.xxx.controller
业务层(Service)封装核心业务逻辑,事务控制@Service、业务接口 + 实现com.xxx.service
数据层(DAO/Mapper)与数据库交互,执行 CRUD 操作MyBatis Mapper、JPA Repositorycom.xxx.mapper
实体层(Entity/DTO)封装数据结构(数据库实体 / 传输对象)POJO、Lombok 注解com.xxx.entity/dto

作用与优势

  • 解耦:例如修改数据库访问方式(从 MySQL 到 MongoDB),只需改 DAO 层,不影响 Controller 和 Service;
  • 易维护:每层代码职责清晰,定位问题(如前端请求报错→先查 Controller)更高效;
  • 易扩展:例如新增支付业务,只需新增 PayController、PayService、PayMapper,不改动原有代码。

反例:如果把数据库操作、业务逻辑、请求处理全写在一个类中,代码会变成 “意大利面”,牵一发而动全身。

2. 依赖注入(DI):控制反转,解耦对象依赖

依赖注入是Spring 核心 IOC 容器的实现方式,解决了 “对象之间硬编码依赖” 的问题。

(1)核心概念
  • 依赖:如果 A 类需要调用 B 类的方法,那么 A 依赖 B;
  • 控制反转(IOC):原本由开发者手动new B()创建对象,改为由 Spring 容器统一创建和管理;
  • 依赖注入:Spring 容器将 A 依赖的 B 对象 “注入” 到 A 中(如通过属性、构造方法),开发者无需手动创建依赖对象。
(2)常见注入方式(Spring Boot)

java

运行

// 1. 构造方法注入(推荐,强制依赖)
@Service
public class UserService {
    private final UserMapper userMapper;
    
    // Spring自动注入UserMapper(需UserMapper被@Mapper注解)
    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
}

// 2. 属性注入(简化,适合非强制依赖)
@Service
public class OrderService {
    @Autowired // 自动注入
    private OrderMapper orderMapper;
}

// 3. Setter注入(适合可选依赖)
@Service
public class ProductService {
    private ProductMapper productMapper;
    
    @Autowired
    public void setProductMapper(ProductMapper productMapper) {
        this.productMapper = productMapper;
    }
}

作用

  • 解耦:服务之间不再通过new硬编码依赖,而是通过 Spring 容器管理,更换实现类只需修改配置;
  • 便于测试:可以轻松注入 Mock 对象(如用 Mockito 模拟 UserMapper),无需依赖真实数据库;
  • 单例管理:Spring 默认将 Bean 设为单例,减少对象创建的性能开销。
3. 配置文件:抽离可变参数,避免硬编码

配置文件用于存储项目中的可变参数(如数据库连接、端口号、第三方接口地址),将代码与配置分离,避免修改参数时重新编译代码。

(1)Java 常见配置文件类型
类型格式适用场景示例
properties键值对简单配置(无嵌套)server.port=8080
yml/yaml层级嵌套复杂配置(如多环境)server: port: 8080
xml标签嵌套传统 Spring 配置(少用)<bean id="..." />
(2)Spring Boot 中使用配置
  1. 编写 application.yml

yaml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456
  # 多环境配置:application-dev.yml、application-prod.yml
  profiles:
    active: dev # 激活开发环境
# 自定义配置
app:
  upload:
    path: /usr/local/upload
    max-size: 10MB
  1. 在代码中读取配置

java

运行

// 方式1:@Value注解读取单个配置
@RestController
public class UploadController {
    @Value("${app.upload.path}")
    private String uploadPath;
}

// 方式2:@ConfigurationProperties绑定配置类(推荐复杂配置)
@Data
@Component
@ConfigurationProperties(prefix = "app.upload")
public class UploadProperties {
    private String path;
    private String maxSize;
}

作用

  • 环境隔离:开发、测试、生产环境使用不同配置,无需修改代码;
  • 动态调整:例如修改数据库连接地址,只需改配置文件,无需重新部署(配合配置中心如 Nacos 更灵活);
  • 统一管理:所有可变参数集中存储,便于维护。

二、核心技术:日志、异常、拦截器、AOP、事务

这五项技术是 Java 后端保障系统稳定性、可观测性、数据一致性的关键,解决了 “如何排查问题”“如何处理错误”“如何统一控制流程”“如何保证数据正确” 的问题。

1. 日志:系统的 “黑匣子”,记录运行轨迹

日志是记录系统运行状态的文本 / 文件,用于排查问题、监控系统、审计操作。Java 中最常用的日志框架是SLF4J(门面)+ Logback(实现)(Spring Boot 默认),替代了传统的 Log4j。

(1)核心用法(Spring Boot)
  1. 注入日志对象

java

运行

@Service
public class UserService {
    // SLF4J门面,底层由Logback实现
    private static final Logger log = LoggerFactory.getLogger(UserService.class);
    
    public User getUserById(Long id) {
        log.info("开始查询用户,id:{}", id); // 信息日志
        try {
            User user = userMapper.selectById(id);
            log.debug("查询结果:{}", user); // 调试日志(开发环境可见)
            return user;
        } catch (Exception e) {
            log.error("查询用户失败,id:{}", id, e); // 错误日志(记录异常栈)
            throw e;
        }
    }
}
  1. 日志级别(从低到高)
    • TRACE:最细粒度,记录所有细节(极少用);
    • DEBUG:调试信息(开发环境开启);
    • INFO:正常运行信息(如 “服务启动成功”);
    • WARN:警告信息(如 “连接超时,重试中”);
    • ERROR:错误信息(如 “数据库连接失败”);
    • FATAL:致命错误(系统崩溃,极少用)。

作用

  • 问题排查:通过日志定位异常原因(如用户登录失败→查 ERROR 日志);
  • 系统监控:结合 ELK(Elasticsearch+Logstash+Kibana)分析日志,监控接口调用量、报错率;
  • 操作审计:记录敏感操作(如 “管理员删除了用户”),便于追溯责任。
2. 异常:统一处理错误,保证系统健壮性

异常是程序运行时的错误(如空指针、数据库主键冲突),Java 通过异常体系捕获和处理错误,避免系统崩溃。Spring Boot 中常用全局异常处理器统一处理所有异常,返回友好的前端响应。

(1)Java 异常体系核心分类
  • 受检异常(Checked Exception):编译时必须处理(如 IOExcepiton);
  • 非受检异常(Unchecked Exception):运行时异常(如 NullPointerException、IllegalArgumentException),无需编译时处理。
(2)Spring Boot 全局异常处理

java

运行

// 全局异常处理器
@RestControllerAdvice // 对所有@RestController生效
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    // 处理自定义业务异常
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException e) {
        log.warn("业务异常:{}", e.getMessage());
        return Result.fail(e.getCode(), e.getMessage());
    }

    // 处理所有运行时异常
    @ExceptionHandler(RuntimeException.class)
    public Result<?> handleRuntimeException(RuntimeException e) {
        log.error("运行时异常", e);
        return Result.fail(500, "服务器内部错误");
    }

    // 处理所有异常(兜底)
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.fail(500, "系统繁忙,请稍后再试");
    }
}

// 自定义业务异常
@Data
public class BusinessException extends RuntimeException {
    private int code;
    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }
}

作用

  • 避免系统崩溃:捕获异常后可优雅处理(如返回错误信息),而非直接抛出给 JVM 导致程序终止;
  • 统一响应格式:所有异常返回相同的 JSON 结构(如{code:500, msg:"错误信息"}),便于前端处理;
  • 分类处理错误:业务异常(如 “用户不存在”)返回友好提示,系统异常(如空指针)返回通用提示,避免暴露敏感信息。
3. 拦截器(Interceptor):拦截请求,统一处理横切逻辑

拦截器是 Spring MVC 提供的请求拦截机制,用于在请求到达 Controller 前后执行统一逻辑(如登录校验、接口限流、日志记录)。

(1)拦截器核心实现步骤
  1. 定义拦截器

java

运行

@Component
public class LoginInterceptor implements HandlerInterceptor {
    // 请求到达Controller前执行(核心)
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 从请求头获取token
        String token = request.getHeader("token");
        if (token == null || !"valid_token".equals(token)) {
            // 未登录,返回401
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(JSON.toJSONString(Result.fail(401, "未登录")));
            return false; // 拦截请求,不继续执行
        }
        return true; // 放行请求
    }

    // 请求处理完成后(Controller执行完)执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    // 整个请求完成后(视图渲染完)执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
  1. 注册拦截器

java

运行

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // 拦截所有请求
                .excludePathPatterns("/login", "/register"); // 排除登录/注册接口
    }
}

作用

  • 统一请求预处理:如登录校验、权限校验、参数过滤;
  • 统一请求后处理:如记录接口响应时间、添加响应头;
  • 简化代码:将重复逻辑(如所有接口的登录校验)抽离到拦截器,避免在每个 Controller 中重复编写。
4. AOP:面向切面编程,处理全局横切逻辑

AOP(Aspect-Oriented Programming)是面向切面编程,用于抽离系统中跨多个模块的重复逻辑(如日志、事务、权限),通过动态代理在不修改原有代码的前提下增强功能。

拦截器只能拦截 Controller 的请求,而 AOP 可以拦截所有 Bean 的方法(如 Service、Mapper),适用范围更广。

(1)AOP 核心概念
  • 切面(Aspect):封装横切逻辑的类(如日志切面、事务切面);
  • 连接点(JoinPoint):程序执行的某个节点(如方法调用、异常抛出);
  • 切入点(Pointcut):匹配连接点的规则(如指定拦截哪个包下的方法);
  • 通知(Advice):切面的具体逻辑(如方法执行前 / 后 / 异常时执行的代码)。
(2)AOP 实战(Spring Boot)
  1. 引入依赖(Spring Boot 已内置):

xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 实现日志切面

java

运行

@Aspect // 标记为切面
@Component
public class LogAspect {
    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    // 切入点:拦截com.xxx.service包下的所有方法
    @Pointcut("execution(* com.xxx.service.*.*(..))")
    public void servicePointcut() {}

    // 前置通知:方法执行前执行
    @Before("servicePointcut()")
    public void before(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info("方法{}开始执行,参数:{}", methodName, Arrays.toString(args));
    }

    // 后置通知:方法执行后执行(无论是否异常)
    @After("servicePointcut()")
    public void after(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        log.info("方法{}执行结束", methodName);
    }

    // 返回通知:方法正常返回后执行
    @AfterReturning(value = "servicePointcut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info("方法{}返回结果:{}", methodName, result);
    }

    // 异常通知:方法抛出异常时执行
    @AfterThrowing(value = "servicePointcut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        String methodName = joinPoint.getSignature().getName();
        log.error("方法{}执行异常:{}", methodName, e.getMessage(), e);
    }

    // 环绕通知:覆盖方法执行的全流程(最灵活)
    @Around("servicePointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            // 执行原方法
            Object result = joinPoint.proceed();
            long time = System.currentTimeMillis() - start;
            log.info("方法{}执行耗时:{}ms", joinPoint.getSignature().getName(), time);
            return result;
        } catch (Throwable e) {
            long time = System.currentTimeMillis() - start;
            log.error("方法{}执行异常,耗时:{}ms", joinPoint.getSignature().getName(), time, e);
            throw e;
        }
    }
}

作用

  • 无侵入增强:在不修改原有业务代码的前提下,为方法添加日志、监控、权限等功能;
  • 统一管理横切逻辑:将跨模块的重复逻辑(如所有 Service 方法的耗时统计)抽离到切面,便于维护;
  • 灵活控制:通过切入点规则精准拦截指定方法,避免全局拦截的性能损耗。
5. 事务:保证数据一致性,解决并发 / 异常问题

事务是数据库操作的原子性执行单元,遵循ACID 原则(原子性、一致性、隔离性、持久性),用于保证多个数据库操作要么全部成功,要么全部失败。Spring Boot 中通过声明式事务(@Transactional)快速实现事务控制。

(1)事务核心特性(ACID)
  • 原子性(Atomicity):事务中的操作要么全做,要么全不做;
  • 一致性(Consistency):事务执行前后,数据库数据保持一致(如转账后总金额不变);
  • 隔离性(Isolation):多个事务并发执行时,互不干扰;
  • 持久性(Durability):事务提交后,数据修改永久保存到数据库。
(2)Spring Boot 声明式事务
  1. 基础用法

java

运行

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private ProductMapper productMapper;

    // 声明该方法为事务方法
    @Transactional(rollbackFor = Exception.class) // 所有异常都回滚(默认只回滚运行时异常)
    public void createOrder(Order order) {
        // 1. 插入订单
        orderMapper.insert(order);
        // 2. 扣减商品库存
        productMapper.decreaseStock(order.getProductId(), order.getNum());
        // 3. 若此处抛出异常,上述两步操作会全部回滚
        if (order.getAmount() < 0) {
            throw new BusinessException(400, "订单金额不能为负");
        }
    }
}
  1. 事务属性配置

java

运行

// 更细粒度的事务配置
@Transactional(
    isolation = Isolation.READ_COMMITTED, // 隔离级别:读已提交(解决脏读)
    propagation = Propagation.REQUIRED, // 传播行为:默认,当前无事务则新建,有则加入
    timeout = 30, // 超时时间:30秒
    readOnly = false, // 是否只读:false(增删改)
    rollbackFor = {BusinessException.class, SQLException.class}, // 指定回滚的异常
    noRollbackFor = {NullPointerException.class} // 指定不回滚的异常
)

作用

  • 保证数据一致性:如转账、下单等场景,避免部分操作成功导致数据错乱;
  • 简化事务管理:通过注解替代传统的 JDBC 事务代码(Connection.commit ()/rollback ());
  • 处理并发问题:通过隔离级别控制事务并发,避免脏读、不可重复读、幻读。

三、技术之间的关联与选型

  1. 拦截器 vs AOP:拦截器专注于Web 请求拦截,AOP 专注于所有 Bean 方法的横切逻辑,二者可配合使用(如拦截器做登录校验,AOP 做 Service 方法的日志);
  2. 异常处理 vs AOP:全局异常处理器处理 Controller 的异常,AOP 的异常通知可处理 Service/Mapper 的异常,二者结合实现全链路异常监控;
  3. 事务 vs AOP:Spring 的声明式事务本质是通过AOP 实现的,@Transactional 注解是一个特殊的切面,在方法执行前后开启 / 提交 / 回滚事务。

四、总结

  • 分层、依赖注入、配置文件是 Java 后端项目的基础工程规范,解决了代码耦合、硬编码、扩展性差的问题,是大型项目的必备设计;
  • 日志、异常、拦截器、AOP、事务是 Java 后端的核心保障技术,分别负责问题排查、错误处理、请求拦截、横切逻辑、数据一致性,是系统稳定运行的关键。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值