黑马JAVAWeb-03 SpringBootWeb-分层解耦-三层架构-@SpringBootApplication注解-IOC控制反转-DI依赖注入
1.三层架构
- 在单层架构中,数据访问,逻辑处理和请求响应均放在同一类中


- 三层架构

- 以下按照 实体类 → Controller 层 → Service 层 → Dao 层 的顺序重新整理代码,包含 @RestController 及详细注解,基于 Spring Boot 框架规范实现:
- 实体类(User.java)
import lombok.Data;/*** 用户实体类(POJO)* 用于封装用户数据,作为各层之间的数据传输载体*/
@Data // Lombok注解:自动生成getter、setter、toString、equals等方法,简化代码
public class User {private Integer id; // 用户唯一标识IDprivate String username; // 用户名(登录账号)private String password; // 密码(实际开发中需加密存储,如BCrypt加密)private String nickname; // 用户昵称(显示用)
}
- 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,降低耦合度,便于测试和扩展*/@Autowiredprivate 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();}
}
-
- Service 层
接口:UserService.java
- Service 层
/*** 用户业务逻辑接口* 定义用户相关的业务操作规范,隔离Controller层与数据访问层*/
public interface UserService {/*** 根据ID查询用户* @param id 用户ID* @return 用户实体(若不存在则抛出异常)*/User getUserById(Integer id);/*** 新增用户* @param user 待新增的用户数据(不含ID,由数据库自动生成)* @return 新增后的用户数据(包含ID)*/User addUser(User user);
}
-
- 实现类:UserServiceImpl.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** 用户业务逻辑实现类* 处理核心业务逻辑(如参数校验、事务控制等),调用Dao层操作数据*/
@Service // Spring注解:标识为业务层组件,交由Spring容器管理
public class UserServiceImpl implements UserService {/*** 依赖注入:自动获取UserDao实例*/@Autowiredprivate UserDao userDao;@Overridepublic 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;}@Overridepublic User addUser(User user) {// 业务校验:用户名不能为空if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {throw new IllegalArgumentException("用户名不能为空");}// 调用Dao层插入数据userDao.insert(user);// 返回插入后的用户(包含自动生成的ID)return user;}
}
-
- 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);
}
-
- Dao 层(数据访问层) -接口-实现类的形式
接口:UserDao.java
- Dao 层(数据访问层) -接口-实现类的形式
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);
}
-
- 实现类:(由 MyBatis 自动生成,无需手动编写)
MyBatis 会通过接口和 XML 映射文件动态生成实现类,无需开发者手动编写实现代码。核心是通过 XML 文件定义 SQL 与接口方法的映射关系。
- 实现类:(由 MyBatis 自动生成,无需手动编写)
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 三层 展示:
-
- 实体类(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; }
}
- 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 注入到这里 ->在这里进行了解耦@Autowiredprivate UserService userService;@GetMapping("/users")public List<User> getAllUsers() {return userService.listUsers(); // 调用 Service 层方法}
}
-
- Service 层(业务逻辑层)
import java.util.List;public interface UserService {List<User> listUsers();
}
-
- 实现类:UserServiceImpl.java
@Service // 标记为 Service 层 Bean,由 Spring 容器管理
public class UserServiceImpl implements UserService {// 依赖注入:Spring 自动将 UserDao 的 Bean 注入到这里 ->这里实现了解耦@Autowiredprivate UserDao userDao;@Overridepublic List<User> listUsers() {return userDao.findAll(); // 调用 Dao 层方法}
}
-
- 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 注解的具体配置,主要用于自定义排除某些类的扫描,
核心是通过两个自定义过滤器(TypeExcludeFilter 和 AutoConfigurationExcludeFilter)
排除不需要注册为 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 实例*/@Beanpublic DateUtil dateUtil() {return new DateUtil(); // Spring 容器会管理这个对象的生命周期}
}// 测试类:验证自定义 Bean 的注入
@RestController
public class TestController {// 注入自定义配置类中注册的 DateUtil Bean@Autowiredprivate 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=UTCusername: rootpassword: 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 {@Overridepublic 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 三种注入方式 - 主要还是用属性注入



