一、Java Web 三层架构

Java Web 应用可以大致分为三层:

  • Controller 层(控制层)
  • Service 层(业务逻辑层)
  • Dao 层(数据访问层)

1. Controller 层

Controller 层主要负责接收前端发送的 HTTP 请求,并对请求进行处理后响应数据。

  • 职责
    • 接收来自客户端(如浏览器、移动设备等)的 HTTP 请求(GET、POST、PUT、DELETE 等)。
    • 解析请求中的参数(如查询字符串或表单数据)。
    • 调用 Service 层的方法处理业务逻辑。

:Controller 层不直接实现业务逻辑,而是通过调用 Service 层的方法来完成具体操作。

2. Service 层

Service 层负责实现具体的业务逻辑,是应用的核心部分。

  • 职责
    • 实现复杂且核心的业务逻辑(例如银行系统中的转账业务)。
    • 管理事务处理,确保多个数据库操作要么全部成功(提交事务),要么全部失败(回滚事务)。
    • 为 Controller 层提供业务处理方法。

3. Dao 层

Dao 层(Data Access Object 层)主要负责数据访问和持久化操作。

  • 职责
    • 将数据库操作封装为统一的接口,屏蔽具体数据库(如 MySQL、Oracle)及其底层细节。
    • 执行数据的增删改查操作,例如调用 findUserByIdsaveUser 方法。
    • 实现数据的持久化,因而也被称为持久层。

插图展示

Java Web 三层架构

三层架构动态图


二、分层解耦

在软件开发中,高内聚低耦合是提高灵活性和可扩展性的关键设计思想。

  • 内聚:模块内部功能的联系。
  • 耦合:模块之间的依赖和关联程度。

在开发过程中,我们应尽量减少各层之间的依赖关系,实现解耦设计。

想要解耦,可以将一些对象放到容器中,在有容器去分配对象的使用

示例说明

以下内容以 Spring 框架为例,介绍如何通过控制反转(IoC)和依赖注入(DI)实现解耦。


三、控制反转(IoC)

在未使用 Spring 等框架前,对象之间的依赖通常由对象自己创建和管理,如下所示:

1
2
3
4
5
6
7
public class BusinessLogicClass {
public void doSomeBusinessLogic() {
DataAccessClass dataAccess = new DataAccessClass();
// 使用 dataAccess 对象进行数据操作
}
}

这种方式会导致业务逻辑类与数据访问类紧密耦合,当数据访问层的实现发生变化时,业务逻辑类也需做相应修改。

控制反转(IoC) 的思想是将对象的创建和依赖关系管理交给外部容器(如 Spring 容器),由容器负责创建和管理对象(bean)。例如:

1
2
3
4
5
6
7
8
9
10
11
12
public class BusinessLogicClass {
private DataAccessInterface dataAccess;

public BusinessLogicClass(DataAccessInterface dataAccess) {
this.dataAccess = dataAccess;
}

public void doSomeBusinessLogic() {
// 使用 dataAccess 对象进行数据操作
}
}

在这个例子中,BusinessLogicClass 通过构造函数接收实现了 DataAccessInterface 的对象,从而只依赖于接口而非具体实现,达到了低耦合的目的。

控制反转示意图

1
2
3
4
5
6
@Component // IOC将该类交给bean管理
public class UserServiceImpl implments UserService {
@Autowired
private UserDao userdap; // 加上了这个代表会自动去找bean中相应对象并实例化

}

  • 注意@Component是加载实现类上而非接口上

IOC详解:

  • 要把某个对象交给IOC容器管理,要在类上加上一下注解之一
    • @Component : 声明bean的基础注解
    • @Controller : 标注在控制层上
    • @Service : 标注在业务层上
    • @Repository :

bean的名字 : 默认是把你类名的首字母换成小写, 也可以在声明bean的时候声明名字,在注解后面打个括号后写入名字

IOC 的四大注解想要生效,必须具备组件扫描注解@ComponentScan扫描,这个注解没有显式h配置,但是在启动类的生命注解@SpringBootApplication中,默认的扫描范围是启动类所在的包和他的子包.

声明SpringBoot集成Web开发中,声明控制器bean只能用@Controller


四、依赖注入(DI)

依赖注入是指由容器在运行时将所需的依赖资源注入到对象中。Spring 框架支持多种依赖注入方式:

  • 构造函数注入:在对象创建时通过构造函数传入依赖对象。(代码简洁但是隐藏了类的依赖关系)
  • Setter 注入:通过 setter 方法注入依赖对象。 (如果只有一个构造函数@Autowired可以省略)
  • 字段注入:直接在字段上使用注解(如 @Autowired)进行注入。

下面是字段注入的示例:

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
import org.springframework.beans.factory.annotation.Autowired;

// 属性注入
public class BusinessLogicClass {
@Autowired
private DataAccessInterface dataAccess;

public void doSomeBusinessLogic() {
// 使用 dataAccess 对象进行数据操作
}
}

// 使用构造函数注入
public class A {
private final USerService user
@Autowired
public void A(UserService uservice) {
this.user = uservice;
}
}

// setter注入
public class A {
private final USerService user
@Autowired
public void setuser(UserService uservice) {
this.user = uservice;
}
}

有时候在一个包内注入了多个同类型的bean,此时会报错,解决方法 :

  • @Primary 注解在多个可选的相同类型的 bean 存在时,告诉 Spring 容器优先选择带有 @Primary 注解的 bean 进行依赖注入。这避免了需要使用限定符(如 @Qualifier 注解)来指定所需的 bean。
  • @Qualifier 直接指定想要使用对应的bean的名字(记得名字是类名首字母小写或者是自己指定),
  • @Resource(name = "") : 在name里面指定需要的bean的名字

Resource和Autowired的区别:

  • @Autowired是spring提供的注解,而@Resource是JavaEE提供的
  • @Autowired是按照类型注入,而@Resource是按照名称注入

    Bean对象 :

在IOC容器中创建,管理的对象就是Bean

DI 示例

DI 另一示例


五、解耦的好处

1. 可维护性

  • 接口解耦:当某个模块的实现发生变化时,只需修改对应的实现类,不会影响其他模块。
  • 例如:数据库从一种切换到另一种时,仅需调整数据访问层的实现和配置。

2. 可测试性

  • 通过依赖注入,可以在单元测试中注入模拟对象(Mock 对象),隔离其他模块,专注于测试业务逻辑。

3. 可扩展性

  • 新功能模块的集成只需在 Spring 配置文件中定义新的 bean,并通过依赖注入整合到系统中,实现灵活扩展。

以上内容详细阐述了 Java Web 的三层架构以及如何通过控制反转和依赖注入实现解耦设计,从而提升系统的可维护性、可测试性和可扩展性。