案例引入
获取员工数据,返回统一响应结果,在页面渲染展示
首先需要引入 dom4j 依赖,用于解析 xml 文件
1
2
3
4
5
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
引入解析 XML 的工具类 XMLParserUtils、对应实体类 Emp、XML 文件 emp.xml
引入静态页面文件,放在 resources/static
SpringBoot 项目的静态资源 (h5+css+js 等前端资源) 默认存放目录为
clsspath:/static
,classpath:/public
,clsspath:/resources
对应 Maven 来说 classpath 为
src/main/resources
编写 Controller 程序,处理请求,响应数据 (此例代码省略,使用下述三层架构)
三层架构
上例 emp 代码将数据访问、处理逻辑和接收响应请求放在一个 Controller 里,使得复用性差、难以维护,为此需要将其分开以满足单一职责原则,三层架构使得代码复用性强、便于维护、利于拓展
三层架构分为 Controller、Service 与 Dao
- Controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据
- Service:业务逻辑层,处理具体的业务逻辑
- Dao:数据访问层 (Data Access Object) 或持久层,负责数据访问操作,CRUD
浏览器发出请求 -> Controller 接收请求、响应数据 -> Service 逻辑处理 -> Dao 数据访问
上例 emp 代码可以优化为
- Controller
1
2
3
4
5
6
7
8
9
@RestController
public class EmpController {
@RequestMapping("/listEmp")
public Result listEmp(){
EmpService empServiceA = new EmpServiceA();
List<Emp> empList = empServiceA.listEmp();
return Result.success(empList);
}
}
- Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// interface
public interface EmpService {
public List<Emp> listEmp();
}
public class EmpServiceA implements EmpService {
private EmpDao empDao = new EmpDaoA();
public List<Emp> listEmp(){
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("讲师");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就业指导");
}
});
return empList;
}
}
- Dao
1
2
3
4
5
6
7
8
9
10
11
12
// interface
public interface EmpDao {
public List<Emp> listEmp();
}
public class EmpDaoA implements EmpDao {
public List<Emp> listEmp(){
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
return empList;
}
}
分层解耦
内聚:软件中各个功能模块内部的功能联系
耦合:衡量软件中各个层/模块之间的依赖、关联的程度
软件设计原则:高内聚低耦合
例如上例三层架构,Controller 与 Service 耦合,Service 与 Dao 耦合
在 Controller 中直接创建 Service 对象 EmpService empServiceA = new EmpServiceA();
使用 A,若变更为 B 则需要更改 Controller,为了换 Service 而不更改 Controller,可以创建一个容器,使得 Controller 从容器获取对象 (依赖注入) ,Service 把服务注入容器 (控制反转)
- 控制反转:Inverse Of Control,简称 IOC。对象的创建控制权由程序自身转移到外部 (容器)
- 依赖注入:Dependency Injection,简称 DI。容器为应用程序提供运行时,所依赖的资源
- Bean 对象:IOC 容器中创建、管理的对象
IOC
要把某个对象交给 IOC 容器管理,需要在对应的类上加上如下注解之一
注解 | 说明 | 位置 |
---|---|---|
@Component |
声明 bean 的基础注解 | 不属于以下三类时用此注解 (工具类) |
@Controller |
@Component 的衍生注解 | 标注在控制器类上 |
@Service |
@Component 的衍生注解 | 标注在业务类上 |
@Repository |
@Component 的衍生注解 | 标注在数据访问类上 (mybatis 整合,用的少) |
- 声明 bean 时,可以通过 value 属性指定名字,若没有,默认类名首字母小写
- 在 SpringBoot 集成 web 开发中,声明控制器 bean 只能用
@Controller
Bean 组件扫描
上面声明 bean 的四个注解,若想生效,还需被组件扫描注解 @ComponentScan
扫描
此注解实际上已经包含在启动类声明注解 @SpringBootApplication
中,默认的扫描范围是启动类所在包及其子包
通过 value 或者 basePackage 属性指定扫描范围
1
@ComponentScan({"dao","net.yexca"})
示例
上例中
- Controller
由于 @RestController 注解已经包含 @Controller,无需修改
- Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// interface
public interface EmpService {
public List<Emp> listEmp();
}
// 在实现类添加
@Service // 将当前类交给IOC容器管理,成为IOC容器中的Bean
public class EmpServiceA implements EmpService {
private EmpDao empDao = new EmpDaoA();
public List<Emp> listEmp(){
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("讲师");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就业指导");
}
});
return empList;
}
}
- Dao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// interface
public interface EmpDao {
public List<Emp> listEmp();
}
// 在实现类添加
@Repository // 将当前类交给IOC容器管理,成为IOC容器中的Bean
public class EmpDaoA implements EmpDao {
public List<Emp> listEmp(){
String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
return empList;
}
}
DI
使用 @Autowired
注解可以注入依赖,不过默认是按照类型进行,如果存在多个相同类型的 bean 将报错
将以上例子加上 DI (修改的代码将被注释)
- Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class EmpController {
@Autowired // 运行时,IOC容器会提供该类型的Bean对象,并赋值给该变量 -- 依赖注入
private EmpService empService;
@RequestMapping("/listEmp")
public Result listEmp(){
//EmpService empServiceA = new EmpServiceA();
//List<Emp> empList = empServiceA.listEmp();
List<Emp> empList = empService.listEmp();
return Result.success(empList);
}
}
- Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// interface
public interface EmpService {
public List<Emp> listEmp();
}
// 在实现类添加
@Service // 将当前类交给IOC容器管理,成为IOC容器中的Bean
public class EmpServiceA implements EmpService {
@Autowired // 运行时,IOC容器会提供该类型的Bean对象,并赋值给该变量 -- 依赖注入
private EmpDao empDao;
// private EmpDao empDao = new EmpDaoA();
public List<Emp> listEmp(){
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("讲师");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就业指导");
}
});
return empList;
}
}
- Dao
没有数据注入,无需修改
@Primary
若将 Service 添加一个实现类 EmpServiceB 并且也使用了 @Autowired 程序将报错,可以通过 @Primary 注解指定使用哪个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// interface
public interface EmpService {
public List<Emp> listEmp();
}
// 在实现类添加
@Service // 将当前类交给IOC容器管理,成为IOC容器中的Bean
public class EmpServiceA implements EmpService {
@Autowired // 运行时,IOC容器会提供该类型的Bean对象,并赋值给该变量 -- 依赖注入
private EmpDao empDao;
// private EmpDao empDao = new EmpDaoA();
public List<Emp> listEmp(){
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("讲师");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就业指导");
}
});
return empList;
}
}
@Primary // 使用此bean
@Service // 将当前类交给IOC容器管理,成为IOC容器中的Bean
public class EmpServiceB implements EmpService {
@Autowired // 运行时,IOC容器会提供该类型的Bean对象,并赋值给该变量 -- 依赖注入
private EmpDao empDao;
// private EmpDao empDao = new EmpDaoA();
public List<Emp> listEmp(){
List<Emp> empList = empDao.listEmp();
empList.forEach(emp -> {
String gender = emp.getGender();
if("1".equals(gender)){
emp.setGender("男");
} else if ("2".equals(gender)) {
emp.setGender("女");
}
String job = emp.getJob();
if("1".equals(job)){
emp.setJob("讲师");
} else if ("2".equals(job)) {
emp.setJob("班主任");
} else if ("3".equals(job)) {
emp.setJob("就业指导");
}
});
return empList;
}
}
@Qualifier
指定使用哪个 bean,在 Controller 使用,将上方 @Primary 注解取消
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class EmpController {
@Qualifier("empServiceA") // 使用bean empServiceA
@Autowired // 运行时,IOC容器会提供该类型的Bean对象,并赋值给该变量 -- 依赖注入
private EmpService empService;
@RequestMapping("/listEmp")
public Result listEmp(){
//EmpService empServiceA = new EmpServiceA();
//List<Emp> empList = empServiceA.listEmp();
List<Emp> empList = empService.listEmp();
return Result.success(empList);
}
}
@Resource
与 @Qualifier 类似,不过不使用 @Autowired
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
public class EmpController {
// @Qualifier("empServiceA") // 使用bean empServiceA
// @Autowired
@Resource(name = "empServiceB") // 使用bean empServiceB
private EmpService empService;
@RequestMapping("/listEmp")
public Result listEmp(){
//EmpService empServiceA = new EmpServiceA();
//List<Emp> empList = empServiceA.listEmp();
List<Emp> empList = empService.listEmp();
return Result.success(empList);
}
}
@Autowired 与 @Resource 区别
@Autowired 是 Spring 框架提供的注解,而 @Resource 是 JDK 提供的注解
@Autowired 默认是按照类型注入,而 @Resource 默认是按照名称注入