节制,是比仁慈更高尚的品德。|

subeipo

园龄:1个月粉丝:0关注:0

📂Java
🔖Java
2025-02-14 00:00阅读: 7评论: 0推荐: 0

Java笔记-15、Web后端基础 分层解耦

三层架构

满足单一职责原则。

  1. Controller控制层:接收前端发送的请求,对请求进行处理并响应数据。
  2. Service业务逻辑层:处理具体的业务逻辑。
  3. Dao数据访问层(持久层):负责数据访问操作,包括数据的增删改查。
  • 浏览器发过来请求,首先访问Controller层,Controller调用Service层,Service层调用Dao层。架构例子如下。
    CleanShot 2025-02-13 at 23.38.24@2x

三层架构的例子

// 一个读取文件数据并解析成JSON的例子
@RestController
public class UserController {
@RequestMapping("/list")
public List<User> list() throws Exception {
//1.加载并读取user.txt文件,获取用户
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
//2.解析用户信息,封装User对象到List集合中
List<User> userList = lines.stream().map(line -> {
String[] parts = line.split(",");
Integer id = Integer.parseInt(parts[0]);
String username = parts[1];
String password = parts[2];
String name = parts[3];
Integer age = Integer.parseInt(parts[4]);
LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return new User(id, username, password, name, age, updateTime);
}).collect(Collectors.toList());
//3.将List集合转换为JSON格式的字符串,并返回
return userList;
}
}

现在将其拆分。

public interface UserDao {
/**
* 加载用户数据
* @return
*/
public List<String> findAll();
}
///////////////////////////////////////
public class UserDaoImpl implements UserDao {
@Override
public List<String> findAll() {
//1.加载并读取user.txt文件,获取用户
InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
return lines;
}
}
///////////////////////////////////////
public interface UserService {
/**
* 查询所有用户信息
* @return
*/
public List<User> findAll();
}
///////////////////////////////////////
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl(); // 出现了耦合
@Override
public List<User> findAll() {
List<String> lines = userDao.findAll();
List<User> userList = lines.stream().map(line -> {
String[] parts = line.split(",");
Integer id = Integer.parseInt(parts[0]);
String username = parts[1];
String password = parts[2];
String name = parts[3];
Integer age = Integer.parseInt(parts[4]);
LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return new User(id, username, password, name, age, updateTime);
}).collect(Collectors.toList());
return userList;
}
}
///////////////////////////////////////
@RestController
public class UserController {
private UserService userService = new UserServiceImpl(); // 出现了耦合
@RequestMapping("/list")
public List<User> list() throws Exception {
List<User> userList = userService.findAll();
//3.将List集合转换为JSON格式的字符串,并返回
return userList;
}
}

分层解耦

耦合:衡量软件中各个层/各个模块的依赖关联程度。
内聚:软件中各个功能模块内部的功能联系。
软件设计原则:高内聚低耦合。

上述代码中有模块件的耦合,在调用Service层和Dao层时,new了对象。

private UserDao userDao = new UserDaoImpl();

解耦合的思路是:在调用Service层和Dao层时不需要new对象,而是使用一个容器管理实现类的对象,当需要用到的时候,由容器提供这个对象。

这个思路有两个问题:

  1. 如何将实现类的对象交给容器管理? 控制反转IOC
  2. 容器如何为应用提供需要依赖的对象? 依赖注入DI

IOC与DI

IOC DI入门

  • 控制反转:Inversion of Control,简称IOC。对象的创建控制权由程序自身转移到外部(IOC容器或者叫Spring容器),这种思想称为控制反转。是Spring框架中的第一大核心。
  • 依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
  • Bean对象:IOC容器中创建、管理的对象,称之为Bean。

依旧考虑上述代码例子,需要分层解耦,就需要完成:

  1. 将Dao和Service的实现类,交给IOC容器管理。
    这一步需要给这两个实现类添加注解:@Component
  2. 为Controller 及 Service注入运行时所依赖的对象。
    这一步需要在成员变量上添加注解:@Autowired。应用程序运行时,会自动的查询该类型的bean对象,并赋值给该成员变量。
@Autowired
private UserDao userDao;

IOC详解

四大注解

要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:

注解 说明 位置
@Component 声明bean的基础注解 不属于以下三类时,用此注解
@Controller @Component的衍生注解 标注在控制层类上
@Service @Component的衍生注解 标注在业务层类上
@Repository @Component的衍生注解 标注在数据访问层类上(由于与mybatis整合,用的少)

在Springboot集成web开发中,声明控制器bean只能用@Controller。但一般Controller不用再加@Controller,因为@RestController已经封装了@Controller
容器中bean的默认名字就是类名首字母小写。

注解的生效

  • bean的四大注解要想生效,还需要被组件扫描注解@ComponentScan扫描。
  • 该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包

DI详解

注入方式

基于@Autowired进行依赖注入的常见方式有三种。

  1. 属性注入
@RestController
public class UserController {
@Autowired
private UserService userService;
}
  1. 构造函数注入
@RestController
public class UserController {
private final UserService userService; // final提高安全性
@Autowired
public UserController(UserService userService){
this.userService = userService;
}
}
  1. setter注入
@RestController
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService){
this.userService = userService;
}
}

注入方式的选择

属性注入的优缺点

优点:代码简洁、方便快速开发。
缺点:隐藏了类之间的依赖关系、可能会破坏类的封装性。

按照Java面向对象来说,封装应该对成员变量有setter\getter方法进行赋值和获取,但加上@Autowired底层是通过反射对成员变量赋值,破坏了封装。

构造函数注入的优缺点

优点:能清晰地看到类的依赖关系、提高了代码的安全性。
缺点:代码繁琐、如果构造参数过多,可能会导致构造函数臃肿。

注意:如果只有一个构造函数,@Autowired注解可以省略。

setter注入的优缺点

优点:保持了类的封装性,依赖关系更清晰。
缺点:需要额外编写setter方法,增加了代码量。

官方推荐与实践选择
  • 在Spring的官方文档中,推荐使用构造函数注入。
  • 在开发实践中,大多数项目组使用的是属性注入。

注意事项

  • @Autowired注解,默认是按照类型进行注入的。
    如果存在多个相同类型的bean,将会报错。
    解决方案:
  1. @Primary:在实现类上指定。
@Primary
@Service
public class UserServiceImpl2 implements UserService {
@Override
public List<User> List(){...}
}
  1. @Qualifier+@Autowired:在注入时指定,配合@Autowired
@RestController
public class UserController {
@Autowired
@Qualifier"userServiceImpl2"
private UserService userService;
}
  1. @Resource:java自带注解,不是Spring提供的,在注入时指定。不需要@Autowired
@RestController
public class UserController {
@Resource(name = "userServiceImpl2"
private UserService userService;
}

本文作者:subeipo

本文链接:https://www.cnblogs.com/subeipo/p/18714621/java-bi-ji15web-hou-duan-ji-chu-fen-ceng-jie-ou

版权声明:本作品采用署名—非商业性使用—相同方式共享 4.0 协议许可协议进行许可。

posted @   subeipo  阅读(7)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起