当前位置: 首页 > news >正文

JAVA学习-Web基础2 分层解耦

脑子混乱到不知道该干些什么了,这个时候就该拆分一下了。

三层架构

用户列表展示中使用到的请求处理类如下:

@RestController
public class UserController {@RequestMapping("/list")public List<UserData> list() throws Exception {//1.加载并读取文件InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());//2.解析数据并封装List<UserData> userlist = lines.stream().map(line->{String[] parts = line.split(",");Integer id = Integer.valueOf(parts[0]);String username = parts[1];String password = parts[2];String name = parts[3];Integer age = Integer.valueOf(parts[4]);LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));return new UserData(id,username,password,name,age,updateTime);}).toList();//3.响应数据return userlist;}
}

麻雀虽小五脏俱全,其涵盖了三个部分:数据访问,逻辑处理,接受请求响应数据

这也就是经典的三层架构
image

当一个部分受到影响需要修改时,我们不希望涉及到其他两个部分,因此需要进行重构从而提高程序的扩展性和可维护性,这也符合程序设计的单一职责原则(SOLID五大原则之一)

拆分

请求响应代码可拆分为基本的三层:controller(接受请求,响应数据),service(逻辑处理),dao(数据访问)

拆分完之后其结构如图所示:
image
其中I表示interface虚拟接口,而Impl存放接口的实现类
具体代码如下:

//UserDao.java
public interface UserDao {public List<String> findAll();
}
//UserDaoImpl.java
public class UserDaoImpl implements UserDao {@Overridepublic List<String> findAll() {InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());return lines;}
}
//UserService.java
public interface UserService {public List<UserData> findAll();
}
//UserServiceImpl.java
public class UserServiceImpl implements UserService {private UserDao userDao = new UserDaoImpl();@Overridepublic List<UserData> findAll() {List<String> lines = userDao.findAll();List<UserData> userlist = lines.stream().map(line->{String[] parts = line.split(",");Integer id = Integer.valueOf(parts[0]);String username = parts[1];String password = parts[2];String name = parts[3];Integer age = Integer.valueOf(parts[4]);LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));return new UserData(id,username,password,name,age,updateTime);}).toList();return userlist;}
}
//UserController.java
public interface UserController {public List<UserData> findAll();
}
//UserControllerImpl.java
@RestController
public class UserControllerImpl implements UserController {private UserService userService = new UserServiceImpl();@Override@RequestMapping("/list")public List<UserData> findAll() {List<UserData> userList = userService.findAll();return userList;}
}

解构完之后代码结构以及具体内容如上所示,整体的可扩展性以及可维护性提高很多

分层解耦

耦合:衡量软件中各个层/各个模块的依赖程度
内聚:软件中各个功能模块内部的功能联系

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

上面三层架构的代码可以看到UserControllerImpl依赖于UserServiceImpl,而UserServiceImpl依赖于UserDaoImpl,这使得我们需要想办法降低他们的耦合性

设计思路为:设计一个公共的容器,从而使得其不是互相依赖而是依赖于共同的容器,从而降低耦合性,将连接复杂度从n^2较低到n级别

IOC和DI

IOC(Inversion of Control,控制反转)和 DI(Dependency Injection,依赖注入)是 Spring 框架的核心概念,它们共同构成了 Spring 框架的基础。

IOC(控制反转)

IOC 是一种设计原则,它将对象的创建和管理权从程序代码中转移到外部容器。在传统的程序设计中,对象的创建和依赖关系是由程序代码直接管理的。而使用 IOC 后,这些职责被反转给了一个容器(如 Spring 容器),容器负责创建对象、管理对象的生命周期以及处理对象之间的依赖关系。IOC容器中创建管理的对象称之为Bean对象

IOC的核心思想

‌控制权反转‌:原本由程序代码控制的对象创建和依赖关系管理,转由容器来管理。
‌解耦‌:通过 IOC,对象之间的依赖关系被解耦,降低了组件间的耦合度。
‌灵活性‌:通过配置文件或注解,可以轻松地改变对象的依赖关系,而无需修改代码。

DI(依赖注入)

DI 是实现 IOC 的一种具体方式。它指的是将一个对象所依赖的其他对象通过某种方式传递给该对象,而不是让对象自己去创建或查找依赖的对象。DI 通常通过构造函数、Setter 方法或字段注入等方式实现。

DI 的实现方式

‌构造函数注入‌:通过构造函数参数传递依赖对象。
‌Setter 方法注入‌:通过 Setter 方法设置依赖对象。
‌字段注入‌:直接通过字段注入依赖对象(通常使用注解实现)。

实现分层解耦的思路就是:将项目中的类交给IOC容器管理,然后应用程序运行时需要依赖的对象使用DI完成

IOC和DI入门编程

这一部分学了之后才发现其实挺简单的,面向注解编程

我们给被依赖的类前添加注解@Component,给类中用到被依赖的类的地方添加注解@Autowired

@RestController
public class UserControllerImpl implements UserController {@Autowiredprivate UserService userService;@Override@RequestMapping("/list")public List<UserData> findAll() {List<UserData> userList = userService.findAll();return userList;}
}@Component
public class UserServiceImpl implements UserService {@Autowiredprivate UserDao userDao;@Overridepublic List<UserData> findAll() {List<String> lines = userDao.findAll();List<UserData> userlist = lines.stream().map(line->{String[] parts = line.split(",");Integer id = Integer.valueOf(parts[0]);String username = parts[1];String password = parts[2];String name = parts[3];Integer age = Integer.valueOf(parts[4]);LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));return new UserData(id,username,password,name,age,updateTime);}).toList();return userlist;}
}
@Component
public class UserDaoImpl implements UserDao {@Overridepublic List<String> findAll() {InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8,new ArrayList<>());return lines;}
}

然后开始运行,并且可以通过打断点检测中间值,发现确实已经被赋值了
image

IOC详解

IOC注解除了@Component之外,还有以下这些

@Component‌:通用的组件注解,可以作用在任意层的类上。这是最基础的注解,其他三个注解都是它的特化版本。
‌@Controller‌:用于标识控制层的组件,通常与 Spring MVC 结合使用,处理 Web 请求。值得注意的是声明控制层的bean必须只能用@Controller
‌@Service‌:用于标识业务逻辑层的组件,处理具体的业务逻辑。
‌@Repository‌:用于标识数据访问层(DAO 层)的组件,负责数据访问操作。

查看运行时IOC指定的bean对象,bean对象名称如果没有指定的话,默认为类名称首字母小写
image

IOC注解配置完成后,还需要被注解@ComponentScan扫描,但是我们没有配置该注解,因为启动类声明注解@SpringBootApplication中已经包含了前者,但是后者的默认扫描范围是启动类所在包及其子包

DI详解

基于DI往往存在三种方式

//方法一:属性注入
@RestController
public class UserControllerImpl implements UserController {@Autowiredprivate UserService userService;
}//方法二:构造器注入(该方法下如果类中只存在一个构造函数,那么@Autowired可以省略)
@RestController
public class UserControllerImpl implements UserController {private final UserService userService;// @Autowiredprivate UserControllerImpl(UserService userService) {this.userService = userService;}
}//方法三:setter注入
@RestController
public class UserControllerImpl implements UserController {private UserService userService;@Autowiredprivate void setUserControllerImpl(UserService userService) {this.userService = userService;}
}

可以看出,属性注入代码更加简洁,方便快速开发,但是相较于构造器注入和setter注入,前者隐藏(注意是隐藏)了类之间的依赖关系,而后两者能够清晰了解代码之间依赖关系,但是代码量明显增加。

实际开发中,方法一和方法二使用更多。


使用属性注入时有时会存在一个问题:如果出现一个接口对应多个实现类,也就是多个重名的bean对象,会引发报错
image

此时存在三种方法:

方法一:@Primary

此时可以看到报错信息中出现了@Primary,这也就是告诉我们使用@Primary来指定哪个作为Bean对象

@Primary
@Service
public class UserServiceImpl implements UserService {}

方法二:@Qualifier

使用@Qualifier注解来指定bean对象的名称

@RestController
public class UserControllerImpl implements UserController {@Autowired@Qualifier("userServiceImpl")private UserService userService;}

方法三:@Resource

使用@Resource注解来指定,值得注意的是,@Resource是JAVAEE规范提供的注解,而@Autowired是Spring框架提供的注解,前者按照名称注入,后者按照类型注入

@RestController
public class UserControllerImpl implements UserController {@Resource(name="userServiceImpl")private UserService userService;
s
}

终于整理完了

http://www.jsqmd.com/news/497528/

相关文章:

  • Go微服务框架选型比较
  • 前端面试题 - P1
  • Spring Boot 自动装配机制优化方案
  • 【UART】Verilog实现UART接收和发送模块
  • DeepSeek-OCR-2快速入门
  • 【多线程基础】线程状态 同步 协作 线程池 Lambda表达式
  • 软件工程软件开发生命周期瀑布模型与敏捷模型的比较
  • 三、SpringCloud入门概述
  • Python的__getattribute__方法实现属性访问监控与性能分析在调试
  • 设计师高级|表达意图能复现(精品可可,精品巧克力)
  • Python的__getattr__业务对象
  • 滑动窗口滤波的C语言实现(简单易移植)
  • 分布式锁实战嵌入式安全
  • Rust宏编程系统过程宏与声明宏在领域特定语言开发中的应用
  • TypeScript学习笔记 - P1
  • Rust的匹配中的@绑定模式与类型推断在泛型上下文中的行为
  • VMware 安装 Centos7(超详细教程)
  • TypeScript学习笔记 - P2
  • 【BBF系列协议】TR143 诊断协议规范
  • AI 模型推理的批量执行优化方案
  • 0硬件知识体系目录2021-10-12
  • 【BBF系列协议】TR098 InternetGatewayDevice:1根数据模型定义
  • M201-S机顶盒刷机通用教程S905M2芯片S905L芯片线刷卡刷包
  • 记一个BUG:Trae里MongoDB和MySQL MCP不能共存
  • 【BBF系列协议】Data Models Library数据模型库设计与实现
  • vue3学习笔记 - P1
  • 【BBF系列协议】TR104 VoIP CPE的配置参数
  • 机器学习11:代价敏感学习
  • 【BBF系列协议】TR157 CWMP的拓展组件对象
  • git、github、npm、node多版本管理