N

Norlcayn的博客

A text-focused Halo theme

  • 首页
  • 文章
  • 默认分类
  • 关于
主页 分层解耦
文章

分层解耦

发表于 2026-01-15 更新于 昨天
作者 Administrator
31~40 分钟 阅读

三层架构

  • controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
  • service:业务逻辑层,处理具体的业务逻辑
  • dao:数据访问层(Data Access Object)(持久层)。负责数据访问操作,包括数据的增删改查

代码拆分的优势:

  • 遵循单一职责原则,便于复用、后期维护

分层解耦概念

  • 耦合:衡量软件中各个层/各个模块的依赖关联程度
  • 内聚:软件中各个模块内部的功能联系
  • 软件设计原则:**高内聚低耦合**

分层解耦1

控制反转:Inversion Of Control,简称IOC。对象创建控制权由程序自身转移到外部(容器),这种思想称为控制反转

依赖注入:Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入

Bean对象:IOC容器中创建、管理的对象,称之为Bean

只有被标明了是Bean的类,才会在容器中创建对象

Spring 容器本质上是一个中间件,用于解耦类与类之间的依赖关系

实现分层解耦,就是将项目中的类交给IOC容器管理

当某个类需要对应的类的对象时,直接依赖容器为其提供

IOC详解

将类交给 Spring IOC 容器管理有以下几种主要方式:

  1. 组件扫描注解(最常用):
@Component          // 通用组件
@Service            // 服务层组件
@Repository         // 数据访问层组件
@Controller         // 控制层组件
@RestController     // REST 控制层组件
@Configuration      // 配置类
  • 使用示例:
@Component
public class UserService {
    // 会被自动扫描并管理
}

@Service
public class OrderService {
    // 会被自动扫描并管理
}

@Repository
public class UserRepository {
    // 会被自动扫描并管理
}

总结:

注解 说明 位置
@Component 声明bean的基础注解 不属于以下三类时,用此注解
@Controller @Component的衍生注解 标注在控制层类上
@RestController @Controller的衍生注解 标注在控制层类上,适用于Restful API风格
@Service @Component的衍生注解 标注在业务层类上
@Repository @Component的衍生注解 标注在数据访问层类上(由于与mybatis整合,用的较少)
@Mapper mybatis提供的注解 标注在数据访问层类上
@Configuration @Component的衍生注解 标注在配置类上

------以下方式了解即可------

  1. Java 配置类中的 @Bean:
@Configuration
public class AppConfig {

    @Bean
    public UserService userService() {
        return new UserService();
    }

    @Bean
    public OrderService orderService() {
        return new OrderService(userService()); // 可以注入依赖
    }
}
  1. 包扫描配置:
  • 启动类配置:
@SpringBootApplication
@ComponentScan(basePackages = {"com.norlcyan.service", "com.norlcyan.repository"})
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • 组件类:
package com.norlcyan.service;

@Service  // 在扫描路径内,会被自动发现
public class BusinessService {
    // ...
}
  1. XML 配置(较少使用):
<!-- applicationContext.xml -->
<beans>
    <bean id="userService" class="com.norlcyan.service.UserService"/>
    <bean id="orderService" class="com.norlcyan.service.OrderService"/>
</beans
  1. 条件化 Bean 创建:
@Component
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public class FeatureService {
    // 只在特定条件下创建
}

@Component
@Profile("dev")
public class DevService {
    // 只在 dev 环境下创建
}
  1. Import 注解:
@Configuration
@Import({UserService.class, OrderService.class})
public class ServiceConfig {
    // 批量导入配置类
}

最常用的两种方式:
@Component 及其衍生注解 - 适用于自开发的类
@Bean 注解 - 适用于第三方类或需要复杂配置的 Bean

查看容器中的所有Bean

图片演示:

Snipaste_2025-08-06_13-34-27

Snipaste_2025-08-06_13-37-42

如果在Actuator中将Bean展示为自定义的名称,可以通过@Component("自定义名称")的方式,其他衍生注解同理

IOC组件扫描

  • 前面声明bean的注解(@Component及其衍生注解),想要生效,还需要被组件扫描注解**@ComponentScan**扫描
  • 该注解虽然没有显式配置,但是实际上已经包含了启动类声明注解@SpringBootApplication中,默认扫描范围是启动类所在包及其子包

DI详解

依赖注入的方式有很多种,但是大部分都基于@Autowired这个注解

  1. 构造函数注入(推荐)
@Service
public class UserService {
    private final UserRepository userRepository;

    // 构造函数注入
    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

// --------------- 不通过依赖注入的方式如下,用于做对比 --------------- //
@Service
public class UserService {
    private UserRepository userRepository = new UserRepository();
}

优点:

  • 对象创建后状态不可变(final 字段)
  • 保证依赖不为 null
  • 便于单元测试和 Mock

当Spring框架启动时,会自动调用注入的构造方法为其需要的对象赋值

如果类中只有一个注入构造函数,可以省略@Autowired。但存在多个构造函数时,注入构造函数上的@Autowird是不能省略的

为了保持代码的一致性和可读性,建议所有注入构造函数上都加上@Autowired

  1. Setter 方法注入(了解)
@Service
public class UserService {
    private UserRepository userRepository;

    // Setter 注入
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
  1. 字段注入(最简单,适用于快速开发小型项目)
@Service
public class UserService {
    // 字段注入
    @Autowired
    private UserRepository userRepository;
}
  1. 方法注入
@Service
public class UserService {
    private UserRepository userRepository;

    // 任意方法注入
    @Autowired
    public void configure(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}
  1. @Value 注入简单值
@Component
public class ConfigService {
    @Value("${app.name}")
    private String appName;

    @Value("${app.version:1.0}")  // 带默认值
    private String version;

    @Value("#{systemProperties['java.home']}")  // SpEL 表达式
    private String javaHome;
}
  1. 集合注入
@Service
public class BusinessService {

    // List 注入
    @Autowired
    private List<StringService> stringServices;

    // Map 注入
    @Autowired
    private Map<String, StringService> stringServiceMap;

    // 数组注入
    @Autowired
    private StringService[] serviceArray;
}
  1. 条件化注入
@Service
public class UserService {

    // 可选注入(不存在时不报错)
    @Autowired(required = false)
    private OptionalService optionalService;

    // 多个相同类型的 Bean 选择
    @Autowired
    @Qualifier("primaryUserRepository")
    private UserRepository userRepository;
}
  1. Java 配置中的注入
@Configuration
public class AppConfig {

    @Bean
    public UserService userService(UserRepository userRepository) {
        // 参数自动注入
        return new UserService(userRepository);
    }

    @Bean
    public UserRepository userRepository() {
        return new DatabaseUserRepository();
    }
}

总结

注入方式 推荐程度 优点 缺点
构造函数注入 ⭐⭐⭐⭐⭐ 不可变、必需依赖、利于测试 循环依赖问题
Setter 注入 ⭐⭐⭐⭐ 灵活、可解决循环依赖 可能为 null
字段注入 ⭐⭐ 简单、适用于快速开发小型项目 不利于测试、不可变性差

注入冲突问题

  • @Autowired注解,默认是按照类型进行注入的

但是,如果出现以下情况:

public interface A {
String getUsers();
}

@Service
public class A1TestImpl implements A { ... }

@Service
public class A2TestImpl implements A { ... }

@Component
public class Test {
@Autowired
private A a;// 到底是A a = new A1TestImpl()呢,还是A a = new A2TestImpl()呢?
}

这时候,无法知道到底是注入A1TestImpl呢,还是注入A2TestImpl呢

所以会出现以下报错:

Snipaste_2025-08-06_14-22-17

解决方案一(**@Primary**):

@Primary
@Service
public class A1TestImpl implements A { ... }

@Primary主要用于将当前类的Bean提升优先级

解决方案二(**@Qulifier**):

@Component
public class Test {
@Autowired
@Qualifier("a2TestImpl")// 指定注入的Bean的名字,需要搭配@Autowired使用
private A a;
}

解决方案三(**@Resource**):

@Component
public class Test {
@Resource("a2TestImpl")// 指定注入的Bean的名字,使用这个注解后可以省略@Autowired
private A a;
}

Bean默认命名规则:类名首字母小写

循环注入问题

  • 循环注入(Circular Injection/Circular Dependency)是指两个或多个Bean之间相互依赖,形成一个循环依赖关系
  • 想要了解项目是否存在循环注入问题,可以通过日志信息、Actuator监控等方式

循环注入的类型:

  1. 直接循环依赖
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}
  1. 间接循环依赖
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired
    private ServiceC serviceC;
}

@Service
public class ServiceC {
    @Autowired
    private ServiceA serviceA;
}

解决方案:

  1. 重新设计架构
// 将共同依赖提取到第三方服务
@Service
public class CommonService {
    // 共同功能
}

@Service
public class ServiceA {
    @Autowired
    private CommonService commonService;
}

@Service
public class ServiceB {
    @Autowired
    private CommonService commonService;
}
  1. 使用Setter注入
@Service
public class ServiceA {
    private ServiceB serviceB;

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}
  1. 使用@Lazy注解
@Service
public class ServiceA {
    @Autowired
    @Lazy
    private ServiceB serviceB;
}
后端 Java Web入门 SpringBoot IOC/DI
许可协议:  CC BY 4.0
分享

相关文章

下一篇

JDBC

上一篇

HTTP协议基础知识

最近更新

  • Hello Halo
  • 状态机
  • 数据表设计规范
  • 缓存优化
  • 事务失效

热门标签

Halo 异常处理 Redisson 并行 Vue 并发 递归 请求限流 SpringAMQP 类加载器

目录

©2026 Norlcayn的博客. 保留部分权利。

使用 Halo 主题 Chirpy