黑马JAVAWeb-03 SpringBootWeb-分层解耦-三层架构-@SpringBootApplication注解-IOC控制反转-DI依赖注入

1.三层架构

  • 在单层架构中,数据访问,逻辑处理和请求响应均放在同一类中

  • 三层架构
  • 以下按照 实体类 → Controller 层 → Service 层 → Dao 层 的顺序重新整理代码,包含 @RestController 及详细注解,基于 Spring Boot 框架规范实现:
  1. 实体类(User.java)
java 复制代码
import lombok.Data;

/**
 * 用户实体类(POJO)
 * 用于封装用户数据,作为各层之间的数据传输载体
 */
@Data  // Lombok注解:自动生成getter、setter、toString、equals等方法,简化代码
public class User {
    private Integer id;         // 用户唯一标识ID
    private String username;    // 用户名(登录账号)
    private String password;    // 密码(实际开发中需加密存储,如BCrypt加密)
    private String nickname;    // 用户昵称(显示用)
}
  1. Controller 层(UserController.java)
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * 用户控制层
 * 负责接收前端HTTP请求,调用Service层处理业务,返回JSON响应
 * @RestController = @Controller + @ResponseBody:标识为控制器且所有方法返回JSON
 */
@RestController
@RequestMapping("/api/v1/users")  // 接口统一前缀,用于版本控制和路径规划
public class UserController {

    /**
     * 依赖注入:自动从Spring容器中获取UserService实例
     * 无需手动new,降低耦合度,便于测试和扩展
     */
    @Autowired
    private UserService userService;

    /**
     * 根据用户ID查询用户信息
     * @param id 路径参数(用户ID),通过@PathVariable绑定
     * @return 包含用户数据的响应实体(状态码+数据)
     */
    @GetMapping("/{id}")  // 处理GET请求,路径为 /api/v1/users/{id}
    public ResponseEntity<User> getUserById(
            @PathVariable Integer id,  // 绑定URL路径中的{id}参数
            @RequestHeader(required = false) String token  // 可选:获取请求头中的token(用于鉴权)
    ) {
        // 实际开发中可在此处添加前置校验(如token验证)
        User user = userService.getUserById(id);
        // ResponseEntity封装响应:200状态码 + 用户数据
        return ResponseEntity.ok(user);
    }

    /**
     * 新增用户
     * @param user 请求体中的用户数据(JSON格式),通过@RequestBody绑定
     * @return 新增后的用户数据(包含自动生成的ID)
     */
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)  // 成功时响应201状态码(资源创建成功)
    public User createUser(@RequestBody User user) {  // 接收JSON请求体并转为User对象
        return userService.addUser(user);
    }

    /**
     * 全局异常处理:捕获参数非法异常
     * 统一处理异常,避免返回默认错误页面,提升用户体验
     */
    @ExceptionHandler(IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)  // 响应400状态码(参数错误)
    public String handleIllegalArgument(IllegalArgumentException e) {
        return "参数错误:" + e.getMessage();
    }

    /**
     * 全局异常处理:捕获用户不存在异常
     */
    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)  // 响应404状态码(资源不存在)
    public String handleUserNotFound(RuntimeException e) {
        return "错误:" + e.getMessage();
    }
}
    1. Service 层
      接口:UserService.java
java 复制代码
/**
 * 用户业务逻辑接口
 * 定义用户相关的业务操作规范,隔离Controller层与数据访问层
 */
public interface UserService {

    /**
     * 根据ID查询用户
     * @param id 用户ID
     * @return 用户实体(若不存在则抛出异常)
     */
    User getUserById(Integer id);

    /**
     * 新增用户
     * @param user 待新增的用户数据(不含ID,由数据库自动生成)
     * @return 新增后的用户数据(包含ID)
     */
    User addUser(User user);
}
    1. 实现类:UserServiceImpl.java
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 用户业务逻辑实现类
 * 处理核心业务逻辑(如参数校验、事务控制等),调用Dao层操作数据
 */
@Service  // Spring注解:标识为业务层组件,交由Spring容器管理
public class UserServiceImpl implements UserService {

    /**
     * 依赖注入:自动获取UserDao实例
     */
    @Autowired
    private UserDao userDao;

    @Override
    public User getUserById(Integer id) {
        // 业务校验:参数合法性判断
        if (id == null || id <= 0) {
            throw new IllegalArgumentException("用户ID必须为正整数");
        }
        // 调用Dao层查询数据
        User user = userDao.selectById(id);
        // 业务逻辑:用户不存在时抛出异常
        if (user == null) {
            throw new RuntimeException("用户不存在(ID:" + id + ")");
        }
        return user;
    }

    @Override
    public User addUser(User user) {
        // 业务校验:用户名不能为空
        if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        // 调用Dao层插入数据
        userDao.insert(user);
        // 返回插入后的用户(包含自动生成的ID)
        return user;
    }
}
    1. Dao 层(数据访问层)-注解的方式
java 复制代码
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

/**
 * 用户数据访问接口
 * 定义与数据库交互的方法,由MyBatis自动生成实现类
 */
@Mapper  // MyBatis注解:标识为数据访问接口,SpringBoot会自动扫描并创建代理对象
public interface UserDao {

    /**
     * 根据ID查询用户
     * @param id 用户ID
     * @return 用户实体(若不存在则返回null)
     */
    @Select("SELECT id, username, password, nickname FROM user WHERE id = #{id}")
    User selectById(Integer id);

    /**
     * 新增用户
     * @param user 待插入的用户数据(ID会由数据库自动生成)
     */
    @Insert("INSERT INTO user (username, password, nickname) VALUES (#{username}, #{password}, #{nickname})")
    void insert(User user);
}
    1. Dao 层(数据访问层) -接口-实现类的形式
      接口:UserDao.java
java 复制代码
import org.springframework.stereotype.Repository;

/**
 * 用户数据访问接口
 * 定义与用户相关的数据库操作规范(增删改查)
 * 接口仅声明方法,具体实现由实现类完成,便于更换数据库访问方式(如JDBC/MyBatis)
 */
@Repository  // Spring注解:标识为数据访问层组件,纳入容器管理(语义化注解,便于分层识别)
public interface UserDao {

    /**
     * 根据ID查询用户
     * @param id 用户ID
     * @return 匹配的用户实体,无数据则返回null
     */
    User selectById(Integer id);

    /**
     * 新增用户
     * @param user 待插入的用户数据(ID由数据库自动生成)
     * @return 受影响的行数(1表示成功,0表示失败)
     */
    int insert(User user);

    /**
     * 根据ID更新用户信息
     * @param user 包含更新信息的用户实体(必须包含ID)
     * @return 受影响的行数(1表示成功,0表示失败)
     */
    int updateById(User user);

    /**
     * 根据ID删除用户
     * @param id 用户ID
     * @return 受影响的行数(1表示成功,0表示失败)
     */
    int deleteById(Integer id);
}
    1. 实现类:(由 MyBatis 自动生成,无需手动编写)
      MyBatis 会通过接口和 XML 映射文件动态生成实现类,无需开发者手动编写实现代码。核心是通过 XML 文件定义 SQL 与接口方法的映射关系。
java 复制代码
Mapper XML 文件:UserDao.xml(放在 resources/mapper 目录下)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 
  namespace:必须与Dao接口的全类名一致,建立接口与XML的绑定关系
  即:接口包名.接口名 = com.example.dao.UserDao
-->
<mapper namespace="com.example.dao.UserDao">

    <!-- 定义结果集映射:数据库字段与实体类属性的映射关系(字段名与属性名不一致时必须配置) -->
    <resultMap id="BaseResultMap" type="com.example.entity.User">
        <id column="id" property="id"/>  <!-- 主键字段映射 -->
        <result column="username" property="username"/>  <!-- 普通字段映射 -->
        <result column="password" property="password"/>
        <result column="nickname" property="nickname"/>
    </resultMap>

    <!-- 
      select标签:对应查询操作
      id:必须与接口中的方法名一致(selectById)
      parameterType:参数类型(Integer)
      resultMap:引用上面定义的结果集映射(BaseResultMap)
    -->
    <select id="selectById" parameterType="java.lang.Integer" resultMap="BaseResultMap">
        SELECT id, username, password, nickname 
        FROM user 
        WHERE id = #{id}  <!-- #{id}:参数占位符,MyBatis自动处理SQL注入 -->
    </select>

    <!-- insert标签:对应新增操作 -->
    <insert id="insert" parameterType="com.example.entity.User" useGeneratedKeys="true" keyProperty="id">
        <!-- 
          useGeneratedKeys="true":开启自增主键获取
          keyProperty="id":将数据库生成的主键值设置到User对象的id属性中
        -->
        INSERT INTO user (username, password, nickname) 
        VALUES (#{username}, #{password}, #{nickname})
    </insert>

    <!-- update标签:对应更新操作 -->
    <update id="updateById" parameterType="com.example.entity.User">
        UPDATE user 
        SET username = #{username},
            password = #{password},
            nickname = #{nickname}
        WHERE id = #{id}  <!-- 条件:根据ID更新 -->
    </update>

    <!-- delete标签:对应删除操作 -->
    <delete id="deleteById" parameterType="java.lang.Integer">
        DELETE FROM user 
        WHERE id = #{id}
    </delete>

</mapper>



2.分层解耦

  • 高内聚和低耦合
  • 如何实现高内聚和低耦合?


    2.1 分层解耦的实现
  • "控制反转(IOC)" 和 "依赖注入(DI)"的核心概念,解释了如何通过这两个技术实现分层解耦 **。
  • 一、代码层的 "耦合问题"(未用 IOC/DI 时)
    先看右侧的 UserServiceImpl 类:
java 复制代码
public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl(); // 硬编码创建依赖
    // ...
}
这里 UserServiceImpl 直接 new UserDaoImpl(),意味着Service 层和 Dao 层 "强绑定"------ 
如果要换 Dao 的实现(比如从 UserDaoImpl 换成 UserDaoMockImpl 用于测试),
必须修改 UserServiceImpl 的代码,这就是高耦合。
  • 二、"控制反转(IOC)" 的核心思想
    "控制反转" ,体现了 "对象创建权的转移"
  • 原本由 UserServiceImpl 自己创建 UserDaoImpl(程序自身控制);
  • 现在把 "创建 UserDao 对象" 的控制权转移给外部容器(比如 Spring 容器)。
    这样,UserServiceImpl 不再关心 UserDao 是怎么创建的,只需要 "用" 即可 ------ 这就是 "控制反转"(Inversion of Control)。
  • 三、"依赖注入(DI)" 的落地方式
    再看左侧的 UserController 类:
java 复制代码
@RestController
public class UserController {
    private UserService userService; // 声明依赖,但不自己创建
    // ...
}
(比如 Spring)主动把 UserService 的实例 "注入" 到 UserController 中
------ 这就是 "依赖注入"(Dependency Injection)。
  • 以下是用 Spring Boot 框架实现 IOC/DI 分层解耦的最简代码案例,分 Controller、Service、Dao 三层 展示:
    1. 实体类(User.java)
java 复制代码
 public class User {
    private Integer id;
    private String name;

    // 构造方法、getter/setter 省略
    public User() {}
    public User(Integer id, String name) { this.id = id; this.name = name; }
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}
  1. Controller 层(控制层)
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;

@RestController // 标记为 Controller 层 Bean,同时支持 REST 接口
public class UserController {
    // 依赖注入:Spring 自动将 UserService 的 Bean 注入到这里 ->在这里进行了解耦
    @Autowired
    private UserService userService;

    @GetMapping("/users")
    public List<User> getAllUsers() {
        return userService.listUsers(); // 调用 Service 层方法
    }
}
    1. Service 层(业务逻辑层)
java 复制代码
import java.util.List;

public interface UserService {
    List<User> listUsers();
}
    1. 实现类:UserServiceImpl.java
java 复制代码
@Service // 标记为 Service 层 Bean,由 Spring 容器管理
public class UserServiceImpl implements UserService {

    // 依赖注入:Spring 自动将 UserDao 的 Bean 注入到这里  ->这里实现了解耦
    @Autowired
    private UserDao userDao;

    @Override
    public List<User> listUsers() {
        return userDao.findAll(); // 调用 Dao 层方法
    }
}
    1. Dao 层(数据访问层)
java 复制代码
@Repository // 标记为 Dao 层 Bean,由 Spring 容器管理
public class UserDao {
    // 模拟从数据库查询用户
    public List<User> findAll() {
        List<User> userList = new ArrayList<>();
        userList.add(new User(1, "张三"));
        userList.add(new User(2, "李四"));
        return userList;
    }
}
  • @Component 是 Spring 中最基础的注解,用于标记一个类为 "Spring 管理的组件(Bean)",让 Spring 容器自动创建并管理这个类的实例。
    • 它是 @Controller、@Service、@Repository 的 "父注解",这三个注解本质上都是 @Component 的特殊形式(只是语义不同,分别对应控制层、业务层、数据访问层)。
  • 解耦的核心就是,把各个实现类都打上注解@Conponent,变成Bean对象,通过@Autowire注入到对应的接口上;

    3.IOC 控制反转详解
  • IOC(控制反转,Inversion of Control)是一种软件设计思想,核心是将对象的创建、依赖管理和生命周期控制从代码内部转移到外部容器(如 Spring 容器),从而实现模块间的解耦
  • 一、核心思想:"控制权反转 "
    传统开发中,对象 A 若依赖对象 B,需要在 A 内部主动new B()来创建依赖;而在 IOC 思想中,A 不再主动创建 B,而是由外部容器(如 Spring)创建 B 并 "注入" 到 A 中------ 即 "对象的控制权从代码自身反转到外部容器"。
  • 二、实现方式:依赖注入(DI)是核心
    IOC 的实现手段主要是依赖注入(Dependency Injection),即容器在运行时将对象的依赖主动 "注入" 到需要的地方。
  • 三、IOC 容器:管理对象的 "管家"
    IOC 容器是实现 IOC 的核心载体(如 Spring 的ApplicationContext),它的核心职责是:
    • 对象创建:根据配置(注解、XML 等)创建 Bean 对象。
    • 依赖注入:解析对象的依赖关系,自动注入所需的 Bean。
    • 生命周期管理:控制 Bean 的初始化、销毁等过程,支持单例、原型等作用域。
  • IOC 的本质是 "把对象的控制权交给容器,专注于业务逻辑"。通过依赖注入实现解耦,让代码从 "主动创建依赖" 变为 "被动接收依赖",最终达成 "高内聚、低耦合" 的设计目标。这一思想是 Spring 等框架的核心

3.1组件扫描

  • 前面通过各种方式把实现类都变成了Bean,那要怎么让bean生效呢? 必须要被@ComponentScan注解扫描
  • @ComponentScan 是 Spring 框架中用于自动扫描并注册组件到 IOC 容器的核心注解
  • 一、核心作用
    自动扫描指定包及其子包下的类,将带有 @Component 及其派生注解(@Service、@Controller、@Repository)的类注册为 Spring Bean,纳入 IOC 容器管理。
  • 点进@SpringBootApplication注解
java 复制代码
1.指定扫描包
@ComponentScan(basePackages = "com.example.service") // 扫描该包及其子包
public class SpringBootWebApplication{
}

2.指定多包扫描
@ComponentScan(basePackages = {"com.example.service", "com.example.controller"})

3.高级过滤:包含 / 排除特定类
这段代码是 Spring Boot 中 @ComponentScan 注解的具体配置,主要用于自定义排除某些类的扫描,
核心是通过两个自定义过滤器(TypeExcludeFilter 和 AutoConfigurationExcludeFilter)
排除不需要注册为 Spring Bean 的类。
@ComponentScan(
    excludeFilters = {
        @Filter(
            type = FilterType.CUSTOM,  // 过滤器类型:自定义
            classes = {TypeExcludeFilter.class}  // 自定义过滤器类
        ),
        @Filter(
            type = FilterType.CUSTOM,
            classes = {AutoConfigurationExcludeFilter.class}  // 另一个自定义过滤器类
        )
    }
)
)
  1. (@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan)是 Spring Boot 的核心注解,共同构成了 @SpringBootApplication 注解的底层实现,也是 Spring Boot 实现 "自动配置" 和 "快速开发" 的关键。

4.1 @SpringBootConfiguration 实现自定义配置类

  • @SpringBootConfiguration 是 Spring Boot 对 @Configuration 的封装,用于定义自定义配置类,在类中可以通过 @Bean 注册自定义 Bean。
java 复制代码
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;

/**
 * 自定义配置类:演示 @SpringBootConfiguration 的作用
 * 功能:注册一个自定义工具类到 Spring 容器
 */
@SpringBootConfiguration  // 标识这是一个 Spring Boot 配置类(等价于 @Configuration)
public class CustomConfig {

    /**
     * 定义一个 Bean:创建 DateUtil 实例并交由 Spring 管理
     * @return DateUtil 实例
     */
    @Bean
    public DateUtil dateUtil() {
        return new DateUtil();  // Spring 容器会管理这个对象的生命周期
    }
}

// 测试类:验证自定义 Bean 的注入
@RestController
public class TestController {
    // 注入自定义配置类中注册的 DateUtil Bean
    @Autowired
    private CustomConfig.DateUtil dateUtil;
}
@SpringBootConfiguration:标记类为 Spring Boot 配置类,
Spring 会扫描其中的 @Bean 方法,将返回的对象注册为容器中的 Bean。

4.2 EnableAutoConfiguration 实现自动配置(模拟第三方 Starter)

  • @EnableAutoConfiguration 是 Spring Boot 自动配置的核心,它会根据项目依赖自动加载并配置相关组件。
  • @EnableAutoConfiguration 自动加载数据库的例子
    • 数据源自动配置:DataSourceAutoConfiguration 类会读取 application.yml 中的 spring.datasource 配置,自动创建 DataSource 实例

配置数据库连接(application.yml)

java 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC
    username: root
    password: your_password

核心逻辑说明

  • DataSourceAutoConfiguration 读取配置并创建 DataSource:该类会读取 application.yml 中 spring.datasource 前缀的配置,自动创建 DataSource 实例

4.3 小结

  • @EnableAutoConfiguration 是 "自动模式",根据依赖和配置自动生成环境所需的 Bean(如数据源、Web 组件);
  • @SpringBootConfiguration 是 "手动模式",允许开发者主动定义配置类,通过 @Bean 手动注册特定 Bean。

5.DI 依赖注入

  • 依赖注入(DI,Dependency Injection)是控制反转(IOC)的具体实现方式,核心是 "容器在运行时自动将对象所需的依赖(其他对象)传递给它,而不是由对象自己创建依赖"。
  • 以下是基于接口的依赖注入(DI)代码示例,通过 "接口定义 + 实现类 + 注入依赖" 的方式,体现低耦合的设计思想:
java 复制代码
1. 定义接口(抽象依赖)
// 用户服务接口(抽象行为定义)
public interface UserService {
    String getUserName(Long userId);
}
2. 实现接口(具体依赖)
// 接口的实现类(具体业务逻辑)
@Service // 注册为 Spring Bean,由容器管理
public class UserServiceImpl implements UserService {
    @Override
    public String getUserName(Long userId) {
        // 模拟从数据库查询
        return "用户" + userId + ":张三";
    }
}
3.// 构造器注入(推荐方式,强制依赖不可变)
    @Autowired // Spring 自动注入 UserService 的实现类(UserServiceImpl)
    public UserController(UserService userService) {
        this.userService = userService;
    }

关键说明
1.依赖接口而非实现:UserController 只依赖 UserService 接口,不直接依赖 UserServiceImpl,降低了耦合度。
2.灵活替换实现:若需要更换业务逻辑,只需新增一个实现类(如 UserServiceMockImpl),
并注册为 Bean,UserController 无需任何修改:

5.1 三种注入方式 - 主要还是用属性注入


相关推荐
微露清风3 小时前
系统性学习C++-第十讲-stack 和 quene
java·c++·学习
一蓑烟雨任平生√3 小时前
两种上传图片的方式——91张先生
java·ossinsight
凤凰战士芭比Q3 小时前
部署我的世界-java版服务器-frp内网穿透
java·服务器
小肖爱笑不爱笑3 小时前
2025/11/5 IO流(字节流、字符流、字节缓冲流、字符缓冲流) 计算机存储规则(ASCII、GBK、Unicode)
java·开发语言·算法
CodeCraft Studio3 小时前
PPT处理控件Aspose.Slides教程:使用Java将PowerPoint笔记导出为PDF
java·笔记·pdf·powerpoint·aspose·ppt转pdf·java将ppt导出pdf
手握风云-3 小时前
Java 数据结构第二十八期:反射、枚举以及 lambda 表达式
java·开发语言
ᐇ9593 小时前
Java Vector集合全面解析:线程安全的动态数组
java·开发语言
毕设源码-朱学姐4 小时前
【开题答辩全过程】以 广州网红点打卡介绍网站为例,包含答辩的问题和答案
java·eclipse
程序定小飞4 小时前
基于springboot的web的音乐网站开发与设计
java·前端·数据库·vue.js·spring boot·后端·spring