分层解耦
三层架构
- controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
- service:业务逻辑层,处理具体的业务逻辑
- dao:数据访问层(Data Access Object)(持久层)。负责数据访问操作,包括数据的增删改查
代码拆分的优势:
- 遵循单一职责原则,便于复用、后期维护
分层解耦概念
- 耦合:衡量软件中各个层/各个模块的依赖关联程度
- 内聚:软件中各个模块内部的功能联系
- 软件设计原则:**高内聚低耦合**

控制反转:Inversion Of Control,简称IOC。对象创建控制权由程序自身转移到外部(容器),这种思想称为控制反转
依赖注入:Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入
Bean对象:IOC容器中创建、管理的对象,称之为Bean
只有被标明了是Bean的类,才会在容器中创建对象
Spring 容器本质上是一个中间件,用于解耦类与类之间的依赖关系
实现分层解耦,就是将项目中的类交给IOC容器管理
当某个类需要对应的类的对象时,直接依赖容器为其提供
IOC详解
将类交给 Spring IOC 容器管理有以下几种主要方式:
- 组件扫描注解(最常用):
@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的衍生注解 | 标注在配置类上 |
------以下方式了解即可------
- Java 配置类中的 @Bean:
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
@Bean
public OrderService orderService() {
return new OrderService(userService()); // 可以注入依赖
}
}
- 包扫描配置:
- 启动类配置:
@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 {
// ...
}
- XML 配置(较少使用):
<!-- applicationContext.xml -->
<beans>
<bean id="userService" class="com.norlcyan.service.UserService"/>
<bean id="orderService" class="com.norlcyan.service.OrderService"/>
</beans
- 条件化 Bean 创建:
@Component
@ConditionalOnProperty(name = "feature.enabled", havingValue = "true")
public class FeatureService {
// 只在特定条件下创建
}
@Component
@Profile("dev")
public class DevService {
// 只在 dev 环境下创建
}
- Import 注解:
@Configuration
@Import({UserService.class, OrderService.class})
public class ServiceConfig {
// 批量导入配置类
}
最常用的两种方式:
@Component 及其衍生注解 - 适用于自开发的类
@Bean 注解 - 适用于第三方类或需要复杂配置的 Bean
查看容器中的所有Bean
图片演示:


如果在Actuator中将Bean展示为自定义的名称,可以通过
@Component("自定义名称")的方式,其他衍生注解同理
IOC组件扫描
- 前面声明bean的注解(@Component及其衍生注解),想要生效,还需要被组件扫描注解**@ComponentScan**扫描
- 该注解虽然没有显式配置,但是实际上已经包含了启动类声明注解@SpringBootApplication中,默认扫描范围是启动类所在包及其子包
DI详解
依赖注入的方式有很多种,但是大部分都基于@Autowired这个注解
- 构造函数注入(推荐)
@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
- Setter 方法注入(了解)
@Service
public class UserService {
private UserRepository userRepository;
// Setter 注入
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
- 字段注入(最简单,适用于快速开发小型项目)
@Service
public class UserService {
// 字段注入
@Autowired
private UserRepository userRepository;
}
- 方法注入
@Service
public class UserService {
private UserRepository userRepository;
// 任意方法注入
@Autowired
public void configure(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
- @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;
}
- 集合注入
@Service
public class BusinessService {
// List 注入
@Autowired
private List<StringService> stringServices;
// Map 注入
@Autowired
private Map<String, StringService> stringServiceMap;
// 数组注入
@Autowired
private StringService[] serviceArray;
}
- 条件化注入
@Service
public class UserService {
// 可选注入(不存在时不报错)
@Autowired(required = false)
private OptionalService optionalService;
// 多个相同类型的 Bean 选择
@Autowired
@Qualifier("primaryUserRepository")
private UserRepository userRepository;
}
- 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呢
所以会出现以下报错:

解决方案一(**@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监控等方式
循环注入的类型:
- 直接循环依赖
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
- 间接循环依赖
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceC serviceC;
}
@Service
public class ServiceC {
@Autowired
private ServiceA serviceA;
}
解决方案:
- 重新设计架构
// 将共同依赖提取到第三方服务
@Service
public class CommonService {
// 共同功能
}
@Service
public class ServiceA {
@Autowired
private CommonService commonService;
}
@Service
public class ServiceB {
@Autowired
private CommonService commonService;
}
- 使用Setter注入
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
- 使用@Lazy注解
@Service
public class ServiceA {
@Autowired
@Lazy
private ServiceB serviceB;
}