Java笔记-15、Web后端基础 分层解耦
三层架构
满足单一职责原则。
- Controller控制层:接收前端发送的请求,对请求进行处理并响应数据。
- Service业务逻辑层:处理具体的业务逻辑。
- Dao数据访问层(持久层):负责数据访问操作,包括数据的增删改查。
- 浏览器发过来请求,首先访问Controller层,Controller调用Service层,Service层调用Dao层。架构例子如下。
三层架构的例子
// 一个读取文件数据并解析成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对象,而是使用一个容器管理实现类的对象,当需要用到的时候,由容器提供这个对象。
这个思路有两个问题:
- 如何将实现类的对象交给容器管理? 控制反转IOC
- 容器如何为应用提供需要依赖的对象? 依赖注入DI
IOC与DI
IOC DI入门
- 控制反转:Inversion of Control,简称IOC。对象的创建控制权由程序自身转移到外部(IOC容器或者叫Spring容器),这种思想称为控制反转。是Spring框架中的第一大核心。
- 依赖注入: Dependency Injection,简称DI。容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。
- Bean对象:IOC容器中创建、管理的对象,称之为Bean。
依旧考虑上述代码例子,需要分层解耦,就需要完成:
- 将Dao和Service的实现类,交给IOC容器管理。
这一步需要给这两个实现类添加注解:@Component
。 - 为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
进行依赖注入的常见方式有三种。
- 属性注入
@RestController public class UserController { @Autowired private UserService userService; }
- 构造函数注入
@RestController public class UserController { private final UserService userService; // final提高安全性 @Autowired public UserController(UserService userService){ this.userService = userService; } }
- 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,将会报错。
解决方案:
@Primary
:在实现类上指定。
@Primary @Service public class UserServiceImpl2 implements UserService { @Override public List<User> List(){...} }
@Qualifier
+@Autowired
:在注入时指定,配合@Autowired
。
@RestController public class UserController { @Autowired @Qualifier("userServiceImpl2") private UserService userService; }
@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 协议许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步