Home 三层架构与分层解耦
Post
Cancel

三层架构与分层解耦

案例引入

获取员工数据,返回统一响应结果,在页面渲染展示

首先需要引入 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 代码可以优化为

  1. 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);
    }
}
  1. 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;
    }
}
  1. 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;
    }
}

分层解耦

内聚:软件中各个功能模块内部的功能联系

耦合:衡量软件中各个层/模块之间的依赖、关联的程度

软件设计原则:高内聚低耦合

具体见 https://blog.yexca.net/archives/145

例如上例三层架构,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"})

示例

上例中

  1. Controller

由于 @RestController 注解已经包含 @Controller,无需修改

  1. 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;
    }
}
  1. 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 (修改的代码将被注释)

  1. 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);
    }
}
  1. 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;
    }
}
  1. 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 默认是按照名称注入

This post is licensed under CC BY 4.0 by the author.

HTML 学习一 - 基础

JavaScript 学习