Spring三层架构

发布于:2025-06-18 ⋅ 阅读:(22) ⋅ 点赞:(0)

 

编者想说:

作为Java开发者,我们每天都在和Spring框架打交道,但你是否真正理解过Spring项目中表现层、业务逻辑层、数据访问层这三层架构的设计逻辑?为什么需要分层?各层的职责边界在哪里?Spring又是如何通过IOC和AOP支撑这种分层结构的?

本文将从底层设计思想出发,结合Spring核心特性(IOC/AOP),通过一个完整的用户登录场景,带你彻底掌握Spring三层架构的精髓。


一、为什么需要三层架构?从"面条代码"到工程化

在早期的Java Web开发中,我们经常能看到这样的代码:

// 混合了HTML拼接、数据库查询、业务逻辑的"万能Servlet"
public class UserServlet extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        
        // 直接连接数据库查询(数据访问逻辑)
        Connection conn = DriverManager.getConnection(...);
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("SELECT * FROM user WHERE username='" + username + "'");
        
        if (rs.next()) {
            String dbPassword = rs.getString("password");
            if (dbPassword.equals(password)) { // 密码校验(业务逻辑)
                req.setAttribute("msg", "登录成功");
                req.getRequestDispatcher("/success.jsp").forward(req, resp); // 视图渲染(表现逻辑)
            } else {
                req.setAttribute("msg", "密码错误");
                req.getRequestDispatcher("/login.jsp").forward(req, resp);
            }
        } else {
            req.setAttribute("msg", "用户不存在");
            req.getRequestDispatcher("/login.jsp").forward(req, resp);
        }
    }
}

这种表面"大而全"的代码其实里面存在的问题可是很严重的:

  • 可维护性差​:修改数据库连接方式需要改动所有Servlet
  • 可测试性低​:无法单独测试业务逻辑(依赖Servlet容器和数据库)
  • 协作效率低​:前端和后端开发人员需要频繁修改同一份代码

而我们的spring提出的三层架构,正是为了解决这些问题。它通过职责分离将复杂系统拆解为三个高内聚、低耦合的模块,让代码更易维护、扩展和测试。


二、Spring三层架构的核心定义与职责边界

Spring框架下的三层架构通常指:​表现层(Web层)、业务逻辑层(Service层)、数据访问层(DAO/Mapper层)​。各层的核心职责和Spring中的典型实现如下:

1. 表现层(Web Layer):用户交互的入口

核心职责​:处理HTTP请求/响应,负责参数校验、视图渲染(或JSON返回)、简单的参数转换。
Spring实现​:通过@Controller(返回视图)或@RestController(返回JSON)注解的类实现,配合@RequestMapping系列注解定义路由。

关键原则​:

  • 不包含业务逻辑(如密码加密、权限校验)
  • 不直接操作数据库(禁止出现JDBC/MyBatis代码)
  • 参数校验应尽量前置(如使用@Valid+JSR-380注解)

示例代码​:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService; // 注入Service层

    @PostMapping("/login")
    public Result login(@RequestBody @Valid LoginForm form) {
        // 仅做简单参数格式校验(如非空),复杂校验交给Service
        return userService.login(form.getUsername(), form.getPassword());
    }
}

2. 业务逻辑层(Service Layer):系统的核心大脑

核心职责​:封装核心业务逻辑(如交易流程、权限控制、事务管理),协调多个DAO完成复杂操作,处理业务异常。
Spring实现​:通过@Service注解的类实现,使用@Transactional管理事务,通过依赖注入调用DAO层。

关键原则​:

  • 包含业务规则(如"新用户首单9折")
  • 控制事务边界(通过@Transactional注解)
  • 处理业务异常(如"库存不足"需抛出自定义异常)
  • 避免直接暴露底层数据结构(返回DTO而非Entity)

示例代码​:

@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper; // 注入DAO层

    @Override
    public Result login(String username, String password) {
        // 1. 查询用户(调用DAO层)
        User user = userMapper.selectByUsername(username);
        if (user == null) {
            throw new BusinessException("用户不存在"); // 业务异常
        }

        // 2. 密码校验(业务逻辑)
        String encryptedPassword = BCrypt.hashpw(password, BCrypt.gensalt());
        if (!encryptedPassword.equals(user.getPassword())) {
            throw new BusinessException("密码错误");
        }

        // 3. 生成Token(附加业务操作)
        String token = JwtUtil.generateToken(user.getId());
        
        return Result.success(token);
    }
}

3. 数据访问层(DAO/Mapper Layer):数据库的翻译官

核心职责​:封装数据库操作(CRUD、复杂查询),处理SQL优化,隔离数据库方言(如MySQL/Oracle)。
Spring实现​:通过@Repository注解的接口(MyBatis Mapper)或JpaRepository(Spring Data JPA)实现,Spring自动处理JDBC连接和事务管理。

关键原则​:

  • 仅负责数据持久化(不包含业务逻辑)
  • 使用DTO/Entity与数据库表映射(避免直接操作VO)
  • 复杂查询建议使用SQL语句(而非HQL/JPQL),保证性能
  • 隔离数据库差异(如分页插件统一处理不同数据库的分页语法)

示例代码(MyBatis Mapper)​​:

@Mapper // MyBatis注解,Spring会自动生成实现类
public interface UserMapper {

    @Select("SELECT * FROM user WHERE username = #{username}")
    User selectByUsername(String username);

    @Insert("INSERT INTO user(username, password) VALUES(#{username}, #{password})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
}

三、以用户登录为例的请求流程

通过一个完整的用户登录流程,看三层如何协作:

  1. 表现层​:接收前端发送的JSON请求(LoginForm),通过@RequestBody反序列化。
  2. 表现层调用Service​:将参数传递给UserService.login(),不处理任何业务逻辑。
  3. Service层处理业务​:
    • 调用UserMapper.selectByUsername()查询用户(数据访问层)。
    • 校验密码(业务逻辑)。
    • 生成Token(附加业务操作)。
  4. Service返回结果​:将Result对象返回给表现层。
  5. 表现层响应前端​:将Result序列化为JSON,返回HTTP 200响应。

四、Spring分层的关键支撑技术

Spring框架通过以下核心技术,让三层架构的落地变得简单高效:

1. IOC(控制反转):解耦层间依赖

通过@Autowired注解,Spring容器自动将DAO层的实例注入到Service层,Service层的实例注入到Controller层,实现了依赖的自动管理。开发者无需手动new对象,避免了层间硬编码依赖。

2. AOP(面向切面):实现横切逻辑的解耦

通过AOP可以轻松实现日志记录、权限校验、事务管理等横切逻辑,避免将这些代码侵入三层核心业务中。例如:

@Aspect
@Component
public class LogAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object logService(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long cost = System.currentTimeMillis() - start;
        log.info("方法:{},耗时:{}ms", joinPoint.getSignature(), cost);
        return result;
    }
}

这段日志切面会自动拦截所有Service层方法的执行,无需在每个Service方法中手动添加日志代码。

3. 事务管理:Service层的天然屏障

Spring的@Transactional注解让事务管理变得极其简单。只需在Service方法上添加该注解,Spring会自动:

  • 开启事务(获取数据库连接,设置autoCommit=false)。
  • 执行方法内的数据库操作。
  • 若出现异常则回滚事务,否则提交事务。

五、常见误区与最佳实践

误区1:业务逻辑层"空心化"

很多新手会将本应属于Service层的业务逻辑写到Controller或DAO中,例如:

  • 在Controller中直接校验用户权限(应在Service中处理)。
  • 在DAO中拼接复杂业务SQL(如"查询用户并计算积分",应在Service中组合多个DAO调用)。

最佳实践​:Service层是业务逻辑的唯一载体,所有涉及"业务规则"的操作都应在此完成。

误区2:数据访问层"越权"操作

部分开发者会在Mapper中直接处理业务逻辑,例如:

// 错误示例:在Mapper中计算用户等级(业务逻辑)
@Select("SELECT * FROM user WHERE id = #{id}")
User selectWithLevel(@Param("id") Long id);

// Mapper XML中
<resultMap id="userWithLevel" type="User">
    <result column="id" property="id"/>
    <result column="username" property="username"/>
    <result property="level" column="score" 
            typeHandler="com.example.handler.LevelTypeHandler"/> <!-- 直接计算等级 -->
</resultMap>

最佳实践​:数据访问层仅负责数据的CRUD,业务相关的计算(如等级、状态转换)应在Service层完成。

最佳实践1:使用DTO隔离内外数据

表现层与前端交互使用VO(View Object),Service层与DAO层交互使用Entity,两者之间通过DTO(Data Transfer Object)转换,避免直接暴露数据库结构。

// VO(返回给前端)
public class UserVO {
    private String username;
    private String nickname;
    // getter/setter
}

// Entity(数据库映射)
@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String username;
    private String password; // 数据库有password字段,但VO中不暴露
    private String nickname;
}

// Service层转换
public UserVO getUserInfo(Long userId) {
    User user = userMapper.selectById(userId);
    return UserVO.builder()
        .username(user.getUsername())
        .nickname(user.getNickname())
        .build();
}

最佳实践2:合理使用异常体系

  • 业务异常​:继承RuntimeException(如UserNotFoundException),由全局异常处理器捕获并返回友好提示。
  • 系统异常​:如数据库连接失败,由Spring默认的事务管理回滚,并记录日志。
// 全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result handleBusinessException(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(DataAccessException.class)
    public Result handleDataAccessException(DataAccessException e) {
        log.error("数据库异常", e);
        return Result.error(500, "服务器繁忙,请稍后再试");
    }
}

六、总结:三层架构的未来与演进

三层架构是Java企业级开发的基石,尽管近年来DDD(领域驱动设计)、CQRS(命令查询职责分离)等模式逐渐兴起,但三层架构的思想(职责分离、低耦合)依然是底层支撑。

对于初学者,建议先熟练掌握三层架构的规范写法;对于进阶开发者,可以结合DDD优化领域模型的设计,但切记不要为了"追新"而忽视基础架构的重要性。

最后送大家一句话:​好的架构不是设计出来的,而是演进出来的。但无论怎么演进,清晰的职责划分永远是架构设计的第一原则。​


网站公告

今日签到

点亮在社区的每一天
去签到