分层解耦
三层架构
在web开发中,一般有三层结构,从第一层到第三层分别为控制层(controller)、业务逻辑层(service)、数据访问层(dao)
controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。
service:业务逻辑层,处理具体的业务逻辑。
dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、改、查。
三层拆分前的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@RestController //@Controller + @ResponseBody -----> 如果返回的是一个对象/集合 --> 转json --> 响应
public class UserController {
@RequestMapping("/list")
public List<User> list(){
//1. 读取user.txt中的数据
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
ArrayList<String> lines = IoUtil.readUtf8Lines(in, new ArrayList<>());
//2. 业务逻辑处理: 解析数据, 封装User对象 --> List<User>
List<User> userList = lines.stream().map(line -> {
String[] split = line.split(",");
return new User(
Integer.parseInt(split[0]),
split[1],
split[2],
split[3],
Integer.parseInt(split[4]),
LocalDateTime.parse(split[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}).toList();
//3. 响应数据
return userList;
}
|
三层拆分后的代码:

IOC与DI入门
如果按照刚刚Controller层中用new对象获取Service的方法,即
1
|
private UserService userService = new UserServiceImpl();
|
按照这种形式获取的话,如果有多个实现类需要切换就得频繁修改代码,为了解决这个问题,spring提供了控制反转和依赖注入
控制反转: Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转
依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入
Bean对象:IOC容器中创建、管理的对象,称之为Bean
所以要将DAO和Service层的实现类交给IOC容器管理,并为Controller和Service注入运行时所依赖的对象
要做到上述操作只需要在实现类上方加上@Component,在Service和DAO成员变量上方加上@Autowired即可,例如
1
2
3
4
5
6
7
|
@Component //将当前类交给spring管理, 声明为spring容器中bean对象
public class UserDaoImpl implements UserDao
-----------------------------------------------
@Autowired //应用程序运行时,会自动查询该类型的bean对象,并赋值给该成员变量
private UserService userService;
|
IOC详解
常见Bean注解
要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一
| 注解 |
说明 |
位置 |
| @Component |
声明 bean 的基础注解 |
不属于以下三类时,用此注解 |
| @Controller |
@Component 的衍生注解 |
标注在控制层类上 |
| @Service |
@Component 的衍生注解 |
标注在业务层类上 |
| @Repository |
@Component 的衍生注解 |
标注在数据访问层类上(由于与 mybatis 整合,用的少) |
在idea中查看Bean
在idea中,启动项目后可以查看Bean

点击添加依赖后重新启动项目,再打开此处即可查看

Bean的默认名字为首字母小写的类名,可以自定义Bean的名字,如图所示

Bean的生效范围
前面声明bean的四大注解,要想生效,还需要被组件扫描注解@ComponentScan扫描。
该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中,默认扫描的范围是启动类所在包及其子包。
DI详解
基于@Autowired进行依赖注入的常见方式有如下三种:
属性注入
1
2
3
4
5
6
|
@RestController
public class UserController {
@Autowired
private UserService userService;
//......
}
|
优点:代码简洁、方便快速开发
缺点:隐藏了类之间的依赖关系、可能会破坏类的封装性
构造函数注入(构造器注入)
1
2
3
4
5
6
7
8
9
|
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
}
|
优点:能清晰地看到类的依赖关系、提高了代码的安全性
缺点:代码繁琐、如果构造参数过多,可能会导致构造函数臃肿
注意:如果只有一个构造函数,@Autowired注解可以省略
setter注入
1
2
3
4
5
6
7
8
9
|
@RestController
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
|
优点:保持了类的封装性,依赖关系更清晰
缺点:需要额外编写setter方法,增加了代码量
@Autowired的注意事项
@Autowired注解默认是按照类型进行注入的,如果存在多个相同类型的Bean(多个实现类),将会报错,比如

有三种解决方式:
①**@Primary**
1
2
3
4
5
6
7
8
|
@Primary
@Service
public class UserServiceImpl implements UserService {
@Override
public List<User> list() {
// 省略 ……
}
}
|
②**@Qualifier**
1
2
3
4
5
6
|
@RestController
public class UserController {
@Autowired
@Qualifier("userServiceImpl")
private UserService userService;
}
|
③**@Resource**
1
2
3
4
5
|
@RestController
public class UserController {
@Resource(name = "userServiceImpl")
private UserService userService;
}
|
@Resource 与 @Autowired区别 ?
@Autowired是Spring框架提供的注解,而@Resource是JavaEE规范提供的
@Autowired默认是按照类型注入,而@Resource默认是按照名称注入