分层解耦

三层架构

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

代码拆分的优势:

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

分层解耦概念

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

分层解耦1

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

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

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

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

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

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

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

IOC详解

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

  1. 组件扫描注解(最常用):
1
2
3
4
5
6
@Component          // 通用组件
@Service // 服务层组件
@Repository // 数据访问层组件
@Controller // 控制层组件
@RestController // REST 控制层组件
@Configuration // 配置类
  • 使用示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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:
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class AppConfig {

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

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

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

@Component
@Profile("dev")
public class DevService {
// 只在 dev 环境下创建
}
  1. Import 注解:
1
2
3
4
5
@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. 构造函数注入(推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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 方法注入(了解)
1
2
3
4
5
6
7
8
9
10
@Service
public class UserService {
private UserRepository userRepository;

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

// 任意方法注入
@Autowired
public void configure(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
  1. @Value 注入简单值
1
2
3
4
5
6
7
8
9
10
11
@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. 集合注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class BusinessService {

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

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

// 数组注入
@Autowired
private StringService[] serviceArray;
}
  1. 条件化注入
1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class UserService {

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

// 多个相同类型的 Bean 选择
@Autowired
@Qualifier("primaryUserRepository")
private UserRepository userRepository;
}
  1. Java 配置中的注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class AppConfig {

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

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

总结

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

注入冲突问题

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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**):

1
2
3
@Primary
@Service
public class A1TestImpl implements A { ... }

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

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

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

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

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

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

循环注入问题

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

循环注入的类型:

  1. 直接循环依赖
1
2
3
4
5
6
7
8
9
10
11
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}

@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
  1. 间接循环依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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. 重新设计架构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 将共同依赖提取到第三方服务
@Service
public class CommonService {
// 共同功能
}

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

@Service
public class ServiceB {
@Autowired
private CommonService commonService;
}
  1. 使用Setter注入
1
2
3
4
5
6
7
8
9
@Service
public class ServiceA {
private ServiceB serviceB;

@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
  1. 使用@Lazy注解
1
2
3
4
5
6
@Service
public class ServiceA {
@Autowired
@Lazy
private ServiceB serviceB;
}