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

1.三层架构

  • 在单层架构中,数据访问,逻辑处理和请求响应均放在同一类中
    在这里插入图片描述
    在这里插入图片描述
  • 三层架构
    在这里插入图片描述
  • 以下按照 实体类 → Controller 层 → Service 层 → Dao 层 的顺序重新整理代码,包含 @RestController 及详细注解,基于 Spring Boot 框架规范实现:
  1. 实体类(User.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)
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
/**
* 用户业务逻辑接口
* 定义用户相关的业务操作规范,隔离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
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 层(数据访问层)-注解的方式
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
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 与接口方法的映射关系。
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 类:
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 类:
@RestController
public class UserController {
private UserService userService; // 声明依赖,但不自己创建
// ...
}
(比如 Spring)主动把 UserService 的实例 “注入” 到 UserController 中
—— 这就是 “依赖注入”(Dependency Injection)。

在这里插入图片描述

  • 以下是用 Spring Boot 框架实现 IOC/DI 分层解耦的最简代码案例,分 Controller、Service、Dao 三层 展示:
    1. 实体类(User.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 层(控制层)
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 层(业务逻辑层)
import java.util.List;
public interface UserService {
List<User> listUsers();
  }
    1. 实现类:UserServiceImpl.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 层(数据访问层)
@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注解
    在这里插入图片描述
1.指定扫描包
@ComponentScan(basePackages = "com.example.service") // 扫描该包及其子包
public class SpringBootWebApplication{
}
2.指定多包扫描
@ComponentScan(basePackages = {"com.example.service", "com.example.controller"})
3.高级过滤:包含 / 排除特定类
这段代码是 Spring Boot@ComponentScan 注解的具体配置,主要用于自定义排除某些类的扫描,
核心是通过两个自定义过滤器(TypeExcludeFilterAutoConfigurationExcludeFilter)
排除不需要注册为 Spring Bean 的类。
@ComponentScan(
excludeFilters = {
@Filter(
type = FilterType.CUSTOM,  // 过滤器类型:自定义
classes = {TypeExcludeFilter.class}  // 自定义过滤器类
),
@Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}  // 另一个自定义过滤器类
)
}
)
)

在这里插入图片描述
4. (@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan)是 Spring Boot 的核心注解,共同构成了 @SpringBootApplication 注解的底层实现,也是 Spring Boot 实现 “自动配置” 和 “快速开发” 的关键。
4.1 @SpringBootConfiguration 实现自定义配置类

  • @SpringBootConfiguration 是 Spring Boot 对 @Configuration 的封装,用于定义自定义配置类,在类中可以通过 @Bean 注册自定义 Bean。
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)

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)代码示例,通过 “接口定义 + 实现类 + 注入依赖” 的方式,体现低耦合的设计思想:
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),
并注册为 BeanUserController 无需任何修改:

5.1 三种注入方式 - 主要还是用属性注入
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

posted @ 2025-12-02 08:17  gccbuaa  阅读(0)  评论(0)    收藏  举报